/*
#  $Id$
# -----------------------------------------------------------------------------
#  The part of 'VideoNEXT MediaClient SDK'
# -----------------------------------------------------------------------------
#  Author: Petrov Maxim, 03/04/2009
#  Edited by:
#  QA by:
#  Copyright: videoNEXT LLC
# -----------------------------------------------------------------------------
*/

#include <sys/time.h>
#include <stdlib.h>
#include <assert.h>

extern "C" {
#include "libavcodec/avcodec.h"
}

#include "AudioVideoPlaybackController.h"
#include "FrameConsumer.h"

#if (defined(__WIN32__) || defined(_WIN32)) && !defined(IMN_PIM) && !defined(__MINGW32__)
// For Windoze, we need to implement our own gettimeofday()
extern int gettimeofday(struct timeval*, int*);
#endif

namespace videonext { namespace media {

FrameConsumer::FrameConsumer(AdaptiveUsageEnvironment& env, 
                             AudioVideoPlaybackController *c,
                             unsigned bufferSize,
                             unsigned decoderType,
                             StreamInfo *streamInfo)
    : MediaSink(env),
      fEnv(env),
      fPlaybackController(c),
      fBuffer(NULL), fBufferSize(bufferSize),
      fLastFrameNum(0),
      fStreamInfo(streamInfo),
      fDecoderType(decoderType),
      fRTPTimeDiff(0,0), fRTPTimeDiffPlus(true),
      fRtpSource(0)
{
    fBuffer = new unsigned char[fBufferSize];    
}
                              
FrameConsumer::~FrameConsumer() 
{    
    delete[] fBuffer;

    if (fStreamInfo->configData) delete[] fStreamInfo->configData;

    delete fStreamInfo;
}

/*virtual*/void FrameConsumer::addData(unsigned char* data, unsigned dataSize,
                                       const struct timeval &presentationTime,
                                       bool parseOurHeader, const std::string* pObjectsData)
{
    assert(data != 0);
    
    if (data == 0 || dataSize == 0)
        return;
    
    MediaFrame *pMediaFrame;
    try
    {
       pMediaFrame = new MediaFrame(NULL, 0, fPlaybackController->memCounter());
    }
    catch (std::bad_alloc &)
    {   
        envir().setResultErrMsg("Request memory for pMediaFrame fail: ");
        envir() <<  envir().getResultMsg();
        return;
    }
 
    /* Don't forget that ffmpeg requires a little more bytes
     * that the real frame size */
    pMediaFrame->size = dataSize;

    if ( (pMediaFrame->data = (unsigned char*)malloc(dataSize+AV_INPUT_BUFFER_PADDING_SIZE*4)) == NULL)    
    {
        envir().setResultErrMsg("Request memory for pMediaFrame->data fail: ");
        envir() <<  envir().getResultMsg();
        
        delete pMediaFrame;    
        return;
    }
    
    memset( pMediaFrame->data + pMediaFrame->size, 0,
            AV_INPUT_BUFFER_PADDING_SIZE*4 );
   
    pMediaFrame->tv = presentationTime;
    memcpy(pMediaFrame->data, data, dataSize);
    
    CPMediaFrame cpMediaFrame(pMediaFrame);
    
    addData(cpMediaFrame);
}

void FrameConsumer::addData(CPMediaFrame &cpMediaFrame) 
{
    if (cpMediaFrame->data == 0 || cpMediaFrame->size == 0)
        return;

   // Setting frame type:
   setFrameType(cpMediaFrame);

   cpMediaFrame->num = fLastFrameNum++;
  
   fPlaybackController->addFrame(cpMediaFrame);
}


/*static*/ void FrameConsumer::afterGettingFrame(void* clientData, unsigned frameSize,
    unsigned /*numTruncatedBytes*/,
    struct timeval presentationTime,
    unsigned /*durationInMicroseconds*/) 
{
    FrameConsumer* sink = (FrameConsumer*)clientData;
    sink->afterGettingFrame1(frameSize, presentationTime);
    
} 

void FrameConsumer::afterGettingFrame1(unsigned frameSize,
                                       struct timeval presentationTime) 
{
    // Calculate pts
    if (fSource && ((MediaSource*)fSource)->isRTPSource())
    {
        RTPSource *s = (RTPSource*)fSource;

        if (s->hasBeenSynchronizedUsingRTCP())
        {
            videonext::media::Timeval now;
            // calculate diff
            if (fRTPTimeDiff == ZERO)
            {
                if (now > videonext::media::Timeval(presentationTime.tv_sec, presentationTime.tv_usec))
                    fRTPTimeDiff = now - videonext::media::Timeval(presentationTime.tv_sec, presentationTime.tv_usec);
                else
                {
                    fRTPTimeDiff = videonext::media::Timeval(presentationTime.tv_sec, presentationTime.tv_usec) - now;
                    fRTPTimeDiffPlus = false;
                }

                fprintf(stderr, "RTP time diff: %s%ld.%ld\n", (fRTPTimeDiffPlus?"+":"-"), fRTPTimeDiff.seconds(), fRTPTimeDiff.useconds());
            }

            // fix presentation time
            videonext::media::Timeval pts(0,0);
            if (fRTPTimeDiffPlus)
                pts = videonext::media::Timeval(presentationTime.tv_sec, presentationTime.tv_usec) + fRTPTimeDiff;
            else
                pts = videonext::media::Timeval(presentationTime.tv_sec, presentationTime.tv_usec) - fRTPTimeDiff;
            
            presentationTime.tv_sec = pts.seconds();
            presentationTime.tv_usec = pts.useconds();
        }
        else
        {
            // keep presentation time as is. Ie trust to server
            // gettimeofday(&presentationTime, NULL);
        }
/*          
           {
               videonext::media::Timeval now;
               fprintf(stderr, "pts: %d.%d, sync:%d (now:%d.%d)\n",
                       tv.tv_sec, tv.tv_usec, s->hasBeenSynchronizedUsingRTCP(), now.seconds(), now.useconds());
           }
*/ 
    }
    else
    {
        gettimeofday(&presentationTime, NULL);
    }

    addData(fBuffer, frameSize, presentationTime);
    
    // Then try getting the next frame:
    continuePlaying();
}

/*virtual*/Boolean FrameConsumer::startPlaying(MediaSource& source,
                                               afterPlayingFunc* afterFunc,
                                               void* afterClientData)
{
   fRtpSource = (RTPSource*)&source;
   return MediaSink::startPlaying(source, afterFunc, afterClientData);
}

/*virtual*/ Boolean FrameConsumer::continuePlaying() 
{
    if (fSource == NULL) return False;
    
    fSource->getNextFrame(fBuffer, fBufferSize,
                          afterGettingFrame, this,
                          onSourceClosure, this);
    
    return True;
}


}}

