#include "FFMPEGEncoder.hh"
#include "FFMPEGDummySource.hh"

#include "liveMedia.hh"
#include "BasicUsageEnvironment.hh"
#include "GroupsockHelper.hh"

//Define which streams to generate (only  streaming audio does not seem to work, though...)
#define VIDEO
#define AUDIO

//#define AUDIO_USING_ADUS // not working...?

#define FORMAT              "avi"
#define VIDEO_CODEC         "mpeg4"
#define AUDIO_CODEC         "mp2"
#define VIDEO_BIT_RATE      400000
#define VIDEO_WIDTH         768
#define VIDEO_HEIGHT        432
#define AUDIO_BIT_RATE      128000
#define AUDIO_SAMPLE_RATE   48000
#define AUDIO_NUM_CHANNELS  2

char const* remoteStreamName = "test.sdp"; 
const char* dssNameOrAddress = "127.0.0.1";

UsageEnvironment*       env;
FFMPEGEncoder*          encoder;

FramedSource*           videoSource;
FramedSource*           audioSource;
RTPSink*                videoSink;
RTPSink*                audioSink;

// function declarations
Boolean awaitConfigInfo(RTPSink* sink);
void play();

const char* programName;

int main(int argc, char** argv) 
{  
    programName = argv[0];
    
    encoder = new FFMPEGEncoder(FORMAT, VIDEO_CODEC, AUDIO_CODEC, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_BIT_RATE, 
                                AUDIO_BIT_RATE, AUDIO_SAMPLE_RATE, AUDIO_NUM_CHANNELS);
    
    // Begin by setting up our usage environment:
    TaskScheduler* scheduler = BasicTaskScheduler::createNew();
    env = BasicUsageEnvironment::createNew(*scheduler);
    
    // Create a 'Darwin injector' object:
    DarwinInjector* injector = DarwinInjector::createNew(*env, programName);
    
    // Create 'groupsocks' for RTP and RTCP:
    char* destinationAddressStr = "127.0.0.1";
    const unsigned short rtpPortNumAudio = 6666;
    const unsigned short rtcpPortNumAudio = rtpPortNumAudio+1;
    const unsigned short rtpPortNumVideo = 8888;
    const unsigned short rtcpPortNumVideo = rtpPortNumVideo+1;
    const unsigned char ttl = 7;
    
    struct in_addr destinationAddress;
    destinationAddress.s_addr = our_inet_addr(destinationAddressStr);
    const Port rtpPortAudio(rtpPortNumAudio);
    const Port rtcpPortAudio(rtcpPortNumAudio);
    const Port rtpPortVideo(rtpPortNumVideo);
    const Port rtcpPortVideo(rtcpPortNumVideo);
    
    const unsigned maxCNAMElen = 100;
    unsigned char CNAME[maxCNAMElen+1];
    gethostname((char*)CNAME, maxCNAMElen);
    CNAME[maxCNAMElen] = '\0';
#ifdef AUDIO    
    ////////// AUDIO //////////
    struct in_addr dummyDestAddress;
    dummyDestAddress.s_addr = 0;
    Groupsock rtpGroupsockAudio(*env, destinationAddress, rtpPortAudio, ttl);
    Groupsock rtcpGroupsockAudio(*env, destinationAddress, rtcpPortAudio, ttl);
    
    // Create a 'MPEG Audio RTP' sink from the RTP 'groupsock':
#ifdef AUDIO_USING_ADUS
    audioSink= MP3ADURTPSink::createNew(*env, &rtpGroupsockAudio, 96);
#else
    audioSink = MPEG1or2AudioRTPSink::createNew(*env, &rtpGroupsockAudio);
#endif    
    // Create (and start) a 'RTCP instance' for this RTP sink:
    const unsigned estimatedSessionBandwidthAudio = AUDIO_BIT_RATE/1000; // in kbps; for RTCP b/w share
    RTCPInstance* audioRTCP =
        RTCPInstance::createNew(*env, &rtcpGroupsockAudio,
                                estimatedSessionBandwidthAudio, CNAME,
                                audioSink, NULL);
    // Note: This starts RTCP running automatically
    
    // Add these to our 'Darwin injector':
    injector->addStream(audioSink, audioRTCP);
    ////////// END AUDIO //////////
#endif    
#ifdef VIDEO
    ////////// VIDEO //////////
    Groupsock rtpGroupsockVideo(*env, destinationAddress, rtpPortVideo, ttl);
    Groupsock rtcpGroupsockVideo(*env, destinationAddress, rtcpPortVideo, ttl);
    
    // Create a 'MPEG-4 Video RTP' sink from the RTP 'groupsock':
    videoSink = MPEG4ESVideoRTPSink::createNew(*env, &rtpGroupsockVideo, 96);
    
    // HACK, specifically for MPEG-4 video:
    // Before we can use this RTP sink, we need its MPEG-4 'config' information (for
    // use in the SDP description).  Unfortunately, this config information depends
    // on the the properties of the MPEG-4 input data.  Therefore, we need to start
    // 'playing' this RTP sink from the input source now, and wait until we get
    // the needed config information, before continuing:
    // that we need:
    *env << "Beginning streaming...\n";
    play();
    
    if (!awaitConfigInfo(videoSink)) 
    {
        *env << "Failed to get MPEG-4 'config' information from input file: "
        << env->getResultMsg() << "\n";
        exit(1);
    }
    
    // Create (and start) a 'RTCP instance' for this RTP sink:
    const unsigned estimatedSessionBandwidthVideo = VIDEO_BIT_RATE/1000; // in kbps; for RTCP b/w share
    RTCPInstance* videoRTCP =
        RTCPInstance::createNew(*env, &rtcpGroupsockVideo,
                                estimatedSessionBandwidthVideo, CNAME,
                                videoSink, NULL);
    // Note: This starts RTCP running automatically
    
    // Add these to our 'Darwin injector':
    injector->addStream(videoSink, videoRTCP);
    ////////// END VIDEO //////////
#endif    
    
    // Next, specify the destination Darwin Streaming Server:
    if (!injector->setDestination(dssNameOrAddress, remoteStreamName,
                                  programName, "LIVE555 Streaming Media")) 
    {
        *env << "injector->setDestination() failed: "
        << env->getResultMsg() << "\n";
        exit(1);
    }
    
    *env << "Play this stream (from the Darwin Streaming Server) using the URL:\n"
        << "\trtsp://" << dssNameOrAddress << "/" << remoteStreamName << "\n";
    
    env->taskScheduler().doEventLoop(); // does not return
    
    return 0; // only to prevent compiler warning
}

void afterPlaying(void* clientData)
{
    *env << "...done reading from file\n";
    
    Medium::close(videoSource);
    Medium::close(audioSource);
    
    // Start playing once again:
    play();
}

void play() 
{
#ifdef VIDEO
    // Open the video input
    FFMPEGDummySource* videoFileSource = FFMPEGDummySource::createNew(*env, encoder, VIDEO_SOURCE);
    if (videoFileSource == NULL) 
    {
        *env << "Unable to open video input\n";
        exit(1);
    }
    
    FramedSource* videoES = videoFileSource;
    videoSource = MPEG4VideoStreamFramer::createNew(*env, videoES);
#endif
#ifdef AUDIO    
    // Open the audio input
    FFMPEGDummySource* audioFileSource = FFMPEGDummySource::createNew(*env, encoder, AUDIO_SOURCE);
    if (audioFileSource == NULL) 
    {
        *env << "Unable to open audio input\n";
        exit(1);
    }
    
    FramedSource* audioES = audioFileSource;
    audioSource = MPEG1or2AudioStreamFramer::createNew(*env, audioES);

#ifdef AUDIO_USING_ADUS
    audioSource = ADUFromMP3Source::createNew(*env, audioSource);
    if (audioSource == NULL) {
      *env << "Unable to create a MP3->ADU filter for the source\n";
      exit(1);
    }
#endif
#endif    
    // Finally, start playing:
#ifdef VIDEO
    *env << "Beginning to read from video input...\n";
    videoSink->startPlaying(*videoSource, afterPlaying, videoSink);
#endif
#ifdef AUDIO
    *env << "Beginning to read from audio input...\n";
    audioSink->startPlaying(*audioSource, afterPlaying, audioSink);
#endif
}

static char doneFlag = 0;

static void checkForAuxSDPLine(void* clientData) 
{
    RTPSink* sink = (RTPSink*)clientData;
    if (sink->auxSDPLine() != NULL) 
    {
        // Signal the event loop that we're done:
        doneFlag = ~0;
    } else 
    {
        // No luck yet.  Try again, after a brief delay:
        int uSecsToDelay = 100000; // 100 ms
        env->taskScheduler().scheduleDelayedTask(uSecsToDelay,
                                                 (TaskFunc*)checkForAuxSDPLine, sink);
    }
}

Boolean awaitConfigInfo(RTPSink* sink) 
{
    // Check whether the sink's 'auxSDPLine()' is ready:
    checkForAuxSDPLine(sink);
    
    env->taskScheduler().doEventLoop(&doneFlag);
    
    char const* auxSDPLine = sink->auxSDPLine();
    return auxSDPLine != NULL;
}
