/************************************************************************************
This source file is part of the Theora Video Playback Library
For latest info, see http://libtheoraplayer.sourceforge.net/
*************************************************************************************
Copyright (c) 2008-2010 Kresimir Spes (kreso@cateia.com)
This program is free software; you can redistribute it and/or modify it under
the terms of the BSD license: http://www.opensource.org/licenses/bsd-license.php
*************************************************************************************/

#include <memory.h>
#include <ogg/ogg.h>
#include <vorbis/vorbisfile.h>
#include <theora/theoradec.h>
#include "TheoraVideoClip.h"
#include "TheoraVideoManager.h"
#include "TheoraVideoFrame.h"
#include "TheoraFrameQueue.h"
#include "TheoraAudioInterface.h"
#include "TheoraTimer.h"
#include "TheoraDataSource.h"
#include "TheoraUtil.h"
#include "TheoraException.h"

class TheoraInfoStruct
{
public:
        // ogg/vorbis/theora variables
        ogg_sync_state   OggSyncState;
        ogg_page         OggPage;
        ogg_stream_state VorbisStreamState;
        ogg_stream_state TheoraStreamState;
        //Theora State
        th_info        TheoraInfo;
        th_comment     TheoraComment;
        th_setup_info* TheoraSetup;
        th_dec_ctx*    TheoraDecoder;
        //Vorbis State
        vorbis_info      VorbisInfo;
        vorbis_dsp_state VorbisDSPState;
        vorbis_block     VorbisBlock;
        vorbis_comment   VorbisComment;

        TheoraInfoStruct()
        {
                TheoraDecoder=0;
                TheoraSetup=0;
        }
};

//! clears a portion of memory with an unsign
void memset_uint(void* buffer,unsigned int colour,unsigned int size_in_bytes)
{
        unsigned int* data=(unsigned int*) buffer;
        for (unsigned int i=0;i<size_in_bytes;i+=4)
        {
                *data=colour;
                data++;
        }
}

TheoraVideoClip::TheoraVideoClip(TheoraDataSource* data_source,
                                                                 TheoraOutputMode output_mode,
                                                                 int nPrecachedFrames,
                                                                 bool usePower2Stride):
        mAudioInterface(NULL),
        mNumDroppedFrames(0),
        mNumDisplayedFrames(0),
        mTheoraStreams(0),
        mVorbisStreams(0),
        mAudioSkipSeekFlag(0),
        mSeekPos(-1),
        mDuration(-1),
    mName(data_source->repr()),
    mStride(usePower2Stride),
    mAudioGain(1),
    mOutputMode(output_mode),
    mRequestedOutputMode(output_mode),
    mAutoRestart(0),
    mEndOfFile(0),
    mRestarted(0),
        mIteration(0),
        mLastIteration(0)
{
        mAudioMutex=new TheoraMutex;

        mTimer=mDefaultTimer=new TheoraTimer();

        mFrameQueue=NULL;
        mAssignedWorkerThread=NULL;
        mNumPrecachedFrames=nPrecachedFrames;

        mInfo=new TheoraInfoStruct;

        load(data_source);
}

TheoraVideoClip::~TheoraVideoClip()
{
        // wait untill a worker thread is done decoding the frame
        while (mAssignedWorkerThread)
        {
                _psleep(1);
        }

        delete mDefaultTimer;

        if (!mStream) delete mStream;


        if (mFrameQueue) delete mFrameQueue;

        if (mInfo->TheoraDecoder)
                th_decode_free(mInfo->TheoraDecoder);

        if (mInfo->TheoraSetup)
                th_setup_free(mInfo->TheoraSetup);
        delete mInfo;

        if (mAudioInterface)
        {
                mAudioMutex->lock(); // ensure a thread isn't using this mutex

        // probably not necesarry because all it does is memset to 0
        //      ogg_stream_clear(&mInfo->VorbisStreamState);
        //      vorbis_block_clear(&mInfo->VorbisBlock);
        //      vorbis_dsp_clear(&mInfo->VorbisDSPState);
        //      vorbis_comment_clear(&mInfo->VorbisComment);
        //      vorbis_info_clear(&mInfo->VorbisInfo);
                mAudioInterface->destroy(); // notify audio interface it's time to call it a day
        }
        delete mAudioMutex;

        //ogg_sync_clear(&mInfo->OggSyncState);
}

TheoraTimer* TheoraVideoClip::getTimer()
{
        return mTimer;
}

void TheoraVideoClip::setTimer(TheoraTimer* timer)
{
        if (!timer) mTimer=mDefaultTimer;
        else mTimer=timer;
}

bool TheoraVideoClip::_readData()
{
        int audio_eos=0;
        float audio_time=0;
        float time=mTimer->getTime();
        if (mRestarted) time=0;

        for (;;)
        {
                char *buffer = ogg_sync_buffer( &mInfo->OggSyncState, 4096);
                int bytesRead = mStream->read( buffer, 4096 );
                ogg_sync_wrote(&mInfo->OggSyncState, bytesRead);

                if (bytesRead < 4096)
                {
                        if (bytesRead == 0)
                        {
                                if (mAutoRestart) _restart();
                                else mEndOfFile=true;
                                return 0;
                        }
                }
                while ( ogg_sync_pageout( &mInfo->OggSyncState, &mInfo->OggPage ) > 0 )
                {
                        ogg_stream_pagein(&mInfo->TheoraStreamState,&mInfo->OggPage);
                        if (mAudioInterface &&
                                ogg_page_serialno(&mInfo->OggPage) == mInfo->VorbisStreamState.serialno)
                        {
                                ogg_int64_t g=ogg_page_granulepos(&mInfo->OggPage);
                                audio_time=(float) vorbis_granule_time(&mInfo->VorbisDSPState,g);
                                audio_eos=ogg_page_eos(&mInfo->OggPage);

                                if (mSeekPos == -2 && !mAudioSkipSeekFlag)
                                {

                                        if (g > -1) { mAudioSkipSeekFlag=1; continue; }
                                        if (g == -1) continue;
                                }
                                mAudioMutex->lock();
                                ogg_stream_pagein(&mInfo->VorbisStreamState,&mInfo->OggPage);
                                mAudioMutex->unlock();
                        }
                }
                if (!(mAudioInterface && !audio_eos && audio_time < time+1.0f))
                        break;
        }
        return 1;
}

void TheoraVideoClip::decodeNextFrame()
{
        if (mEndOfFile) return;

        TheoraVideoFrame* frame=mFrameQueue->requestEmptyFrame();
        if (!frame) return; // max number of precached frames reached
        long nSeekSkippedFrames=0;
        ogg_packet opTheora;
        ogg_int64_t granulePos;
        th_ycbcr_buffer buff;

        //writelog("Decoding video "+mName);

        for(;;)
        {
                int ret=ogg_stream_packetout(&mInfo->TheoraStreamState,&opTheora);

                if (ret > 0)
                {

                        if (mSeekPos == -2) // searching for next keyframe
                        {
                                int keyframe=th_packet_iskeyframe(&opTheora);
                                if (!keyframe) { nSeekSkippedFrames++; continue; }
                                mSeekPos=-1;
                                if (nSeekSkippedFrames > 0)
                                        th_writelog(mName+"[seek]: skipped "+str(nSeekSkippedFrames)+" frames while searching for keyframe");
                        }
                        if (th_decode_packetin(mInfo->TheoraDecoder, &opTheora,&granulePos ) != 0) continue; // 0 means success
                        float time=(float) th_granule_time(mInfo->TheoraDecoder,granulePos);
                        unsigned long frame_number=(unsigned long) th_granule_frame(mInfo->TheoraDecoder,granulePos);
                        if (time > mDuration)
                                mDuration=time; // duration corrections


                        if (time < mTimer->getTime() && !mRestarted)
                        {
#ifdef _DEBUG
                                th_writelog(mName+": pre-dropped frame "+str(frame_number));
#endif
                                mNumDisplayedFrames++;
                                mNumDroppedFrames++;
                                continue; // drop frame
                        }
                        frame->mTimeToDisplay=time;
                        frame->mIteration=mIteration;
                        frame->_setFrameNumber(frame_number);
                        th_decode_ycbcr_out(mInfo->TheoraDecoder,buff);
                        frame->decode(buff);
                        //_psleep(rand()%20); // temp
                        break;
                }
                else
                {
                        if (!_readData())
                        {
                                frame->mInUse=0;
                                return;
                        }
                }
        }
}

void TheoraVideoClip::_restart()
{
        long granule=0;
        th_decode_ctl(mInfo->TheoraDecoder,TH_DECCTL_SET_GRANPOS,&granule,sizeof(granule));
        th_decode_free(mInfo->TheoraDecoder);
        mInfo->TheoraDecoder=th_decode_alloc(&mInfo->TheoraInfo,mInfo->TheoraSetup);
        ogg_stream_reset(&mInfo->TheoraStreamState);
        if (mAudioInterface)
        {
                // empty the DSP buffer
                //float **pcm;
                //int len = vorbis_synthesis_pcmout(&mInfo->VorbisDSPState,&pcm);
                //if (len) vorbis_synthesis_read(&mInfo->VorbisDSPState,len);
                ogg_packet opVorbis;
                while (ogg_stream_packetout(&mInfo->VorbisStreamState,&opVorbis) > 0)
                {
                        if (vorbis_synthesis(&mInfo->VorbisBlock,&opVorbis) == 0)
                                vorbis_synthesis_blockin(&mInfo->VorbisDSPState,&mInfo->VorbisBlock);
                }
                ogg_stream_reset(&mInfo->VorbisStreamState);
        }

        ogg_sync_reset(&mInfo->OggSyncState);
        mStream->seek(0);
        //mTimer->seek(0);
        mEndOfFile=false;

        mRestarted=1;
}

void TheoraVideoClip::restart()
{
        bool end=mEndOfFile;
        mEndOfFile=1; //temp, to prevent threads to decode while restarting
        while (mAssignedWorkerThread) _psleep(1); // wait for assigned thread to do it's work
        _restart();
        mTimer->seek(0);
        mFrameQueue->clear();
        mEndOfFile=0;
        mIteration=0;
        mRestarted=0;
        mSeekPos=-1;
}

void TheoraVideoClip::update(float time_increase)
{
        if (mTimer->isPaused() && mSeekPos != -3) return;
        mTimer->update(time_increase);
        float time=mTimer->getTime();
        if (time >= mDuration)
        {
                if (mAutoRestart && mRestarted)
                {
                        mIteration=!mIteration;
                        mTimer->seek(time-mDuration);
                        mRestarted=0;
                        int n=0;
                        for (;;)
                        {
                                TheoraVideoFrame* f=mFrameQueue->getFirstAvailableFrame();
                                if (!f) break;
                                if (f->mTimeToDisplay > 0.5f)
                                {
                                        if (n == 0)
                                        {
                                                f->mTimeToDisplay=time-mDuration;
                                                f->mIteration=!f->mIteration;
                                        }
                                        else
                                                popFrame();
                                        n++;
                                }
                                else break;
                        }
                        if (n > 0) th_writelog("dropped "+str(n)+" end frames");
                }
                else return;
        }
}

float TheoraVideoClip::updateToNextFrame()
{
        TheoraVideoFrame* f=mFrameQueue->getFirstAvailableFrame();
        if (!f) return 0;

        float time=f->mTimeToDisplay-mTimer->getTime();
        update(time);
        return time;
}

void TheoraVideoClip::popFrame()
{
        mNumDisplayedFrames++;
        mFrameQueue->pop(); // after transfering frame data to the texture, free the frame
                                                // so it can be used again
}

TheoraVideoFrame* TheoraVideoClip::getNextFrame()
{
        TheoraVideoFrame* frame;
        float time=mTimer->getTime();
        for (;;)
        {
                frame=mFrameQueue->getFirstAvailableFrame();
                if (!frame) return 0;
                if (frame->mTimeToDisplay > time) return 0;
                if (frame->mTimeToDisplay < time-0.1)
                {
                        if (mRestarted && frame->mTimeToDisplay < 2) return 0;
#ifdef _DEBUG
                        th_writelog(mName+": dropped frame "+str(frame->getFrameNumber()));
#endif
                        mNumDroppedFrames++;
                        mNumDisplayedFrames++;
                        mFrameQueue->pop();
                }
                else break;
        }

        mLastIteration=frame->mIteration;
        return frame;

}

void TheoraVideoClip::decodedAudioCheck()
{
        if (!mAudioInterface || mTimer->isPaused()) return;

        mAudioMutex->lock();

        ogg_packet opVorbis;
        float **pcm;
        int len=0;
        while (1)
        {
                len = vorbis_synthesis_pcmout(&mInfo->VorbisDSPState,&pcm);
                if (!len)
                {
                        if (mRestarted) break;
                        if (ogg_stream_packetout(&mInfo->VorbisStreamState,&opVorbis) > 0)
                        {
                                if (vorbis_synthesis(&mInfo->VorbisBlock,&opVorbis) == 0)
                                        vorbis_synthesis_blockin(&mInfo->VorbisDSPState,&mInfo->VorbisBlock);
                                continue;
                        }
                        else break;
                }
                if (mAudioGain < 1)
                {
                        // gain applied, let's attenuate the samples
                        for (int i=0;i<len;i++)
                        {
                                for (int j=0;j<mInfo->VorbisInfo.channels;j++)
                                {
                                        pcm[j][i]*=mAudioGain;
                                }
                        }
                }
                mAudioInterface->insertData(pcm,len);
                vorbis_synthesis_read(&mInfo->VorbisDSPState,len);
        }

        mAudioMutex->unlock();
}

void TheoraVideoClip::load(TheoraDataSource* source)
{
        mStream=source;
        readTheoraVorbisHeaders();


        mInfo->TheoraDecoder=th_decode_alloc(&mInfo->TheoraInfo,mInfo->TheoraSetup);

        mWidth=mInfo->TheoraInfo.frame_width;
        mHeight=mInfo->TheoraInfo.frame_height;
        mStride=(mStride == 1) ? mStride=_nextPow2(mWidth) : mWidth;

        mFrameQueue=new TheoraFrameQueue(mNumPrecachedFrames,this);


        // find out the duration of the file by seeking to the end
        // having ogg decode pages, extract the granule pos from
        // the last theora page and seek back to beginning of the file
        for (int i=1;i<=10;i++)
        {
                ogg_sync_reset(&mInfo->OggSyncState);
                mStream->seek(mStream->size()-4096*i);

                char *buffer = ogg_sync_buffer(&mInfo->OggSyncState, 4096*i);
                int bytesRead = mStream->read(buffer, 4096*i);
                ogg_sync_wrote(&mInfo->OggSyncState, bytesRead );
                ogg_sync_pageseek(&mInfo->OggSyncState,&mInfo->OggPage);

                while (1)
                {
                        int ret=ogg_sync_pageout( &mInfo->OggSyncState, &mInfo->OggPage );
                        if (ret == 0) break;
                        // if page is not a theora page, skip it
                        if (ogg_page_serialno(&mInfo->OggPage) != mInfo->TheoraStreamState.serialno) continue;

                        unsigned long granule=(unsigned long) ogg_page_granulepos(&mInfo->OggPage);
                        if (granule >= 0)
                        {
                                mDuration=(float) th_granule_time(mInfo->TheoraDecoder,granule);
                                mNumFrames=(unsigned long) th_granule_frame(mInfo->TheoraDecoder,granule)+1;
                        }
                }
                if (mDuration > 0) break;

        }
        if (mDuration < 0)
                th_writelog(mName+": unable to determine file duration!");
        else
                th_writelog(mName+": file duration is "+strf(mDuration)+" seconds");
        // restore to beginning of stream.
        ogg_sync_reset(&mInfo->OggSyncState);
        mStream->seek(0);
        mTheoraStreams=mVorbisStreams=0;
        readTheoraVorbisHeaders();

        if (mVorbisStreams) // if there is no audio interface factory defined, even though the video
                                // clip might have audio, it will be ignored
        {
                vorbis_synthesis_init(&mInfo->VorbisDSPState,&mInfo->VorbisInfo);
                vorbis_block_init(&mInfo->VorbisDSPState,&mInfo->VorbisBlock);
                // create an audio interface instance if available
                TheoraAudioInterfaceFactory* audio_factory=TheoraVideoManager::getSingleton().getAudioInterfaceFactory();
                if (audio_factory) setAudioInterface(audio_factory->createInstance(this,mInfo->VorbisInfo.channels,mInfo->VorbisInfo.rate));
        }
}

void TheoraVideoClip::readTheoraVorbisHeaders()
{
        ogg_packet tempOggPacket;
        bool done = false;
        bool decode_audio=TheoraVideoManager::getSingleton().getAudioInterfaceFactory() != NULL;
        //init Vorbis/Theora Layer
        //Ensure all structures get cleared out.
        memset(&mInfo->OggSyncState, 0, sizeof(ogg_sync_state));
        memset(&mInfo->OggPage, 0, sizeof(ogg_page));
        memset(&mInfo->VorbisStreamState, 0, sizeof(ogg_stream_state));
        memset(&mInfo->TheoraStreamState, 0, sizeof(ogg_stream_state));
        memset(&mInfo->TheoraInfo, 0, sizeof(th_info));
        memset(&mInfo->TheoraComment, 0, sizeof(th_comment));
        memset(&mInfo->VorbisInfo, 0, sizeof(vorbis_info));
        memset(&mInfo->VorbisDSPState, 0, sizeof(vorbis_dsp_state));
        memset(&mInfo->VorbisBlock, 0, sizeof(vorbis_block));
        memset(&mInfo->VorbisComment, 0, sizeof(vorbis_comment));

        ogg_sync_init(&mInfo->OggSyncState);
        th_comment_init(&mInfo->TheoraComment);
        th_info_init(&mInfo->TheoraInfo);
        vorbis_info_init(&mInfo->VorbisInfo);
        vorbis_comment_init(&mInfo->VorbisComment);

        while (!done)
        {
                char *buffer = ogg_sync_buffer( &mInfo->OggSyncState, 4096);
                int bytesRead = mStream->read( buffer, 4096 );
                ogg_sync_wrote( &mInfo->OggSyncState, bytesRead );

                if( bytesRead == 0 )
                        break;

                while( ogg_sync_pageout( &mInfo->OggSyncState, &mInfo->OggPage ) > 0 )
                {
                        ogg_stream_state OggStateTest;

                        //is this an initial header? If not, stop
                        if( !ogg_page_bos( &mInfo->OggPage ) )
                        {
                                //This is done blindly, because stream only accept them selfs
                                if (mTheoraStreams)
                                        ogg_stream_pagein( &mInfo->TheoraStreamState, &mInfo->OggPage );
                                if (mVorbisStreams)
                                        ogg_stream_pagein( &mInfo->VorbisStreamState, &mInfo->OggPage );

                                done=true;
                                break;
                        }

                        ogg_stream_init( &OggStateTest, ogg_page_serialno( &mInfo->OggPage ) );
                        ogg_stream_pagein( &OggStateTest, &mInfo->OggPage );
                        ogg_stream_packetout( &OggStateTest, &tempOggPacket );

                        //identify the codec
                        int ret;
                        if( !mTheoraStreams)
                        {
                                ret=th_decode_headerin( &mInfo->TheoraInfo, &mInfo->TheoraComment, &mInfo->TheoraSetup, &tempOggPacket);

                                if (ret > 0)
                                {
                                        //This is the Theora Header
                                        memcpy( &mInfo->TheoraStreamState, &OggStateTest, sizeof(OggStateTest));
                                        mTheoraStreams = 1;
                                        continue;
                                }
                        }
                        if (decode_audio && !mVorbisStreams &&
                                vorbis_synthesis_headerin(&mInfo->VorbisInfo, &mInfo->VorbisComment, &tempOggPacket) >=0 )
                        {
                                //This is vorbis header
                                memcpy( &mInfo->VorbisStreamState, &OggStateTest, sizeof(OggStateTest));
                                mVorbisStreams = 1;
                                continue;
                        }
                        //Hmm. I guess it's not a header we support, so erase it
                        ogg_stream_clear(&OggStateTest);
                }
        }

        while ((mTheoraStreams && (mTheoraStreams < 3)) ||
                   (mVorbisStreams && (mVorbisStreams < 3)) )
        {
                //Check 2nd'dary headers... Theora First
                int iSuccess;
                while( mTheoraStreams &&
                         ( mTheoraStreams < 3) &&
                         ( iSuccess = ogg_stream_packetout( &mInfo->TheoraStreamState, &tempOggPacket)) )
                {
                        if( iSuccess < 0 )
                                throw TheoraGenericException("Error parsing Theora stream headers.");

                        if( !th_decode_headerin(&mInfo->TheoraInfo, &mInfo->TheoraComment, &mInfo->TheoraSetup, &tempOggPacket) )
                                throw TheoraGenericException("invalid theora stream");

                        mTheoraStreams++;
                } //end while looking for more theora headers

                //look 2nd vorbis header packets
                while(// mVorbisStreams &&
                         ( mVorbisStreams < 3 ) &&
                         ( iSuccess=ogg_stream_packetout( &mInfo->VorbisStreamState, &tempOggPacket)))
                {
                        if(iSuccess < 0)
                                throw TheoraGenericException("Error parsing vorbis stream headers");

                        if(vorbis_synthesis_headerin( &mInfo->VorbisInfo, &mInfo->VorbisComment,&tempOggPacket))
                                throw TheoraGenericException("invalid stream");

                        mVorbisStreams++;
                } //end while looking for more vorbis headers

                //Not finished with Headers, get some more file data
                if( ogg_sync_pageout( &mInfo->OggSyncState, &mInfo->OggPage ) > 0 )
                {
                        if(mTheoraStreams)
                                ogg_stream_pagein( &mInfo->TheoraStreamState, &mInfo->OggPage );
                        if(mVorbisStreams)
                                ogg_stream_pagein( &mInfo->VorbisStreamState, &mInfo->OggPage );
                }
                else
                {
                        char *buffer = ogg_sync_buffer( &mInfo->OggSyncState, 4096);
                        int bytesRead = mStream->read( buffer, 4096 );
                        ogg_sync_wrote( &mInfo->OggSyncState, bytesRead );

                        if( bytesRead == 0 )
                                throw TheoraGenericException("End of file found prematurely");
                }
        } //end while looking for all headers
//      writelog("Vorbis Headers: " + str(mVorbisHeaders) + " Theora Headers : " + str(mTheoraHeaders));
}

std::string TheoraVideoClip::getName()
{
        return mName;
}

bool TheoraVideoClip::isBusy()
{
        return mAssignedWorkerThread || mOutputMode != mRequestedOutputMode;
}

TheoraOutputMode TheoraVideoClip::getOutputMode()
{
        return mOutputMode;
}

void TheoraVideoClip::setOutputMode(TheoraOutputMode mode)
{
        if (mOutputMode == mode) return;
        mRequestedOutputMode=mode;
        while (mAssignedWorkerThread) _psleep(1);
        // discard current frames and recreate them
        mFrameQueue->setSize(mFrameQueue->getSize());
        mOutputMode=mRequestedOutputMode;
}

float TheoraVideoClip::getTimePosition()
{
        return mTimer->getTime();
}
int TheoraVideoClip::getNumPrecachedFrames()
{
        return mFrameQueue->getSize();
}

void TheoraVideoClip::setNumPrecachedFrames(int n)
{
        if (mFrameQueue->getSize() != n)
                mFrameQueue->setSize(n);
}

int TheoraVideoClip::getNumReadyFrames()
{
        return mFrameQueue->getReadyCount();
}

float TheoraVideoClip::getDuration()
{
        return mDuration;
}

int TheoraVideoClip::getFPS()
{
        return mInfo->TheoraInfo.fps_numerator;
}

void TheoraVideoClip::play()
{
        mTimer->play();
}

void TheoraVideoClip::pause()
{
        mTimer->pause();
}

bool TheoraVideoClip::isPaused()
{
        return mTimer->isPaused();
}

bool TheoraVideoClip::isDone()
{
    return mEndOfFile && !mFrameQueue->getFirstAvailableFrame();
}

void TheoraVideoClip::stop()
{
        pause();
        seek(0);
}

void TheoraVideoClip::setPlaybackSpeed(float speed)
{
    mTimer->setSpeed(speed);
}

float TheoraVideoClip::getPlaybackSpeed()
{
    return mTimer->getSpeed();
}

long TheoraVideoClip::seekPage(long targetFrame,bool return_keyframe)
{
        int i,seek_min=0, seek_max=mStream->size();
        long frame;
        ogg_int64_t granule=0;
        bool fineseek=0;

        for (i=0;i<100;i++)
        {
                ogg_sync_reset( &mInfo->OggSyncState );
                mStream->seek((seek_min+seek_max)/2);
                memset(&mInfo->OggPage, 0, sizeof(ogg_page));
                ogg_sync_pageseek(&mInfo->OggSyncState,&mInfo->OggPage);

                for (;;)
                {
                        int ret=ogg_sync_pageout( &mInfo->OggSyncState, &mInfo->OggPage );
                        if (ret == 1)
                        {
                                int serno=ogg_page_serialno(&mInfo->OggPage);
                                if (serno == mInfo->TheoraStreamState.serialno)
                                {
                                        granule=ogg_page_granulepos(&mInfo->OggPage);
                                        if (granule >= 0)
                                        {
                                                frame=(long) th_granule_frame(mInfo->TheoraDecoder,granule);
                                                if (frame < targetFrame-1 && targetFrame-frame < 10)
                                                {
                                                        fineseek=1;
                                                        if (!return_keyframe) break;
                                                }
                                                if (fineseek && frame >= targetFrame)
                                                        break;

                                                if (fineseek) continue;
                                                if (targetFrame-1 > frame) seek_min=(seek_min+seek_max)/2;
                                                else                                   seek_max=(seek_min+seek_max)/2;
                                                break;
                                        }
                                }
                        }
                        else
                        {
                                char *buffer = ogg_sync_buffer( &mInfo->OggSyncState, 4096);
                                int bytesRead = mStream->read( buffer, 4096);
                                if (bytesRead == 0) break;
                                ogg_sync_wrote( &mInfo->OggSyncState, bytesRead );
                        }
                }
                if (fineseek) break;
        }
        if (return_keyframe) return (long) (granule >> mInfo->TheoraInfo.keyframe_granule_shift);

        ogg_stream_pagein(&mInfo->TheoraStreamState,&mInfo->OggPage);
        //granule=frame << mInfo->TheoraInfo.keyframe_granule_shift;
        th_decode_ctl(mInfo->TheoraDecoder,TH_DECCTL_SET_GRANPOS,&granule,sizeof(granule));

        return -1;
}

void TheoraVideoClip::doSeek()
{
        int frame,targetFrame=(int) (mNumFrames*mSeekPos/mDuration);

        if (targetFrame == 0)
        {
                _restart();
                mTimer->seek(0);
                mFrameQueue->clear();
                return;
        }

        mEndOfFile=0;
        mRestarted=0;

        mFrameQueue->clear();
        ogg_stream_reset(&mInfo->TheoraStreamState);
        th_decode_free(mInfo->TheoraDecoder);
        mInfo->TheoraDecoder=th_decode_alloc(&mInfo->TheoraInfo,mInfo->TheoraSetup);

        if (mAudioInterface)
        {
                mAudioMutex->lock();
                ogg_stream_reset(&mInfo->VorbisStreamState);
                vorbis_synthesis_restart(&mInfo->VorbisDSPState);
        }

        // first seek to desired frame, then figure out the location of the
        // previous keyframe and seek to it.
        // then by setting the correct time, the decoder will skip N frames untill
        // we get the frame we want.
        frame=seekPage(targetFrame,1);
        if (frame != -1) seekPage(std::max(0,frame),0);

        float time=((float) targetFrame/mNumFrames)*mDuration;

        if (mAudioInterface)
        {
                // let's decode some pages and seek to the appropriate PCM sample
                ogg_int64_t granule=0;
                float last_page_time=time;
                for (;;)
                {
                        int ret=ogg_sync_pageout( &mInfo->OggSyncState, &mInfo->OggPage );
                        if (ret == 1)
                        {
                                int serno=ogg_page_serialno(&mInfo->OggPage);
                                if (serno == mInfo->VorbisStreamState.serialno)
                                {
                                        granule=ogg_page_granulepos(&mInfo->OggPage);
                                        float g_time=(float) vorbis_granule_time(&mInfo->VorbisDSPState,granule);
                                        if (g_time > time)
                                        {
                                                float **pcm;
                                                int len = vorbis_synthesis_pcmout(&mInfo->VorbisDSPState,&pcm);
                                                if (len > 0)
                                                        break;
                                                //ogg_stream_pagein(&mInfo->VorbisStreamState,&mInfo->OggPage);
                                                time=g_time;
                                                break;
                                        }
                                        last_page_time=g_time;
                                }
                                else ogg_stream_pagein(&mInfo->TheoraStreamState,&mInfo->OggPage);
                        }
                        else
                        {
                                char *buffer = ogg_sync_buffer( &mInfo->OggSyncState, 4096);
                                int bytesRead = mStream->read( buffer, 4096);
                                if (bytesRead == 0) break;
                                ogg_sync_wrote( &mInfo->OggSyncState, bytesRead );
                        }
                }
        }

        mTimer->seek(time);
        mSeekPos=-2; // tell the decoder to discard frames until the keyframe is found
        if (mAudioInterface) mAudioMutex->unlock();
}

void TheoraVideoClip::seek(float time)
{
        mSeekPos=time;
        mEndOfFile=false;
}

float TheoraVideoClip::getPriority()
{
        // TODO
        return getNumPrecachedFrames()*10.0f;
}

float TheoraVideoClip::getPriorityIndex()
{
        float priority=(float) getNumReadyFrames();
        if (mTimer->isPaused()) priority+=getNumPrecachedFrames()/2;
        return priority;
}

void TheoraVideoClip::setAudioInterface(TheoraAudioInterface* iface)
{
        mAudioInterface=iface;
}

TheoraAudioInterface* TheoraVideoClip::getAudioInterface()
{
        return mAudioInterface;
}

void TheoraVideoClip::setAudioGain(float gain)
{
        if (gain > 1) mAudioGain=1;
        if (gain < 0) mAudioGain=0;
        else          mAudioGain=gain;
}

float TheoraVideoClip::getAudioGain()
{
        return mAudioGain;
}

void TheoraVideoClip::setAutoRestart(bool value)
{
        mAutoRestart=value;
        if (value) mEndOfFile=false;
}