/*
#  $Id$
# -----------------------------------------------------------------------------
#  The part of 'VideoNEXT MediaClient SDK'
# -----------------------------------------------------------------------------
#  Author: Petrov Maxim, 03/01/2010
#  Edited by:
#  QA by:
#  Copyright: videoNEXT LLC
# -----------------------------------------------------------------------------
*/
#include "AudioVideoPlaybackController.h"
#include "MediaStreamProducerImpl.h"
#include "FileRecorder.h"

#include <sys/time.h>
#include <stdlib.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 {


AudioVideoPlaybackController
::AudioVideoPlaybackController(AdaptiveUsageEnvironment& env, 
                               MediaStreamProducerImpl *p,
                               MediaStreamHandler *h,
                               unsigned cacheSize, unsigned bufferLen)
    : Medium(env),
      fEnv(env),
      fMediaProducer(p),
      fMediaStreamHandler(h),
      fFileRecorder(NULL),
      fPaused(false), fServerPaused(false), fPlayFramesFromServer(true), 
      fMemCounter(new MemCounter),
      fCache(new Cache(env, cacheSize, fMemCounter)),
      fStreamSpeed(1.0f), fDirection(1),
      fState(IDLE), fErrorCode(0), fWaitForKeyFrame(false), fBufferLen(bufferLen), fPlayoutBuffer(0)
{
   fdummySocketNum = socket(AF_INET, SOCK_DGRAM, 0);

   fSinks[AUDIO] = 0;
   fSinks[VIDEO] = 0;

   if (fBufferLen > 0)
       fPlayoutBuffer = PlayoutBuffer::open(fEnv, this, fBufferLen);
}
////////////////////////////////////////////////////////////////////////////////
                              
AudioVideoPlaybackController
::~AudioVideoPlaybackController() 
{
   MutexGuard mg(fPlayoutBufferMutex);

   //endRecording(); // Destroys file recorder if created
   {
       MutexGuard mg(fRecordingMutex);
       if (fFileRecorder) {
           delete fFileRecorder; // Recording ended in destructor.
           fFileRecorder = NULL;
       }
   }

   if (fPlayoutBuffer)
   {
       PlayoutBuffer::close(fPlayoutBuffer);
       fPlayoutBuffer = 0;
   }

    delete fCache;
    delete fMemCounter;

    ::closeSocket(fdummySocketNum);
}

void AudioVideoPlaybackController::addSink(FrameConsumer *sink, SINK_TYPE type) 
{
    MutexGuard mg(fSinkMutex);
    fSinks[type] = sink;
}

void AudioVideoPlaybackController::removeSink(FrameConsumer *sink) 
{
    MutexGuard mg(fSinkMutex);

   if (fSinks[AUDIO] == sink)
      fSinks[AUDIO] = 0;
   else if (fSinks[VIDEO] == sink)
      fSinks[VIDEO] = 0;
}

void AudioVideoPlaybackController::addFrame(CPMediaFrame &cpMediaFrame)
{
   if (fWaitForKeyFrame && cpMediaFrame->mediaType == MediaFrame::VIDEO 
       && cpMediaFrame->type != 0)
   {
      envir() << "Still waiting for key frame\n";
      return;
   }
 
   if (cpMediaFrame->mediaType == MediaFrame::VIDEO) 
      fWaitForKeyFrame = false;   

   MutexGuard mg(fPlayoutBufferMutex);
   if (fPlayoutBuffer)
   {
       fPlayoutBuffer->scheduleFrame(cpMediaFrame);
   }
   else
   {
       playFrame(cpMediaFrame);
   }
}

void AudioVideoPlaybackController::playFrame(CPMediaFrame &cpMediaFrame, bool show)
{
    // storing frame firstly!
    if (show && fPlayFramesFromServer)
        storeFrame(cpMediaFrame);
    
    //    if (fPlayFramesFromServer)
    {
        FrameConsumer *sink = 0;
        {
            MutexGuard mg(fSinkMutex);
            sink = fSinks[cpMediaFrame->mediaType];
        }
        if (!sink)
            return;

        // Do not play audio when stream speed changed 
        // or player in the step mode.
        if (cpMediaFrame->mediaType == MediaFrame::AUDIO 
            && fState == PLAY_FROM_SERVER
            && (fStreamSpeed != 1.0f || isStepMode()))
        {
            return;
        }

        if ( (fState != PLAY_FROM_SERVER && !fPaused) || isStepMode())
        {
            setState(PLAY_FROM_SERVER);
        }

        sink->playFrame(cpMediaFrame, show);
    }
}

void AudioVideoPlaybackController::setState(STREAM_STATE state, int errorCode)
{
   if (state == PLAY_FROM_SERVER && fState == PLAY_FROM_SERVER) return;
   if ((state == STOPPED && fState == STOPPED))
//           || (state == IDLE && fState == IDLE))
       return;

   if (state == IDLE && (errorCode == EENDOFSTREAM || errorCode == EENDOFARCHIVE || errorCode == EENDOFCHUNK))
       endRecording();
   else if (state == STOPPED)
       endRecording(true);

   if (state == STOPPED && fState != STOPPED)
       fMediaProducer->stop();

   fState = state;
   fErrorCode = errorCode;

   fMediaStreamHandler->stateChanged(fState, ResultStatus(errorCode?errorCode:envir().getErrno(), envir().getResultMsg()));   
}

void AudioVideoPlaybackController::setState2(STREAM_STATE state, int errorCode)
{
   fState = state;
   fErrorCode = errorCode;

   fMediaStreamHandler->stateChanged(fState, ResultStatus(errorCode?errorCode:envir().getErrno(), envir().getResultMsg()));   
}

bool AudioVideoPlaybackController::playFromCache(int direction) 
{ 
    fDirection = direction;
    if (direction < 0) return playBackward();
    else return playForward();
}

/*virtual*/ bool AudioVideoPlaybackController::playForward() 
{      
    bool firstRead = true;
    videonext::media::Timeval lastFrameTime(0,0);
    videonext::media::Timeval codeExecTime(0,0);    
    videonext::media::DelayInterval delayInterval(0,0);

    while (!fPaused)
    {
        Cache::CACHE_STATUS cacheStatus;

        CPMediaFrame cpMediaFrame = fCache->advance(Cache::FORWARD, &cacheStatus);

        if (cacheStatus == Cache::OUT_OF_BUFFER) 
        {
            break;
        }
        else if (cacheStatus == Cache::JUMP2TS)
        {
            fMediaProducer->jumpToTimestamp(cpMediaFrame->tv.tv_sec, false /* do not mark current frame*/);
            break;
        }
       
        if (fWaitForKeyFrame) // make sure we always begin play from I-frame
        {
            if (cpMediaFrame->type == 0)
            {
                fWaitForKeyFrame = false;
            }
            else 
            {
                continue;
            }  
        }

        if (firstRead)
        {
           setState(PLAY_FROM_BUFFER);
           firstRead = false;
        }

        if (cpMediaFrame->mediaType == MediaFrame::AUDIO 
            && (fStreamSpeed != 1.0f || isStepMode())) continue;

        // play frame and measure exec time
        codeExecTime = videonext::media::Timeval();      
        FrameConsumer *sink = fSinks[cpMediaFrame->mediaType];
        if (!sink) break;
        sink->playFrame(cpMediaFrame, true);
        codeExecTime = videonext::media::Timeval() - codeExecTime;

        if (cpMediaFrame->mediaType == MediaFrame::AUDIO && fSinks[VIDEO])
           continue; // do not do delay when playing muxed audio stream

        if (lastFrameTime != videonext::media::ZERO)
        {
            delayInterval = 
                (videonext::media::Timeval(cpMediaFrame->tv) - lastFrameTime) - codeExecTime;
        }
    
        lastFrameTime = videonext::media::Timeval(cpMediaFrame->tv);

        // need to delay?
        if (delayInterval != videonext::media::ZERO 
            && delayInterval.seconds() <= 2)
        {    
            delay(delayInterval);
        }
    }
    
       
    return !fPaused;
}

/*virtual*/ bool AudioVideoPlaybackController::playBackward()
{  
    bool firstRead = true;
    
    videonext::media::Timeval lastFrameTime(0,0);
    videonext::media::Timeval codeExecTime(0,0);    
    videonext::media::DelayInterval delayInterval(0,0);
  
    while (!fPaused)
    {
        Cache::CACHE_STATUS cacheStatus;
        CPMediaFrame cpMediaFrame = fCache->advance(Cache::BACKWARD, &cacheStatus);
    
        if (cacheStatus == Cache::OUT_OF_BUFFER) 
        {
            break;
        }
        
        if (firstRead) 
        {    
            setState(PLAY_FROM_BUFFER);
            firstRead = false;      
        }
        
        // When playing backward we can dispay only I-frames
        // do not play audio frames :)
        if (cpMediaFrame->type != 0 || cpMediaFrame->mediaType == MediaFrame::AUDIO)
        {
           /*  if (cpMediaFrame->mediaType == MediaFrame::AUDIO)
               lastFrameTime = videonext::media::Timeval(cpMediaFrame->tv); 
           */
           continue;
        }
        
        // play frame and measure exec time
        codeExecTime = videonext::media::Timeval();        
        FrameConsumer *sink = fSinks[cpMediaFrame->mediaType];
        if (!sink) break;
        sink->playFrame(cpMediaFrame, true);
        codeExecTime = videonext::media::Timeval() - codeExecTime;
        
        if (lastFrameTime != videonext::media::ZERO)
        {
           delayInterval = 
              (lastFrameTime -videonext::media::Timeval(cpMediaFrame->tv)) - codeExecTime;
        }
        
        // need to delay?
        if (delayInterval != videonext::media::ZERO && delayInterval.seconds() <= 2)
        {    
           delay(delayInterval);
        }
        
        lastFrameTime = videonext::media::Timeval(cpMediaFrame->tv);   
    }
        
    return !fPaused;  
}

bool AudioVideoPlaybackController::isStepMode()
{
   return fMediaProducer->isStepMode(); 
}

void AudioVideoPlaybackController::clear()
{
    for (int i = 0; i < 2; i++)
    {
       if (fSinks[i]) fSinks[i]->clear();
    }   
}

void AudioVideoPlaybackController::setPaused(bool val)
{
   if(fPaused == val)
       return;

   fPaused = val;

   if (fPaused)
       setState(IDLE);

   MutexGuard mg(fPlayoutBufferMutex);
   if (fPlayoutBuffer)
       fPlayoutBuffer->setPaused(val);
}

void AudioVideoPlaybackController::storeFrame(CPMediaFrame &cpMediaFrame)
{
    fCache->add(cpMediaFrame, fPlayFramesFromServer && !isStepMode());
   
    // Notify GUI that we have changed buffer boundaries
    CacheBoundaries bounds;

    bounds.begin.tv_sec = bounds.begin.tv_usec = 0; 
    bounds.end.tv_sec   = bounds.end.tv_usec    = 0;

    if (fPaused && fCache->boundaries(&bounds.begin, &bounds.end))
    {    
       fMediaStreamHandler->bufferChanged(bounds);
    }
}

void AudioVideoPlaybackController::delay(const videonext::media::DelayInterval &delayInterval)
{
    struct timeval tv = {delayInterval.seconds(), (long int)(delayInterval.useconds()/fStreamSpeed)};
    
    FD_ZERO(&fDummySet);
    FD_SET((unsigned)fdummySocketNum, &fDummySet);
    
    if (select(fdummySocketNum+1, &fDummySet, NULL, NULL, &tv) < 0)
    {
        envir().setResultErrMsg("select error: ");
        envir() << envir().getResultMsg() << "\n";
    }
}

void AudioVideoPlaybackController::markJump()
{
    fCache->markJump();
}

bool AudioVideoPlaybackController::jumpToTimestamp(time_t ts, bool playingArchive)
{ 
   fWaitForKeyFrame = true;

   MutexGuard mg(fPlayoutBufferMutex);
      
   if (fPlayoutBuffer)
       fPlayoutBuffer->flush();
   
   return fCache->jumpToTimestamp(ts, playingArchive);
}

void AudioVideoPlaybackController::flushJitterBuffer()
{
    MutexGuard mg(fPlayoutBufferMutex);

    if (fPlayoutBuffer)
    {
        fPlayoutBuffer->flush();
    }
}

void AudioVideoPlaybackController::setJitterBufferLen(unsigned value)
{
    MutexGuard mg(fPlayoutBufferMutex);

    if (fPlayoutBuffer)
    {
        fPlayoutBuffer->soft_flush();
        PlayoutBuffer::close(fPlayoutBuffer);
        fPlayoutBuffer = 0;
    }

    if (value > 0)
    {
        fBufferLen = value;
        fPlayoutBuffer = PlayoutBuffer::open(fEnv, this, value);
    }
}

void AudioVideoPlaybackController::enableJitterBuffer(bool value)
{
    MutexGuard mg(fPlayoutBufferMutex);

    if (value == false)
    {
        if (fPlayoutBuffer)
        {
            fPlayoutBuffer->soft_flush();
            PlayoutBuffer::close(fPlayoutBuffer);
            fPlayoutBuffer = 0;
        }
    }
    else
    {
        if (fBufferLen == 0)
            return;

        if (!fPlayoutBuffer)
            fPlayoutBuffer = PlayoutBuffer::open(fEnv, this, fBufferLen);
    }
}

FileRecorder *AudioVideoPlaybackController::fileRecorder()
{
    MutexGuard mg(fRecordingMutex);

    if (fFileRecorder && fFileRecorder->status() == RECORDING_ENDED) {
        delete fFileRecorder;
        fFileRecorder = NULL;
    }

    return fFileRecorder;
}

bool AudioVideoPlaybackController::startRecording(const char* file_name, const std::map<std::string, std::string>& metadata)
{
    MutexGuard mg(fRecordingMutex);

    if(!(fState == PLAY_FROM_SERVER || fState == PLAY_FROM_BUFFER || fState == BUFFERING))
        return false;

    if (file_name && (strlen(file_name)) && !fFileRecorder)
    {
        fFileRecorder = new FileRecorder(this);
        if (!fFileRecorder->start(file_name, metadata))
        {
            delete fFileRecorder;
            fFileRecorder = NULL;
        } else
            return true;
    }

    return false;
}

bool AudioVideoPlaybackController::endRecording(bool stop)
{
    MutexGuard mg(fRecordingMutex);

    if (!fFileRecorder)
        return false;

    fFileRecorder->end(stop);
    return true;
}

}}
