/********************************************************

	AVI player object
	Copyright 2000 Eugene Kuznetsov  (divx@euro.ru)

*********************************************************/

#include <version.h>
#include "RegAccess.h"
#include "aviplay_impl.h"

#ifdef USE_SDL
#include "SdlAudioRenderer.h"
#endif

#ifdef OSS_SUPPORT
#include "OssAudioRenderer.h"
#endif

#ifdef SUNAUDIO_SUPPORT
#include "SunAudioRenderer.h"
#endif

#include "CopyAudioRenderer.h"

#include <fourcc.h>
#include <cpuinfo.h>
#include <utils.h>

#include <errno.h>//EDEADLK
#include <time.h>//time
#include <unistd.h>//close
#include <pthread.h>

#include <fcntl.h>
#include <signal.h>//itimer
#include <sys/time.h>//gettimeofday
#include <sys/resource.h> //set/getpriority
#include <sys/ioctl.h>

//#include <iomanip>
#ifdef __NetBSD__
#include <sys/sched.h>
#else
#include <sched.h>
#endif
#include <iostream>

// has to go with mga_vid
#if	HAVE_SYS_IOCCOM_H
#include <sys/ioccom.h>
#endif
#include <../drivers/mga_vid.h>

using namespace std;
#include <cmath> // abs(double)

#define __MODULE__ "IAviPlayer"

// preffer video playing thread
const int PRIORITY_ADD_VIDEO = 16;
const int PRIORITY_ADD_AUDIO = 15;

int AVIPLAY_DEBUG = 0;
static bool sigfpe = false;
#undef Debug
#define Debug if(AVIPLAY_DEBUG)

#undef min
#define min(X,Y) ((X)<(Y)?(X):(Y))

double AviPlayer::getVideoAsync()
{
    if (m_lTimeStart == 0)
    {
	m_dFrameStart = (!sigfpe && m_pVideostream) ? m_pVideostream->GetTime() : 0.;
	m_dLastFrameStart = m_dFrameStart;
	m_lTimeStart = longcount();
	// we do not want to clear audio buffer here! - we may loose
	// up to 9 seconds of continuos sound after pause with
	// low quality audio!! seek is clearing buffers itself
	//if (m_pAudioRenderer)
	//    m_pAudioRenderer->clear();

	Debug cout << "AviPlayer::getVideoAsync(): resetting" << endl;
    }

    double actual_time;
    if (m_pAudioRenderer && !m_pAudioRenderer->Eof())
    {
	actual_time = m_pAudioRenderer->GetTime();
	//cout << "     audiorender " <<  actual_time << "   " <</* m_pAudioRenderer->GetSampleTime() << */endl;
    }
    else
    {
	actual_time = m_dFrameStart + to_float(longcount(), m_lTimeStart);
	//cout<<"noaudio " << " "<<to_float(time_current, m_lTimeStart)<<" since "<<m_dFrameStart<<endl;
    }
    //Debug cout<<"AviPlayer::getVideoAsync(): actual_time "<<actual_time<<endl;

    double stream_time = (m_pVideostream && !sigfpe) ? m_pVideostream->GetTime() : actual_time;
    //cout << "stream time " << stream_time << endl;
    //Debug cout<<"AviPlayer::getVideoAsync(): stream_time "<<stream_time<<endl;
    //cout<<"Async "<<stream_time-actual_time<<"\tvstream "<<stream_time<<"\tastream "<<actual_time<<endl;
    //cout << m_dFrameStart << m_lTimeStart << endl;
    return stream_time - actual_time;
}

/*
 * check for synchronization event
 *
 * might block this task - it will await for broadcast from the main thread
 * returns: 0 - quit
 *          1 - ok could continue
 */
int AviPlayer::checkSync(ThreadId i)
{
    while (!m_bQuit)
    {
	//cout << "Thread: " << i  << " m_bHangup: " << m_iHangup << endl;
	if (m_bHangup)
	{
	    if (m_ThreadCond[i].Wait(m_ThreadMut[i]))
		perror("AviPlayer::checkSync pthread_cond_wait");

	    Debug cout << "Thread unlocked " << i << endl;
	}

	if (!m_bInitialized) // FIXME: remove me later
	{
	    Debug cout << "not initialized" << endl;
	    m_ThreadCond[i].Wait(m_ThreadMut[i], 0.2);
	    continue;
	}
	if (!IsOpened())
	{
	    Debug cout << "not opened" << endl;
	    m_ThreadCond[i].Wait(m_ThreadMut[i], 0.2);
	    continue;
	}
	if (!IsValid())
	{
	    Debug cout << "not valid" << endl;
	    return 0;
	}

	break;
    }

    return (!m_bQuit);
}

/*
 * block all running threads
 */
void AviPlayer::lockThreads(const char *name)
{
    Locker locker(m_LockMutex);

    if (m_bPaused || m_bHangup || ++m_iLockCount > 1)
        return;

    m_bHangup = true;

    Debug cout << "Waiting for main_thread to hang up ("
	<< ((name) ? name : "???") << ")... " << flush;

    if (!sigfpe && m_pVideostream)
    {
	m_pVideostream->GetDecoder()->SetEnabled(false);
	m_ThreadMut[THREAD_DECODER].Lock();
	Debug cout << " decoder" << flush;
	m_pVideostream->GetDecoder()->SetEnabled(true);
	m_ThreadMut[THREAD_VIDEO].Lock();
	Debug cout << " video" << flush;
    }

    if (m_pAudioRenderer)
	m_pAudioRenderer->Wake();
    m_ThreadMut[THREAD_AUDIO].Lock();
    Debug cout << " audio" << flush;

    syncFrame();

    // now we have all threads locked
    m_bInitialized = false;

    Debug cout << " OK!" << endl;
}

/*
 * unlock all waiting threads in cond_wait state
 */
void AviPlayer::unlockThreads()
{
    Locker locker(m_LockMutex);

    if (m_bPaused || --m_iLockCount > 0)
        return;

    m_iLockCount = 0;
    m_lTimeStart = 0;
    m_bHangup = false;
    m_bInitialized = true;

    Debug cout << "Unlock threads" << endl;
    m_ThreadCond[THREAD_AUDIO].Broadcast();
    m_ThreadMut[THREAD_AUDIO].Unlock();

    if (!sigfpe && m_pVideostream)
    {
	m_ThreadCond[THREAD_VIDEO].Broadcast();
	m_ThreadMut[THREAD_VIDEO].Unlock();

	m_ThreadCond[THREAD_DECODER].Broadcast();
	m_ThreadMut[THREAD_DECODER].Unlock();
    }
}

static void sigfpe_handler(int hubba)
{
    if (!sigfpe)
	cout << "Got FATAL FPE Signal! - trying to continue with sound stream" << endl;

    sigfpe = true;

    // we can't just return from this place - as this would cause endless
    // loop here, so we will wait for another signal (break, kill)
    // end exit whole program
    pause();
    exit(0);
}

// returns true when some frames were dropped
bool AviPlayer::dropFrame(bool leaveLocked)
{
    //cout << "Oj" <<  m_bVideoAsync << "   " << to_float(longcount(), m_lTimeStart) << endl;
    //cout << "ATime " << m_pAudioRenderer->GetTime() << endl;
    if (m_bVideoAsync || !m_bVideoDropping
	|| to_float(longcount(), m_lTimeStart) < 1.0
	|| m_DropMutex.TryLock() == EBUSY)
	return false;

    bool ret = false;
    // here we try to eliminate all the frames
    // until next key-frame with reasonable close time
    framepos_t orig = m_pVideostream->GetPos();
    framepos_t kpos = orig;
    for (;;)
    {
	framepos_t npos = m_pVideostream->GetNextKeyFrame(kpos);
	double curtime = (m_pAudioRenderer) ? m_pAudioRenderer->GetTime() :
	    m_dFrameStart + to_float(longcount(), m_lTimeStart);
	double nt = m_pVideostream->GetTime(npos) - curtime;

	Debug cout << "AviPlayer::dropFrame() " << endl << "  async  " << getVideoAsync()
	    << "  new_vtime: " << m_pVideostream->GetTime(npos)
	    << "  cur_atime: " << curtime
	    << "  diff " << nt << endl;
	if (nt > 0.1 || npos <= kpos || npos == m_pVideostream->ERR)
	    break;
	//cout << "skip2 " << (npos - kpos) << endl;
        framepos_t posbef = m_pVideostream->GetPos();
	kpos = m_pVideostream->SeekToNextKeyFrame();
	if (kpos != npos)
	{
	    Debug cout << "AviPlayer::dropFrame()  logical fault compare "
		<< npos << "   " << kpos << "   " << orig << "   " << m_pVideostream->GetPos() << "   before " << posbef << endl;
	    //abort();
	}
    }

    if (kpos > orig && kpos != m_pVideostream->ERR)
    {
	ret = true;
	// and finaly we we have keyframe which is
	// is not to ahead of audio
	Debug cout << "AviPlayer::dropFrame() skipped " << (kpos - orig)
	    << " frames  ( " << orig << "  " << kpos << " )" << endl;
	while (orig++ < kpos)
	{
	    m_Drop.insert(100.0);
	    m_iFrameDrop++;
	}
    }

    if (!ret)
    {
	double async = getVideoAsync();

	if (async < -4.0 || (async < -0.07 && m_Drop.average() < 0.05))
	{
            if (async < -1.0)
		m_pVideostream->GetDecoder()->FlushCache();
	    else
	    {
		CImage *im = m_pVideostream->GetFrame(!m_bVideoBuffered);
		if (im)
		{
		    im->Release();
		    m_Drop.insert(100.0);
		    m_iFrameDrop++;
		    Debug cout << "Dropped video frames: " << m_iFrameDrop
			//<< "  a: " << ((m_pAudiostream) ? m_pAudiostream->GetTime() : 0.0)
			<< " atime: " << ((m_pAudioRenderer) ? m_pAudioRenderer->GetTime() : 0.0)
			<< " vtime: " << m_pVideostream->GetTime()
			<< endl;
		    ret = true;
		}
	    }
	}
    }
    if (0)
    {
	// old unused version
	// skip even frame decoding
	// but only non-keyframes
	int flags;
	m_pVideostream->GetFrameFlags(&flags);
	if (!flags)
	    m_pVideostream->SkipFrame();
    }
    //cout << "-----DROPDONE------" << endl;
    if (!leaveLocked || !ret)
	m_DropMutex.Unlock();

    return ret;
}

void* AviPlayer::videoThread()
{
    ThreadId th = THREAD_VIDEO;
    m_ThreadMut[th].Lock();
    Debug cout << "Thread video started" << endl;

    static const double NO_SLEEP = 1e9;
    double lastAsync = NO_SLEEP;
    double acceptOvertime = 0.011;

#ifdef SCHED_FIFO
    changePriority("Video player", 0, SCHED_FIFO);
#endif
    int mga_vid = open("/dev/mga_vid", O_RDONLY);
    if (mga_vid == -1)
	mga_vid = open("/dev/misc/mga_vid", O_RDONLY);

    // can't be smaller then 0.002!
    // not sure but maybe we should provide external
    // configurable option for this parameter -  differnet
    // params might be needed for differnet OS
    double minWait = 0.012;
    double addTime = 0.001;

    if (mga_vid != -1)
    {
	ioctl(mga_vid, MGA_VID_IRQ_ON, 0);
        minWait = 0.002;
        cout << "Using MGA_VID device for VBI synchronization" << endl;
    }

    while (checkSync(th))
    {
	//cout << "VTIME: " << m_pVideostream->GetTime() << "  p: "
	//    << m_pVideostream->GetPos()
	//    << "    " << m_pAudiostream->GetTime() << "    " << getVideoAsync()<< endl;
	if (sigfpe)
	{
	    m_ThreadCond[th].Wait(m_ThreadMut[th], 1.0);
            continue;
	}
	if (m_pVideostream->Eof())
	{
	    //cout << "video stream eof" << endl;
	    m_ThreadCond[th].Wait(m_ThreadMut[th], 1.0);
#if 1
	    m_DropMutex.Lock();
	    if (m_pAudioRenderer)
		m_pAudioRenderer->Reseek(0);
	    m_pVideostream->Seek((framepos_t) 0);
	    m_lTimeStart = 0;
	    m_DropMutex.Unlock();
#endif
	    continue;
	}

	double async = getVideoAsync();
	//cout << " ASYNC       " << async << endl;
	if (lastAsync != async && async > minWait)
	{
	    lastAsync = async;
	    // also used for time in GetPos calculation!

	    if (mga_vid >= 0)
	    {
		int buf[3];
		read(mga_vid, &buf, sizeof(buf));
		// always wait for at least one VBI event
		//cout << buf[0] << " --- " << buf[1] << endl;
		continue;
	    }
	    else
	    {
		if (async > 0.5)
		    async = 0.5;

		float asyncCalc = async - minWait + addTime;
		addTime = 0.001;
		// at least 0.001s wait, which usually becomes 0.011s
		int64_t t1 = longcount();
		//cout << async << "  " << o << "  " << m_dVframetime << endl;
                if (asyncCalc > 0.0)
		    m_ThreadCond[th].Wait(m_ThreadMut[th], asyncCalc);
		float realSleep = to_float(longcount(), t1);
                float miss = realSleep - async; // diff between expected time and real wait time

		if (miss > acceptOvertime)
		{
		    Debug cout << "Delay add: " << async << "  " << asyncCalc
			<< " r: " << realSleep << " d: " << miss
			<< " as: " << getVideoAsync() << endl;
		}
		else if (miss < 0.0 && miss > -0.007)
		    addTime += miss;

		continue;
	    }
	}
	if (lastAsync == NO_SLEEP)
	{
	    Debug cout << "not sleeping " << async << endl;
	}
        else
	    lastAsync = NO_SLEEP;

	syncFrame();
	m_lLastTimeStart = longcount();
	m_dLastFrameStart = m_pVideostream->GetTime();

	bool dropLocked = false;
	//cout << "ASXXXX  " << async << "   " << GetAvgDrop() << endl;
	if (async < -0.08)
	    dropLocked = dropFrame(true);

	CImage *im = m_pVideostream->GetFrame(!m_bVideoBuffered);
	if (dropLocked)
	    m_DropMutex.Unlock();

	if (!im)
	    // could be Hangup -> checkSync
	    continue;

	m_dVframetime = m_pVideostream->GetTime() - m_dLastFrameStart;

	if (m_bVideoMute)
	{
	    im->Release();
	    continue;
	}
	//cout << " ---------  newtime " << m_pVideostream->GetTime() << "\t\t" << m_dVframetime << endl;

	// draw this frame
	m_iFramesVideo++;
	m_Drop.insert(0.0);
	m_Quality.insert(im->GetQuality() * 100.0);

	int64_t copys = longcount();
	// copy image to graphics card
	const subtitle_line_t* sl = GetCurrentSubtitles();
	for (unsigned i = 0; i < m_VideoRenderers.size(); i++)
	{
	    m_VideoRenderers[i]->Draw(im);
	    if (HasSubtitles())
		m_VideoRenderers[i]->DrawSubtitles(sl);
	    //m_VideoRenderers[i]->Sync();
	}
	float copyd = to_float(longcount(), copys); // copy time
 	im->Release();
	m_bCallSync = true;
    }

    if (mga_vid != -1)
    {
	ioctl(mga_vid, MGA_VID_IRQ_OFF, 0);
        close(mga_vid);
    }

    Debug cout << "Thread video finished" << endl;
    return 0;
}

void* AviPlayer::audioThread()
{
    ThreadId th = THREAD_AUDIO;

    m_ThreadMut[th].Lock();
    // for m_pAudioRenderer == NULL this thread is not started
    // so we don't have to check for this condition
    Debug cout << "Thread audio started" << endl;
    bool paused = false;

    changePriority("Audio decoder ", PRIORITY_ADD_AUDIO);

    while (checkSync(th))
    {
	if (m_bAudioMute ||  m_pAudioRenderer->Eof())
	{
	    //m_pAudioRenderer->Stop();
	    m_ThreadCond[th].Wait(m_ThreadMut[th], 1.0);
            if (!m_pVideostream && m_pAudioRenderer->Eof())
	    {
		m_pAudioRenderer->Reseek(0);
		//m_pAudioRenderer->Stop();
		//m_pAudioRenderer->Start();
	    }
	    continue;
	}

	double async = getVideoAsync();
	double buftime = m_pAudioRenderer->GetBufferTime();

	if (!m_bVideoAsync && to_float(longcount(), m_lTimeStart) > 1.0 && async < -1.0)
	{
	    if (!paused && async < -10.0)
	    {
		// ok even with dropping we could not stay in sync
		// so for a while turn of audio track and wait
                // until video will match audio time
		Debug cout << "Thread audio large async time: " << async << "  bt: " << buftime << endl;
		m_pAudioRenderer->Pause(true);
		paused = true;
                //cout << "pausing" << endl;
	    }
	    else if (buftime > 0.7 && m_pVideostream)
	    {
		//cout << "ADROP - buftime " << buftime << " - " << m_pAudioRenderer->bufferTime() << endl;
		if (dropFrame())
		    continue;
	    }
	}
        else if (paused && async > 0.02)
	    paused = (m_pAudioRenderer->Pause(false) != 0);

	//cout << "atime:" << async << "    btime:" << buftime << endl;
	if (buftime > 0.7)
	{
	    m_ThreadCond[th].Wait(m_ThreadMut[th], 0.2);
	    continue;
	}

	double t = (m_pVideostream) ? m_pVideostream->GetTime() : m_pAudioRenderer->GetTime();
	m_pAudioRenderer->Extract(t);
    }

    Debug cout << "Thread audio finished" << endl;
    return 0;
}

void* AviPlayer::decoderThread()
{
    ThreadId th = THREAD_DECODER;

    m_ThreadMut[th].Lock();
    // thread started only with (stream != NULL)
    Debug cout << "Thread decoder started" << endl;

    changePriority("Video decoder ", PRIORITY_ADD_VIDEO);

    signal(SIGFPE, sigfpe_handler);

    if (geteuid() == 0)
        cout << "!!!Running video decoder as ROOT - Dangerous!!!" << endl;

    while (checkSync(THREAD_DECODER))
    {
	if (!m_bVideoBuffered || m_pVideostream->ReadFrame() < 0)
	{
	    m_ThreadCond[th].Wait(m_ThreadMut[th], 0.1);
	}
    }

    Debug cout << "Thread decoder finished" << endl;
    return 0;
}

void AviPlayer::changePriority(const char* taskName, int addPriority, int schedt)
{

#if !defined(__NetBSD__) && !defined(__FreeBSD__)
    if (addPriority != 0)
    {
        if (!taskName)
            taskName = "";
#if 1
	int p = getpriority(PRIO_PROCESS, 0) + addPriority;
	if (p > 20)
	    p = 20;
	setpriority(PRIO_PROCESS, 0, p);
	cout << taskName << "thread lowered priority to "
	    << getpriority(PRIO_PROCESS, 0) << endl;
#else
	// this code doesn't work on Linux!
	// it looks like priority is completely ignored and the only
        // way is to use above setpriority call

	int policy;
	struct sched_param sched;
	pthread_getschedparam(pthread_self(), &policy, &sched);
	sched.sched_priority += addPriority;
	pthread_setschedparam(pthread_self(), policy, &sched);
	cout << taskName << " using priority: " << sched.sched_priority << endl;
#endif
    }

    if (schedt != 0 && m_iEffectiveUid == 0)
    {
        // we could use FIFO scheduler with root access rights
        seteuid(m_iEffectiveUid);
        setegid(m_iEffectiveGid);

	static const char *sched_txt[] = {
	    "SCHED_OTHER", "SCHED_FIFO", "SCHED_RR"
	};
	struct sched_param schedRec;

	if (sched_getparam(0, &schedRec) != 0) {
	    perror("sched_getparam");
	    return;
	}

	schedRec.sched_priority = sched_get_priority_min(schedt);

	if (sched_setscheduler(0, schedt, &schedRec) != 0) {
	    perror("sched_setscheduler");
	    return;
	}

	cout << "Video thread - using scheduler: "
	    << sched_txt[sched_getscheduler(0)] << endl;
	setuid(getuid());
        setgid(getgid());
    }
#endif // __NetBSD__  & __FreeBSD__

}

AviPlayer::AviPlayer(const char* filename, int depth, const char* subname) throw(FatalError)
    :m_Drop("Drop", 50), m_Quality("Quality", 25)
{
    m_pVideostream = 0;
    m_pAudiostream = 0;
    m_bInitialized = false;
    m_bHangup = false;
    m_bPaused = false;
    m_bQuit = false;
    m_bCallSync = false;
    m_iLockCount = 0;
    m_lTimeStart = 0;
    m_dFrameStart = 0;
    m_dLastFrameStart = 0;
    m_iFramesVideo = 0;
    m_iFrameDrop = 0;
    m_pClip = 0;
    m_pAudiofunc = 0;
    m_pAudioRenderer = 0;
    m_fAsync = 0.0;
    m_fSubAsync = 0.0;
    m_fVolume = 1.0;
    m_dVframetime = 0.04;
    m_Fn = filename;
    m_Sn = subname ? subname : "";
    m_iDepth = depth;
    m_pKillhandler = 0;
    m_pSubtitles = 0;
    m_pSubline = 0;

    m_bQualityAuto = false;
    m_bVideoBuffered = false;
    m_bVideoDirect = false;

    memset(propertyRead, 0, sizeof(propertyRead));

    // remember effective ids at the creation time of the object
    // and use the real user id until we really need the euid
    m_iEffectiveUid = geteuid();
    m_iEffectiveGid = getegid();
    if (getuid() != m_iEffectiveUid)
	seteuid(getuid());
    if (getgid() != m_iEffectiveGid)
	setegid(getgid());

    m_bAudioMute = (getenv("AVIPLAY_MUTE_AUDIO") != NULL) ? true : false;
    m_bVideoMute = (getenv("AVIPLAY_MUTE_VIDEO") != NULL) ? true : false;

    m_bVideoAsync = (getenv("AVIPLAY_VIDEO_ASYNC") != NULL) ? true : false;

    AVIPLAY_DEBUG = (getenv("AVIPLAY_DEBUG")   != NULL) ? 1 : 0;

    Debug cout << "Debug is on " << AVIPLAY_DEBUG << endl;
    if (m_bVideoAsync)
	Debug cout << "Video is running asynchronously" << endl;
    try
    {
	if (m_pClip == 0)
	    m_pClip = CreateIAviReadFile(filename);
    }
    catch (FatalError& e)
    {
	e.Print();
	throw;
    }

    if (m_pClip->IsOpened())
    {
	if(!m_pClip->IsValid())
	    throw FATAL("Invalid file");
	construct();
    }
    else
    {
	pthread_t th;
	pthread_create(&th, 0, constructThread, this);
    }
}

IAviPlayer::~IAviPlayer()
{
}

AviPlayer::~AviPlayer()
{
    if (sigfpe)
    {
	cout << "Quiting whole program as SIGFPE has been caught!" << endl;
        exit(0);
    }

    Stop();
    m_bQuit = true;
    if (IsRedirector())
	return;
    unlockThreads();

    if (m_pVideostream)
    {
	int result = pthread_join(m_VideoThread, NULL);
	if (result == EDEADLK)
	{
	    cerr << "ERROR ERROR ERROR :  AviPlayer::endPlayer() called from video_thread" << endl;
	    pthread_exit(NULL);
	}
	if (m_pAudioRenderer)
	{
	    result = pthread_join(m_AudioThread, NULL);
	    if(result == EDEADLK)
	    {
		cerr << "ERROR ERROR ERROR :  AviPlayer::endPlayer() called from audio_thread" << endl;
		pthread_exit(NULL);
	    }
	}

	IVideoDecoder* vd = m_pVideostream->GetDecoder();
	if (vd)
	    vd->FlushCache();

	result = pthread_join(m_DecoderThread, NULL);
	if (result == EDEADLK)
	{
	    cerr << "ERROR ERROR ERROR :  AviPlayer::endPlayer() called from decoder" << endl;
	    pthread_exit(NULL);
	}
	m_pVideostream->StopStreaming();
    }

    while (m_VideoRenderers.size() > 0)
    {
	VideoRenderer* r = m_VideoRenderers.back();
	m_VideoRenderers.pop_back();
	delete r;
    }

    if (m_pAudioRenderer)
	delete m_pAudioRenderer;

    if (m_pAudiostream)
	m_pAudiostream->StopStreaming();

    if (m_pClip)
    {
	cout << "Closing clip" << endl;
	delete m_pClip;
    }
    if (m_iFramesVideo)
	cout<<"Played "<<m_iFramesVideo<<" video frames ( "
	    << m_iFrameDrop*100./m_iFramesVideo << "% drop )" << endl;
    if (m_pSubtitles)
	subtitle_close(m_pSubtitles);
    if (m_pSubline)
        subtitle_line_free(m_pSubline);
}

void* AviPlayer::constructThread(void* arg)
{
    AviPlayer* a = (AviPlayer*)arg;
    return a->constructThreadfunc();
}

void* AviPlayer::constructThreadfunc()
{
    while (!m_pClip->IsOpened() && !m_bQuit)
	avi_usleep(100000);

    if (m_bQuit || !m_pClip->IsValid())
	return 0;

    construct();

    return 0;
}

void AviPlayer::construct()
{
    cout << "File " << m_Fn << " successfully opened" << endl;
    if (IsRedirector())
    {
	cout<<"Redirector"<<endl;
	return;
    }
    cout << m_pClip->StreamCount() << " streams" << endl;

    int def_audio;

    Get(DEFAULT_AUDIO_STREAM, &def_audio, 0);
    while (!m_pAudiostream && def_audio >= 0)
	m_pAudiostream = m_pClip->GetStream(def_audio--, IAviReadStream::Audio);

    bool vqa;
    Get(VIDEO_QUALITY_AUTO, &vqa,
	VIDEO_DROPPING, &m_bVideoDropping,
	VIDEO_BUFFERED, &m_bVideoBuffered,
	VIDEO_DIRECT, &m_bVideoDirect,
	0);

    setVideoQualityAuto(vqa);

    if(m_pAudiostream==0)
	cout << "Audiostream not detected" << endl;
    else if (!m_bAudioMute)
    {
	delete m_pAudioRenderer;
	m_pAudioRenderer=0;
	try
	{
	    WAVEFORMATEX Owf;
	    memset(&Owf, 0, sizeof(Owf));
	    unsigned int forcedFreq = 0;

	    bool resamp;
            Get(AUDIO_RESAMPLING, &resamp, 0);
	    if (resamp)
	    {
		Get(AUDIO_RESAMPLING_RATE, &Owf.nSamplesPerSec,
		    AUDIO_PLAYING_RATE, &forcedFreq,
		    0);
	    }

	    if (!m_pAudiofunc)
	    {
		char* arend;
		Get(AUDIO_RENDERER, &arend, 0);
		if (!strcmp(arend, "SDL"))
		{
#ifdef USE_SDL
		    m_pAudioRenderer = new SdlAudioRenderer(m_pAudiostream, Owf, forcedFreq);
#else
		    cout << "Warning: SDL audio renderer unavailable!" << endl;
#endif
		}
		if (!m_pAudioRenderer && !strcmp(arend, "OSS"))
		{
#ifdef OSS_SUPPORT
		    m_pAudioRenderer = new OssAudioRenderer(m_pAudiostream, Owf);
#else
		    cout << "Warning: OSS audio renderer unavailable!" << endl;
#endif
		}

		if (!m_pAudioRenderer && !strcmp(arend, "SUN"))
		{
#ifdef SUNAUDIO_SUPPORT
		    cout << "Warning: Sun audio renderer out of date - update me!" << endl;
		    //		m_pAudioRenderer = new SunAudioRenderer(m_pAudiostream, Owf);
#else
		    cout << "Warning: Sun audio renderer unavailable!" << endl;
#endif
		}
		if (m_pAudioRenderer)
		{
		    m_pAudioRenderer->SetAsync(m_fAsync);
		    m_pAudioRenderer->SetVolume(m_fVolume);
		}
                free(arend);
	    }
	    else
		m_pAudioRenderer = new CopyAudioRenderer(m_pAudiostream, Owf, m_pAudiofunc, m_pAudiofuncArg);
	}
	catch(FatalError& error)
	{
	    error.Print();
	    m_pAudiostream=0;
	}
    }
    int w = 384, h = 70;
    if (!m_bVideoMute && m_iDepth)
    {
	m_pVideostream = m_pClip->GetStream(0, IAviReadStream::Video);
	if (!m_pVideostream)
	    cerr<<"Videotream not detected" << endl;
	else
	    try
	{
	    m_dVframetime = m_pVideostream->GetFrameTime();

	    // reading subtitles
	    if (m_Sn.size())
		InitSubtitles(m_Sn.c_str());
	    else if (m_Fn.size() >= 4
		     && (strncasecmp(m_Fn.c_str() + m_Fn.size() - 4, ".avi", 4) == 0))
	    {
		static const char* suff[] = {
		    ".sub", ".SUB", ".srt", ".SRT", ".txt", ".TXT", 0
		    // .txt is basicaly .sub
		    // .smi will be added later
		};
		string subname = m_Fn.substr(0, m_Fn.size() - 4);
		int i = 0;
		while (suff[i])
		{
		    string nm = subname + string(suff[i]);
		    if (InitSubtitles(nm.c_str()) == 0)
			break;
		    i++;
		}
	    }

	    int result = m_pVideostream->StartStreaming();
	    if (result != 0)
		throw FATAL("Failed to initialize decoder object");
	    cout << "Video Depth " << m_iDepth << endl;

	    // prefer this format
	    // remove 0 to get YUY2 rendering
	    if (0 & m_iDepth > 16)
		result = m_pVideostream->GetDecoder()->SetDestFmt(16, fccYUY2);
	    else
		result = m_pVideostream->GetDecoder()->SetDestFmt(m_iDepth);
	    if (result < 0)
	    {
		if (m_iDepth > 16)
		    // try again
		    result = m_pVideostream->GetDecoder()->SetDestFmt(m_iDepth);
		if (result < 0)
		    throw FATAL("Current bit m_iDepth is unsupported");
	    }
	    BITMAPINFOHEADER bh;
	    m_pVideostream->GetVideoFormatInfo(&bh, sizeof(bh));
	    w = bh.biWidth;
	    h = bh.biHeight;
	}
	catch (FatalError& e)
	{
	    e.Print();
	    cerr<<"No video will be available"<<endl;
	    m_pVideostream=0;
	}
    } else
    {
	cerr<<"Video disabled"<<endl;
    }

    m_iWidth = w;
    m_iHeight = h;
    m_iFramesVideo=0;
    m_iFrameDrop=0;
    m_lTimeStart=0;
    m_bQuit = false;
    //	short fs;
    //	__asm__ __volatile__ ("movw %%fs, %%ax":"=a"(fs));
    //	cout<<"Before pthread_create: fs is "<<fs<<endl;
    //	fs_seg=fs;

    m_bHangup = true;
    if (!m_bVideoMute && m_pVideostream)
    {
	if (pthread_create(&m_DecoderThread, NULL, startDecoderThread, (void*)this))
	    perror("Error creating video_thread");
	if (pthread_create(&m_VideoThread, NULL, startVideoThread, (void*)this))
	    perror("Error creating video_thread");
    }
    else if (!m_pAudioRenderer)
    {
	cerr << "Cannot play this" << endl;
	m_pClip = 0;
	return;
    }

    //m_pAudioRenderer = 0;
    if (!m_bAudioMute && m_pAudioRenderer)
	pthread_create(&m_AudioThread, NULL, startAudioThread, (void*)this);

    // now wait until these new threads hang in the cond_wait
    lockThreads("Init");
}

HRESULT AviPlayer::SetColorSpace(fourcc_t csp, bool test_only)
{
    if (!m_pVideostream || !m_pVideostream->GetDecoder())
	return -1;

    //cout << "SET COLOR SPACE " << hex << csp << dec << "  " << test_only << endl;
    if (!test_only)
	return m_pVideostream->GetDecoder()->SetDestFmt(0, csp);

    IVideoDecoder::CAPS cap;
    switch (csp)
    {
    case fccYUY2:
	cap = IVideoDecoder::CAP_YUY2;
        break;
    case fccI420:
	cap = IVideoDecoder::CAP_I420;
	break;
    case fccYV12:
	cap = IVideoDecoder::CAP_YV12;
        break;
    case fccIYUV:
	cap = IVideoDecoder::CAP_IYUV;
        break;
    case fccUYVY:
	cap = IVideoDecoder::CAP_UYVY;
        break;
    case fccYVYU:
	cap = IVideoDecoder::CAP_YVYU;
	break;
    default:
	cap = IVideoDecoder::CAP_NONE;
    }

    return !(m_pVideostream->GetDecoder()->GetCapabilities() & cap);
}

HRESULT AviPlayer::InitSubtitles(const char* filename)
{
    if (m_pVideostream)
    {
	m_pSubtitles = subtitle_open(m_pSubtitles, filename);
	if (m_pSubtitles)
	    subtitle_set_fps(m_pSubtitles, GetFps());
    }

    return 0;
}

StreamInfo* AviPlayer::GetAudioStreamInfo() const
{
    if (!m_pAudiostream)
	return 0;

    cout << "unimplemented sorry" << endl;
    return 0;
}

StreamInfo* AviPlayer::GetVideoStreamInfo() const
{
    if (!m_pVideostream)
	return 0;

    return m_pVideostream->GetStreamInfo();
}

const subtitle_line_t* AviPlayer::GetCurrentSubtitles()
{
    if (!m_pVideostream || !m_pSubtitles)
	return 0;
    if (m_pSubline == 0)
    {
	m_pSubline = subtitle_line_new();
	if (m_pSubline == 0)
            return 0;
    }

    int r = subtitle_get(m_pSubline, m_pSubtitles,
			 m_pVideostream->GetTime() + m_fSubAsync);
    if (!r)
	return 0;
    return m_pSubline;
}

bool AviPlayer::HasSubtitles() const
{
    return (m_pSubtitles && subtitle_get_lines(m_pSubtitles) > 0);
}
/*
int AviPlayer::SetDecoding(bool direct, bool buffered,
			   bool autoquality, bool async);
{
    int r = -1;
    if (m_pVideostream && m_pVideostream->GetDecoder())
    {
	if (mode == m_pVideostream->GetDecoder()->GetDecodingMode())
	    return 0;

	bool pl = IsPlaying();

	if (pl)
	    lockThreads("SetPlayMode");
	// has to be locked
	Debug cout << "AviPlayer::SetDecodingMode() " << mode << endl;
	// XXXX
	r = m_pVideostream->GetDecoder()->SetDecodingMode(mode);
	if (pl)
	    unlockThreads();
    }
    return r;
}
*/

void AviPlayer::Start()
{
    Debug cout << "AviPlayer::start" << endl;
    if (!IsValid()) return;
    if (IsRedirector()) return;
    if (IsPlaying())
    {
	cout<<"Can't start(), already started"<<endl;
	return;
    }
//    if(clip==0)
//    {
//	cout<<"Can't start(), no clip"<<endl;
//	return;
//    }
    m_bQuit = false;
    m_bPaused = false;
    m_bBuffering = false;

    if (m_pAudioRenderer)
    {
        // start audio first
        m_pAudioRenderer->Start();
	//m_pAudioRenderer->Reseek(0);
    }

    if (!sigfpe && m_pVideostream)
    {
	m_pVideostream->Seek((framepos_t)0);
	//m_pVideostream->GetDecoder()->SetDecodingMode(IVideoDecoder::REALTIME_QUALITY_AUTO);
    }

    unlockThreads();
}

void AviPlayer::Stop()
{
    Debug cout << "AviPlayer::stop" << endl;
    if (!IsPlaying())
	return;

    lockThreads("Stop");

    if (m_pAudioRenderer)
	m_pAudioRenderer->Stop();

    m_bPaused = false;

    //cout << "KILL handler " << (void*)m_pKillhandler<<endl;
    if (m_pKillhandler)
	m_pKillhandler(0, m_pKillhandlerArg);
}

void AviPlayer::Pause(bool state)
{
    Debug cout << "AviPlayer::pause " << m_bPaused << " -> " << state << endl;
    if (!IsPlaying())
        return;

    if (m_bPaused == state)
	return;

    if (state)
    {
	lockThreads("Pause");
	if (m_pAudioRenderer)
	    m_pAudioRenderer->Pause(state);
	m_bPaused = state;
    }
    else
    {
	if (m_pAudioRenderer)
	{
	    if (m_pAudioRenderer->Pause(state) != 0)
		return;
	}
	m_bPaused = state;

	unlockThreads();
    }
}

void AviPlayer::Play()
{
    Debug cout << "AviPlayer::play " << m_bPaused << endl;
    if (!IsPlaying())
        return;

    if (m_bPaused)
	Pause(!m_bPaused);
}

// in reality this is GetTime!!!
// FIXME - I would propose this name change
double AviPlayer::GetPos() const
{
    if (!sigfpe && m_pVideostream && !m_bVideoMute)
    {
	// this is bit more complicate so here goes light explanation:
	// Asf file could display same frame for 5 second - and
	// it doesn't look good when we show jumping numbers
	// so we pick the last remebered frame time and we are
	// calculating time from this frame - we should be preserveing
	// this speed - for paused mode we display time for currently
        // displayed frame
	double len = m_pVideostream->GetLengthTime();
	if (len > 0.)
	{
	    if (m_bPaused || !IsPlaying())
		return m_pVideostream->GetTime();

	    double ct = m_dLastFrameStart + to_float(longcount(), m_lLastTimeStart);
	    return (ct < len) ? ((ct > 0) ? ct : 0) : len;
	}
    }
    if (m_pAudioRenderer)
    {
	if (!m_pAudioRenderer->Eof())
	    return m_pAudioRenderer->GetTime();
        return m_pAudiostream->GetLengthTime();
    }
    return 0.;
}

framepos_t AviPlayer::GetFramePos() const
{
    return (!sigfpe && m_pVideostream) ? m_pVideostream->GetPos() : 0;
}

double AviPlayer::Reseek(double pos)
{
    Debug cout << "Seek pos: " << pos << endl;

    if (!IsValid() || IsRedirector() || !IsPlaying())
	return -1;

    lockThreads("Reseek");

    if (!sigfpe && m_pVideostream)
	pos = m_pVideostream->SeekTimeToKeyFrame(pos);

    Debug cout << "Keyframe pos: " << pos << endl;

    if (m_pAudioRenderer)
        m_pAudioRenderer->Reseek(pos);

    if (m_bPaused)
	drawFrame();

    unlockThreads();

    return pos;
}

HRESULT AviPlayer::ReseekExact(double pos)
{
    if (!IsValid() || IsRedirector() || !IsPlaying())
	return -1;

    // we might not need this in future
    lockThreads("ReseekExact");

    int r = 0;
    double pos2 = pos;

    if (!sigfpe && m_pVideostream)
    {
	double cft = m_pVideostream->GetTime();
	double nkft = m_pVideostream->GetTime(m_pVideostream->GetNextKeyFrame());
	if (cft < pos &&  pos < nkft)
	{
	    // do nothing
	    //cout << " NOT SEEKING " << cft << "  " << nkft << endl;
	    unlockThreads();
	    return 0;
	}

	pos2 = m_pVideostream->SeekTimeToKeyFrame(pos);
    }

    Debug cout << "Seek OK (" << pos << ", " << pos2 << ")" << endl;
    if (pos2 < 0.0)
    {
	pos2 = 0.0;
        pos = 0;
	cout << "ERROR: reseek_exact  pos2<0!" << endl;

	r = -1;
    }

    if (m_pAudioRenderer)
	m_pAudioRenderer->Reseek(pos2);

    if (pos2>pos)
    {
	cout << "ERROR: reseek_exact: pos2>pos!" << pos2 << "  " << pos << endl;
	r = -1;
    }
    else if (m_pVideostream)
    {
	if (m_bVideoMute) //?? why
	{
	    //Unsigned p=(Unsigned)(pos/stream->GetFrameTime());
	    cout << "??? video_mute_seek ???" << pos << endl;
	    m_pVideostream->SeekTime(pos);
	}
	else
	{
	    //m_bInVideoSeek=true;
	}

	drawFrame();
    }

    unlockThreads();
    return r;
}

HRESULT AviPlayer::PageUp()
{
    if (!IsValid() || IsRedirector() || !IsPlaying())
	return -1;

    int r = 0;
    lockThreads("PageUp");
    if (!sigfpe && m_pVideostream)
    {
	framepos_t pos = m_pVideostream->GetPos();
        framepos_t npos = m_pVideostream->GetNextKeyFrame();
	if (pos < npos && npos != m_pVideostream->ERR)
	{
	    m_pVideostream->SeekToKeyFrame(npos);
	    if (m_pAudioRenderer)
		m_pAudioRenderer->Reseek(m_pVideostream->GetTime());

	    drawFrame();
	}
	else
            r = -1;
    }
    else if (m_pAudioRenderer)
    {
	m_pAudioRenderer->Reseek(m_pAudioRenderer->GetTime() + 1.0);
    }

    unlockThreads();

    return r;
}

HRESULT AviPlayer::PageDown()
{
    if (!IsValid() || IsRedirector() || !IsPlaying())
	return -1;

    static framepos_t prevnpos = ~0;
    static int64_t prevtime = 0;

    int r = 0;
    lockThreads("PageDown");

    if (!sigfpe && m_pVideostream)
    {
        framepos_t cpos = m_pVideostream->GetPos();
	framepos_t npos = m_pVideostream->SeekToPrevKeyFrame();
        if (to_float(longcount(), prevtime) < 0.3)
	    while (npos > 0 && npos >= prevnpos)
	    {
		//cout << "  using trick " << endl;
		npos = m_pVideostream->SeekToPrevKeyFrame();
	    }

	//cout << " POSN " << npos << endl;

	if ((cpos - npos < 5) && !IsPaused())
	{
	    m_pVideostream->Seek(npos);
            npos = m_pVideostream->SeekToPrevKeyFrame();
	    //cout << " POSx " << npos << endl;
	}
        prevtime = longcount();
	prevnpos = npos;
	//cout << "Diff " << cpos << "   " << npos << endl;
	if (npos != m_pVideostream->ERR)
	{
	    //m_pVideostream->SeekToKeyFrame(npos);
	    double pos = m_pVideostream->GetTime();
	    if (m_pAudioRenderer)
		m_pAudioRenderer->Reseek(pos > 0.0 ? pos : 0.0);
	}
	drawFrame();
    }
    else if (m_pAudioRenderer)
	m_pAudioRenderer->Reseek(m_pAudioRenderer->GetTime() - 1.);

    unlockThreads();

    return r;
}

HRESULT AviPlayer::NextFrame()
{
    if (!IsValid() || IsRedirector() || !IsPlaying())
	return -1;

    if (m_pVideostream)
    {
	lockThreads("NextFrame");
	drawFrame();
	if (m_pAudioRenderer)
	    m_pAudioRenderer->Reseek(m_pVideostream->GetTime());
	unlockThreads();
    }

    return 0;
}

// used when we are in pause mode or the player is not playing
int AviPlayer::drawFrame()
{
    if (!sigfpe && m_pVideostream && !m_bVideoMute)
    {
	CImage* im = m_pVideostream->GetFrame(true); // ReadFrame
	if (im)
	{
	    const subtitle_line_t* sl = GetCurrentSubtitles();
	    for (unsigned i = 0; i < m_VideoRenderers.size(); i++)
	    {
		m_VideoRenderers[i]->Draw(im);
                if (HasSubtitles())
		    m_VideoRenderers[i]->DrawSubtitles(sl);

		m_VideoRenderers[i]->Sync();
	    }
	    im->Release();
	    m_iFramesVideo++;
	}
	return 0;
    }
    return -1;
}

void AviPlayer::syncFrame()
{
    if (!sigfpe && m_pVideostream && !m_bVideoMute && m_bCallSync)
    {
	m_bCallSync = false;

	int64_t newVideo = 0;
	double newAudio = 0;
        double async = 0;

	Debug
	{
	    // just for checking if these two diffs are correct
	    // for SdlAudioRender they are now matching!!
	    // Oss & Sun have to be fixed
	    newVideo = longcount();
	    newAudio = (m_pAudioRenderer) ? m_pAudioRenderer->GetTime() : 0.0;
	    async = getVideoAsync();
	}
	// blit image on screen
	for (unsigned i = 0; i < m_VideoRenderers.size(); i++)
	    m_VideoRenderers[i]->Sync();
	// splited into two parts so we do not create jumping images
	// even in the debug mode
	Debug
	{
	    // after check of ahead might be more presice time for this
	    // however we would have to play some with pausing
	    // so we currently place the image right  here
	    //double d = to_float(newVideo, lastVideo);
	    //if (d < 0.027 || d > 0.043)
	    printf("A-V sync: %+8.5f  sleep: %7.5f   Vdiff: %7.5f   Adiff: %7.5f\n",
		   async, to_float(newVideo, m_lLastTimeStart),
		   to_float(newVideo, m_lLastVideoSync),
		   (newAudio - m_dLastAudioSync));

	    float newDiff = to_float(newVideo, m_lLastVideoSync);
	    float d = newDiff - m_fLastDiff;
	    if (d < 0)
                d = -d;

	    if (d > 0.0125)
		printf("Larger diff between frames: %f\n", d);
	    m_lLastVideoSync = newVideo;
	    m_dLastAudioSync = newAudio;
            m_fLastDiff = newDiff;
	}
    }
}

AviPlayer::State AviPlayer::GetState(double* percent)
{
    if (!m_pClip)
	return Invalid;

    if (!m_pClip->IsOpened())
	return Opening;

    if (m_bBuffering)
    {
	if (percent)
	{
	    if (!sigfpe && m_pVideostream)
		*percent = m_pVideostream->CacheSize();
	    else if (m_pAudioRenderer)
		*percent = m_pAudioRenderer->GetCacheSize();
	}
	return Buffering;
    }
    if (IsPaused())
	return Paused;
    if (IsPlaying())
	return Playing;

    return Stopped;
}

bool AviPlayer::IsPaused() const
{
    return (m_pClip != NULL && m_bPaused);
}

bool AviPlayer::IsPlaying() const
{
    // when we are in paused mode  initialized is 0
    return m_bInitialized || m_bPaused;
}

bool AviPlayer::IsStopped() const
{
    return (m_pClip != NULL && !IsPlaying());
}

const std::vector<VideoRenderer*>& AviPlayer::GetVideoRenderers() const
{
    return m_VideoRenderers;
}

HRESULT AviPlayer::SetVideoRenderers(std::vector<VideoRenderer*>)
{
    cerr << "AviPlayer::SetVideoRenderers()  unimplemented yet sorry..." << endl;
    return -1;
}

void AviPlayer::SetAsync(float async)
{
    Set(ASYNC_TIME_MS, int(async * 1000), 0);
}

void AviPlayer::Restart()
{
    if (!IsPlaying() || !m_pVideostream || sigfpe)
	return;

    IVideoDecoder* vs = m_pVideostream->GetDecoder();
    if (!vs)
	return;

    if (m_bPaused)
    {
	vs->Restart();
	return;
    }

    lockThreads("Restart");
    vs->Restart();
    unlockThreads();
    ReseekExact(GetPos());
}

IAviPlayer* CreateAviPlayer(const char* filename, int bitdepth, const char* subname) throw(FatalError)
{
    return new AviPlayer(filename, bitdepth, subname);
}

extern "C" double GetAvifileVersion()
{
    return AVIFILE_VERSION;
}

void* AviPlayer::startVideoThread(void* arg)
{
    AviPlayer* a=(AviPlayer*)arg;
    return a->videoThread();
}

void* AviPlayer::startAudioThread(void* arg)
{
    AviPlayer* a=(AviPlayer*)arg;
    return a->audioThread();
}

void* AviPlayer::startDecoderThread(void* arg)
{
    AviPlayer* a=(AviPlayer*)arg;
    return a->decoderThread();
}
/*
vim: tabstop=8
*/
