/*******************************************************************************
Copyright Datapath Ltd. 2015.

File:    Sample6B.cpp

History: 27 MAR 15    RL   Created.
         12 OCT 15    DC   Added further hard-coded URLs.
                           Included GOP Length as an option in URLs.

*******************************************************************************/

#include <liveMedia.hh>
#include <BasicUsageEnvironment.hh>
#include <GroupsockHelper.hh>

#include <rgb.h>
#include <rgbh264nal.h>

//#define _LOCAL_HEAP_DEBUG
#ifdef _LOCAL_HEAP_DEBUG
#include <CRTDBG.h>
#endif

/******************************************************************************/

static UsageEnvironment *_env = 0;
// Required for busy 1080p I frames

#define MILLI_MS_SECOND 1000
#define MICRO_US_SECOND 1000000

class CRGBEasyH264FrameSource;

typedef struct _RGBEASYH264STREAM
{
   unsigned long           Input;
   uint32_t                Width;
   uint32_t                Height;
   uint32_t                FrameRate; // mHz
   DGCENCLEVEL             Level;
   DGCENCPROFILE           Profile;
   uint32_t                Bitrate; // bps
   uint32_t                KeyFrameInterval; // GOP Length
   ServerMediaSession      *PServerMediaSession;
   H264VideoStreamDiscreteFramer *PVideoDiscreteSource;
   RTCPInstance            *PRtcpInstance;
   Groupsock               *PRtpGroupsock;
   Groupsock               *PRtcpGroupsock;
   RTPSink                 *PVideoSink;
} RGBEASYH264STREAM, *PRGBEASYH264STREAM;

char gStop = 0;

/******************************************************************************/
#if 0
char const* nal_unit_description_h264[32] = {
  "Unspecified", //0
  "Coded slice of a non-IDR picture", //1
  "Coded slice data partition A", //2
  "Coded slice data partition B", //3
  "Coded slice data partition C", //4
  "Coded slice of an IDR picture", //5
  "Supplemental enhancement information (SEI)", //6
  "Sequence parameter set", //7
  "Picture parameter set", //8
  "Access unit delimiter", //9
  "End of sequence", //10
  "End of stream", //11
  "Filler data", //12
  "Sequence parameter set extension", //13
  "Prefix NAL unit", //14
  "Subset sequence parameter set", //15
  "Reserved", //16
  "Reserved", //17
  "Reserved", //18
  "Coded slice of an auxiliary coded picture without partitioning", //19
  "Coded slice extension", //20
};
#endif
/******************************************************************************/

VOID NanoSecondsToTimeVal( uint64_t TimeStamp, timeval *pTV )
{
   uint64_t rem;

   pTV->tv_sec = (long)((uint64_t)(TimeStamp / 10000000));
   rem = TimeStamp % 10000000;
   pTV->tv_usec = (long)((uint64_t)(rem / 10));
}

VOID TimeValToNanoSeconds( timeval *pTV, uint64_t *pTimeStamp )
{
   *pTimeStamp = (((uint64_t) pTV->tv_sec)  * 10000000) +
                 (((uint64_t) pTV->tv_usec) * 10);
}

/******************************************************************************/

class CRGBEasyH264FrameSource : public FramedSource
{
   struct timeval fPresentationTimeEx;
   CRGBEasyH264 *m_pRGBEasyH264;
   void *m_pToken;
   uint32_t m_input;
   uint32_t m_error;
   uint64_t m_timeoffset;

public:
   CRGBEasyH264FrameSource (UsageEnvironment &env,
                             int input,
                             uint32_t width,
                             uint32_t height,
                             uint32_t frameRate,
                             DGCENCLEVEL level,
                             DGCENCPROFILE profile,
                             uint32_t bitrate,
                             uint32_t keyframeinterval ) : FramedSource( env ),
                             m_timeoffset( 0 )
	{
      uint32_t Error = 0;
		m_pToken = NULL;
      m_input = input;
      fPresentationTimeEx.tv_sec = 0;
      fPresentationTimeEx.tv_usec = 0;
      m_pRGBEasyH264 = new CRGBEasyH264 ( input );
      if((Error = m_pRGBEasyH264->RGBEasyH264Start ( width, height, frameRate,
                                        level, profile, bitrate,
                                        keyframeinterval )) == 0)
      {
      OutPacketBuffer::maxSize = (width * height * 3) / 2; // NV12 data format from encoder, assume no compression.
	}
      else
      {
         // We're in a constructor, so extraordinary measures are needed to fail this call.
         // Caller must wrap in a try{} block to discover the error, otherwise whole application unwinds and faults in main()
         delete m_pRGBEasyH264;
         throw Error;
      }
	}

	~CRGBEasyH264FrameSource ()
	{
      if ( m_pToken )
      {
			envir().taskScheduler().unscheduleDelayedTask(m_pToken);
		}
      m_pRGBEasyH264->RGBEasyH264Stop ( );
      delete m_pRGBEasyH264;
	}

protected:
   // Overridden from derived FrameSource class
	virtual void doGetNextFrame ()
	{
      RealGetNextFrame();
	}

private:
	static void getNextFrame (void *ptr)
	{
      // Static to run-time class adapter call.
		((CRGBEasyH264FrameSource*)ptr)->RealGetNextFrame();
	}

   // It would appear that it is incumbent upon this function to - at some point in the future - return a NAL
   // from the source.  In the case that there isn't already a NAL waiting on the queue, this function should
   // schedule a call to itself again, to make sure that the next delivered NAL is picked up in a timely
   // manner.
   // Inspection of the code doesn't show that this function actually returns a frame.  It returns a NAL.
   // Now, it could be that a NAL is also known as a frame, but to me a frame means "of video".  And given that
   // the reader process doesn't process NALs quickly enough such that over time the list willl grow and keep
   // growing, I'm wondering if there's a bug here?
	void RealGetNextFrame ()
   {
      static uint64_t lastts = 0;
      uint64_t timeStamp = 0;
      m_pToken = NULL;

      m_error = m_pRGBEasyH264->RGBEasyH264GetNAL ( fTo, fMaxSize, 
            &fNumTruncatedBytes, &fFrameSize, &timeStamp );

      if ( m_error == 0 )
      {
         // NAL returned from CRGBEasyH264 class.
         fDurationInMicroseconds = 0;
         if ( DoesH264NALUnitBeginNewAccessUnit(fTo) )
         {
            // If the NAL begins a new frame, we recalculate (and cache) the new frame presentation time
            // also, take the opportunity to cope with the timestamp values wrapping round.
            // It would also be good to check the magnitude of clock-drift here and adjust our time deltas
            // according to how much drift there is on the SQX clock.
            if (( m_timeoffset == 0 )|| (lastts > timeStamp))
            {
               struct timeval tvtimeOfDay;
               uint64_t timeOfDay;
               gettimeofday( &tvtimeOfDay, 0 );
               TimeValToNanoSeconds( &tvtimeOfDay, &timeOfDay );
               m_timeoffset = timeOfDay - timeStamp;
            }

            lastts = timeStamp;
            
            NanoSecondsToTimeVal( timeStamp + m_timeoffset, &fPresentationTimeEx );
            // TODO: use EndTime.
            fDurationInMicroseconds = 
               MICRO_US_SECOND / (m_pRGBEasyH264->m_StreamInfo.FPS / MILLI_MS_SECOND);
         }
         // fPresentationTimeEx is either the previous (frame beginning) NAL's presentation time, or the presentation
         // time for this new frame NAL.
         fPresentationTime = fPresentationTimeEx;
         afterGetting(this);
         // Something in the call stack sets up another scheduled event for the next NAL to be read.
         // However, this will cause the queue to grow uncontrollably as NALs may be added in pairs
         // which are only fetched (apparently) singly at the same rate.
         return;
      }
      else
      {
         // No NAL returned from CRGBEasyH264 class, wait a frame time and try again.
         int64_t microseconds;
         
         // Has CRGBEasyH264 class finished initialising with a signal attached?
         if ( m_pRGBEasyH264->m_StreamInfo.FPS )
         {
            microseconds = MICRO_US_SECOND / ( 
               m_pRGBEasyH264->m_StreamInfo.FPS / MILLI_MS_SECOND );
            // microseconds = 1;
         }
         else // wait 16.6ms
         {
            microseconds = MICRO_US_SECOND / ( 60000 / MILLI_MS_SECOND ); 
         }

         m_pToken = envir().taskScheduler().scheduleDelayedTask(
               microseconds, getNextFrame, this);
         return;     
	   }
   }
};

/******************************************************************************/

uint32_t CreateServerMediaSession(PRGBEASYH264STREAM pStream)
{
   TCHAR streamName[255], description[255];
   
   wsprintf( streamName, TEXT( "input%d?resolution=%dx%d&rate=%d" ),
      pStream->Input + 1, pStream->Width, pStream->Height, 
      pStream->FrameRate );
   wsprintf( description, TEXT( "RGBEasy H264 Multicast Session %s" ), 
      streamName );

   pStream->PServerMediaSession = ServerMediaSession::createNew( *_env, 
      streamName, 0, description, True /*SSM*/ );
   if ( pStream->PServerMediaSession )
   {
      pStream->PServerMediaSession->addSubsession ( 
         PassiveServerMediaSubsession::createNew(*pStream->PVideoSink, 
         pStream->PRtcpInstance));
      //*pServerMediaSession->addSubsession ( 
      //   PassiveServerMediaSubsession::createNew(*pStream->PAudioSink, 
      //   pStream->PRtcp));
      return 0;
   }
   return -1;
}

/******************************************************************************/

uint32_t CreateGroupSockets(PRGBEASYH264STREAM pStream)
{
   // Create 'groupsocks' for RTP and RTCP:
   struct in_addr destinationAddress;
   destinationAddress.s_addr = chooseRandomIPv4SSMAddress(*_env);
   // Note: This is a multicast address.  If you wish instead to stream
   // using unicast, then you should use the "testOnDemandRTSPServer"
   // test program - not this test program - as a model. See Sample6A.
#if 0  
   const unsigned short rtpPortNum = 18888;
   const unsigned short rtcpPortNum = rtpPortNum+1;
#else
   const unsigned short rtpPortNum = 18888+(USHORT)pStream->Input;
   const unsigned short rtcpPortNum = 28888+(USHORT)pStream->Input;
#endif
   const unsigned char ttl = 255;

   const Port rtpPort(rtpPortNum);
   const Port rtcpPort(rtcpPortNum);

   pStream->PRtpGroupsock = new Groupsock(*_env, 
      destinationAddress, rtpPort, ttl);
   pStream->PRtpGroupsock->multicastSendOnly(); // we're a SSM source
   pStream->PRtcpGroupsock = new Groupsock(*_env, 
      destinationAddress, rtcpPort, ttl);
   pStream->PRtcpGroupsock->multicastSendOnly(); // we're a SSM source

   // Create a 'H264 Video RTP' sink from the RTP 'groupsock':
   pStream->PVideoSink = H264VideoRTPSink::createNew(*_env, 
      pStream->PRtpGroupsock, 96);
   if( pStream->PVideoSink )
   {
      // Create (and start) a 'RTCP instance' for this RTP sink:
      const unsigned estimatedSessionBandwidth = 500; // in kbps; for RTCP
      const unsigned maxCNAMElen = 100;
      unsigned char CNAME[maxCNAMElen+1];
      gethostname((char*)CNAME, maxCNAMElen);
      CNAME[maxCNAMElen] = '\0'; // just in case
      pStream->PRtcpInstance = RTCPInstance::createNew(*_env, 
         pStream->PRtcpGroupsock,
		   estimatedSessionBandwidth, CNAME,
		   pStream->PVideoSink, NULL /* we're a server */,
		   True /* we're a SSM source */);
      return 0;
   }
   return -1;
}

/******************************************************************************/

void Play ( PRGBEASYH264STREAM pStream);

void AfterPlaying(void* pClientData) 
{
   // This should not occur for a live source.
   PRGBEASYH264STREAM pStream = (PRGBEASYH264STREAM)pClientData;
   *_env << "...EOF reached\n";
   pStream->PVideoSink->stopPlaying();

   Medium::close(pStream->PVideoDiscreteSource);
   // Try playing once again:
   Play(pStream);
}

/******************************************************************************/

void Play ( PRGBEASYH264STREAM pStream) 
{
   pStream->PVideoDiscreteSource = H264VideoStreamDiscreteFramer::createNew(*_env, 
      new CRGBEasyH264FrameSource( *_env, pStream->Input, pStream->Width, 
      pStream->Height, pStream->FrameRate, pStream->Level, pStream->Profile, 
      pStream->Bitrate, pStream->KeyFrameInterval ));
    
   pStream->PVideoSink->startPlaying(*pStream->PVideoDiscreteSource,  
      AfterPlaying, pStream);
}

/******************************************************************************/

BOOL WINAPI HandlerRoutine( DWORD dwCtrlType )
{
   gStop = 1;
   return TRUE;
}

/******************************************************************************/

int main( int argc, TCHAR **argv )
{
   uint32_t inputCount, h264Count, i;
   uint32_t *pInputList;
   
   TaskScheduler *pScheduler = BasicTaskScheduler::createNew();
   _env = BasicUsageEnvironment::createNew(*pScheduler);

#ifdef _LOCAL_HEAP_DEBUG
   {
      int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );

      // Turn on leak-checking bit.
      tmpFlag |= _CRTDBG_LEAK_CHECK_DF;

      // Turn off CRT block checking bit.
      tmpFlag &= ~_CRTDBG_CHECK_CRT_DF;

      // Set flag to the new value.
      _CrtSetDbgFlag( tmpFlag );
   }
#endif

   if ( GetSupportedH264Inputs (&pInputList, &inputCount, &h264Count) == 0 )
   {
#ifdef ACCESS_CONTROL
     UserAuthenticationDatabase* authDB = NULL;
     // To implement client access control to the RTSP server, do the following:
     authDB = new UserAuthenticationDatabase;
     authDB->addUserRecord("username1", "password1"); // replace these with real strings
     // Repeat the above with each <username>, <password> that you wish to allow
     // access to the server.
#endif

      // Create the RTSP server.  Try first with the default port number (554),
      // and then with any alternative port numbers
      RTSPServer* rtspServer = NULL;
      uint32_t port=554;
      do 
      {
         rtspServer = RTSPServer::createNew(*_env, port /*authDB*/);
         if(port==554) port=8553;
         port++;
      } while(rtspServer == NULL && port<9555);

	   if (rtspServer) 
      {
         // Set up each of the possible streams that can be served by the
         // RTSP server.  Each such stream is implemented using a
         // "ServerMediaSession" object, plus one or more
         // "ServerMediaSubsession" objects for each audio/video substream.
         PRGBEASYH264STREAM pStream = (PRGBEASYH264STREAM)malloc (sizeof(RGBEASYH264STREAM)*
                                                                   h264Count);
         if ( pStream )
         {
            unsigned long j =0;
            memset ( pStream, 0, sizeof(RGBEASYH264STREAM)*h264Count);
            for ( i=0; i< inputCount; i++)
            {
               if ( pInputList[i] ) // if input supports h264
               {
                  pStream[j].Input = i;
                  pStream[j].Level = DGCENC_H264_LEVEL_4_2;
                  pStream[j].Profile = DGCENC_H264_PROFILE_MAIN;
                  pStream[j].Bitrate = 50000000;
                  pStream[j].KeyFrameInterval = 90;
                  // Get 'on the wire' defaults or use above defaults.
                  if ( GetInputSignalType ( i, &pStream[j].Width, &pStream[j].Height, 
                        &pStream[j].FrameRate ))
                  {
                     pStream[j].Width = 1920;
                     pStream[j].Height = 1080;
                     pStream[j].FrameRate = 30000;
                  }
                  if ( CreateGroupSockets (&pStream[j]) == 0 )
                  {
                     if ( CreateServerMediaSession (&pStream[j]) == 0 )
                     {
                        // Add the media session to the RTSP server
                        rtspServer->addServerMediaSession(pStream[j].PServerMediaSession);
                        *_env << "using url \"" << rtspServer->rtspURL(
                           pStream[j].PServerMediaSession) << "\"\n";
                        Play(&pStream[j]);
                     }
                     else
                        *_env << "ERROR - creating media session\n";
                  }
                  else
                     *_env << "ERROR - creating group sockets\n";
                  j++;
               }
               *_env << "\n";
            }

            // Add a CTRL-C handler routine.
            SetConsoleCtrlHandler( HandlerRoutine, TRUE );
            gStop = 0;
            *_env << "Beginning to stream in 5 seconds...\n";
            // Chance to read the URI's!
            Sleep(5000);
	         _env->taskScheduler().doEventLoop( &gStop );
            
            for ( i=0; i< inputCount; i++)
            {
               if ( pStream[i].PRtpGroupsock )
                  delete pStream[i].PRtpGroupsock;
               if ( pStream[i].PRtcpGroupsock )
                  delete pStream[i].PRtcpGroupsock;
            }
            free ( pStream );
         }
      }
      else
         *_env << "ERROR - creating RTSPServer\n";

      free( pInputList );
   }
   else
      *_env << "ERROR - No H264 inputs supported\n";

#ifdef _LOCAL_HEAP_DEBUG
   _CrtDumpMemoryLeaks( );
#endif

   Sleep(3000);

	return 0;
}

/******************************************************************************/
