/************************************************************************************
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 "TheoraVideoManager.h"
#include "TheoraWorkerThread.h"
#include "TheoraVideoClip.h"
#include "TheoraAudioInterface.h"
#include "TheoraUtil.h"
#include "TheoraDataSource.h"

TheoraVideoManager* g_ManagerSingleton=0;
// declaring function prototype here so I don't have to put it in a header file
// it only needs to be used by this plugin and called once
void createYUVtoRGBtables();

void theora_writelog(std::string output)
{
        printf("%s\n",output.c_str());
}

void (*g_LogFuction)(std::string)=theora_writelog;

void TheoraVideoManager::setLogFunction(void (*fn)(std::string))
{
        g_LogFuction=fn;
}

TheoraVideoManager* TheoraVideoManager::getSingletonPtr()
{
    return g_ManagerSingleton;
}

TheoraVideoManager& TheoraVideoManager::getSingleton()
{  
    return *g_ManagerSingleton;  
}

TheoraVideoManager::TheoraVideoManager(int num_worker_threads) :
        mDefaultNumPrecachedFrames(16)
{
        g_ManagerSingleton=this;

        logMessage("Initializing Theora Playback Library ("+this->getVersionString()+")");

        mAudioFactory = NULL;
        mWorkMutex=new TheoraMutex();

        // for CPU yuv2rgb decoding
        createYUVtoRGBtables();
        createWorkerThreads(num_worker_threads);
}

TheoraVideoManager::~TheoraVideoManager()
{
        destroyWorkerThreads();

        ClipList::iterator ci;
        for (ci=mClips.begin(); ci != mClips.end();ci++)
                delete (*ci);
        mClips.clear();
        delete mWorkMutex;
}

void TheoraVideoManager::logMessage(std::string msg)
{
        g_LogFuction(msg);
}

TheoraVideoClip* TheoraVideoManager::getVideoClipByName(std::string name)
{
        foreach(TheoraVideoClip*,mClips)
                if ((*it)->getName() == name) return *it;

        return 0;
}

void TheoraVideoManager::setAudioInterfaceFactory(TheoraAudioInterfaceFactory* factory)
{
        mAudioFactory=factory;
}

TheoraAudioInterfaceFactory* TheoraVideoManager::getAudioInterfaceFactory()
{
        return mAudioFactory;
}

TheoraVideoClip* TheoraVideoManager::createVideoClip(std::string filename,
                                                                                                         TheoraOutputMode output_mode,
                                                                                                         int numPrecachedOverride,
                                                                                                         bool usePower2Stride)
{
        TheoraDataSource* src=new TheoraFileDataSource(filename);
        return createVideoClip(src,output_mode,numPrecachedOverride,usePower2Stride);
}

TheoraVideoClip* TheoraVideoManager::createVideoClip(TheoraDataSource* data_source,
                                                                                                         TheoraOutputMode output_mode,
                                                                                                         int numPrecachedOverride,
                                                                                                         bool usePower2Stride)
{
        mWorkMutex->lock();

        TheoraVideoClip* clip = NULL;
        int nPrecached = numPrecachedOverride ? numPrecachedOverride : mDefaultNumPrecachedFrames;
        logMessage("Creating video from data source: "+data_source->repr());
        clip = new TheoraVideoClip(data_source,output_mode,nPrecached,usePower2Stride);
        mClips.push_back(clip);
        mWorkMutex->unlock();
        return clip;
}

void TheoraVideoManager::destroyVideoClip(TheoraVideoClip* clip)
{
        if (clip)
        {
                th_writelog("Destroying video clip: "+clip->getName());
                mWorkMutex->lock();
                bool reported=0;
                while (clip->mAssignedWorkerThread)
                {
                        if (!reported) { th_writelog("Waiting for WorkerThread to finish decoding in order to destroy"); reported=1; }
                        _psleep(1);
                }
                if (reported) th_writelog("WorkerThread done, destroying..");
                foreach(TheoraVideoClip*,mClips)
                        if ((*it) == clip)
                        {
                                mClips.erase(it);
                                break;
                        }
                delete clip;
                th_writelog("Destroyed video.");
                mWorkMutex->unlock();
        }
}

TheoraVideoClip* TheoraVideoManager::requestWork(TheoraWorkerThread* caller)
{
        if (!mWorkMutex) return NULL;
        mWorkMutex->lock();
        TheoraVideoClip* c=NULL;

        float priority,last_priority=100000;

        foreach(TheoraVideoClip*,mClips)
        {
                if ((*it)->isBusy()) continue;
                priority=(*it)->getPriorityIndex();
                if (priority < last_priority)
                {
                        last_priority=priority;
                        c=*it;
                }
        }
        if (c) c->mAssignedWorkerThread=caller;
       
        mWorkMutex->unlock();
        return c;
}

void TheoraVideoManager::update(float time_increase)
{
        foreach(TheoraVideoClip*,mClips)
        {
                (*it)->update(time_increase);
                (*it)->decodedAudioCheck();
        }
}

int TheoraVideoManager::getNumWorkerThreads()
{
        return mWorkerThreads.size();
}

void TheoraVideoManager::createWorkerThreads(int n)
{
        TheoraWorkerThread* t;
        for (int i=0;i<n;i++)
        {
                t=new TheoraWorkerThread();
                t->startThread();
                mWorkerThreads.push_back(t);
        }
}

void TheoraVideoManager::destroyWorkerThreads()
{
        foreach(TheoraWorkerThread*,mWorkerThreads)
        {
                (*it)->waitforThread();
                delete (*it);
        }
        mWorkerThreads.clear();
}

void TheoraVideoManager::setNumWorkerThreads(int n)
{
        if (n == getNumWorkerThreads()) return;
        th_writelog("changing number of worker threats to: "+str(n));

        destroyWorkerThreads();
        createWorkerThreads(n);
}

std::string TheoraVideoManager::getVersionString()
{
        int a,b,c;
        getVersion(&a,&b,&c);
        std::string out=str(a)+"."+str(b);
        if (c != 0)
        {
                if (c < 0) out+=" RC"+str(-c);
                else       out+="."+str(c);
        }
        return out;
}

void TheoraVideoManager::getVersion(int* a,int* b,int* c)
{
        *a=1;
        *b=0;
        *c=-2;
}