#include "frame.h"
#include <videodecoder.h>
#include <cpuinfo.h>
#include <utils.h>
#include <fourcc.h>
#include <renderer.h>

#include <unistd.h>
#include <sys/time.h> //gettimeofday
#include <iostream>

using namespace std;

IVideoDecoder::IVideoDecoder(const CodecInfo& info, const BITMAPINFOHEADER& format)
    :record(info), m_bEnabled(true), m_pDecoderImage(0), m_pVideoRenderer(0)
{
    // initialize buffer's 'constants' -
    // FIXME - in the future add some methods for runtime change
    // now we could play with these just by recompiling this source file
    VBUFSIZE = 13;
    QMARKHI = VBUFSIZE - 2;
    if (QMARKHI < 0)
	QMARKHI = 0;
    QMARKLO = QMARKHI - 10;
    if (QMARKLO < 0)
        QMARKLO = 0;
    DMARKHI = 6; // unused
    DMARKLO = 4;

    vbuf = 0;

    unsigned bihs = (format.biSize < (int) sizeof(BITMAPINFOHEADER)) ?
	sizeof(BITMAPINFOHEADER) : format.biSize;
    m_bh = (BITMAPINFOHEADER*) new char[bihs];
    memcpy(m_bh, &format, bihs);

    m_State = STOP;
    m_pFrame = 0;
    m_Mode = DIRECT;
    m_iDecpos = 0;
    m_iPlaypos = -1;
    m_fQuality = 0.0f;
    m_bCapable16b = true;

    // some codecs really needs the last frame
    switch (m_bh->biCompression)
    {
    case fcccvid:
    case fccIV32:
	m_bLastNeeded = true;
        break;
    default:
	m_bLastNeeded = false;
    }
}

IVideoDecoder::~IVideoDecoder()
{
    // this locking is rather for safety and
    // might be removed later
    // now it's for catching our faults in the
    // thread comunication - when it locks here
    // we have made something wrong elsewhere
    decoding.Lock();
    reading.Lock();
    decposmut.Lock();
    playposmut.Lock();

    if (m_Mode != DIRECT)
        delete[] vbuf;

    delete m_pFrame;
    delete (char*) m_bh;
}

const BITMAPINFOHEADER& IVideoDecoder::DestFmt() const
{
    return (const BITMAPINFOHEADER&) m_obh;
}

int IVideoDecoder::DropFrame() const
{
    int r = 0;

    if (m_Mode != DIRECT)
    {
	Locker locker(playposmut);
	if (m_iPlaypos >= 0)
	    r = vbuf[ m_iPlaypos]->getDrop();
    }

    return r;
}

int IVideoDecoder::FlushCache()
{
    if (m_Mode != DIRECT)
    {
	Locker locker(playposmut);
	decposmut.Lock();

	frame *tmp = vbuf[0];
	tmp->setPos(~0U);
	tmp->setTime(-1.0);
	m_bFilling = true;
	m_iDecpos = 0;
	decposmut.Unlock();

	m_iPlaypos = -1;
	playposcond.Broadcast();
	//cout << "VIDEO FLUSHED" << endl;
    }
    else
    {
        m_pFrame->setTime(-1.0);
        m_pFrame->setPos(~0U);
    }

    return 0;
}

IVideoDecoder::CAPS IVideoDecoder::GetCapabilities() const
{
    return CAP_NONE;
}

const CodecInfo& IVideoDecoder::GetCodecInfo() const
{
    return record;
}

framepos_t IVideoDecoder::GetPos() const
{
    framepos_t p;

    if (m_Mode != DIRECT)
    {
	Locker locker(playposmut);
	p = (m_iPlaypos >= 0) ? vbuf[m_iPlaypos]->getPos() : ~0U;
    }
    else
        p = m_pFrame->getPos();

    return p;
}

double IVideoDecoder::GetTime() const
{
    double t;
    if (m_Mode != DIRECT)
    {
	Locker locker(playposmut);
	t = (m_iPlaypos >= 0) ? vbuf[m_iPlaypos]->getTime() : -1.0;
    }
    else
        t = m_pFrame->getTime();

    return t;
}

CImage* IVideoDecoder::GetFrame()
{
    Locker locker(reading);
    CImage* im;

    if (m_Mode != DIRECT)
    {
	playposmut.Lock();
	decposmut.Lock();

	if ((m_iPlaypos + 1) >= m_iDecpos)
	{
	    decposmut.Unlock();
	    playposmut.Unlock();
	    // we don't have new frame
	    // I'm not sure if we want to use waiting condition here
	    // I think this is responsibility of the library user
	    // anyway for now we will sleep for a while
	    //cout << "expecting new frame  " << m_iPlaypos << "  " << m_iDecpos << "  " << m_bWakeUp << endl;
	    decposmut.Lock();
	    decposcond.Wait(decposmut, 0.1);
	    decposmut.Unlock();
	    // we have to get decpos mutex after playpos mutex
	    // that is why we unlock/lock decpos several times here
	    playposmut.Lock();
	    decposmut.Lock();
	    if ((m_iPlaypos + 1) >= m_iDecpos)
	    {
		decposmut.Unlock();
		playposmut.Unlock();
		return 0;
	    }
	    //cout << "have expecting new frame  " << m_iPlaypos << "  " << m_iDecpos << "  " << m_bWakeUp << endl;
	}
	decposmut.Unlock();

	// we do not want to have our picture overwritten
	// so we are increasing position here
	m_iPlaypos++;
	//cout << "Reading " << m_iPlaypos << endl;
	im = vbuf[m_iPlaypos]->getData();
	//static framepos_t lpp = 0;
	//if (vbuf[m_iPlaypos]->getPos() - lpp > 1)
	//    cout << "data " << vbuf[m_iPlaypos]->getTime() << "  " << vbuf[m_iPlaypos]->getPos() << "  lp:" << lpp << "  " << m_iPlaypos << endl;
	//lpp = vbuf[m_iPlaypos]->getPos();
	playposcond.Broadcast();
	playposmut.Unlock();
    }
    else // DIRECT mode
	im = (m_pFrame) ? m_pFrame->getData() : 0;

    if (!im)
	return 0;

    if (!im->IsFmt(&m_obh))
	// this should be useless and should never happen!
	return new CImage(im, &m_obh);

    im->AddRef();
    return im;
}

int IVideoDecoder::GetFreeBuffers() const
{
    if (m_Mode == DIRECT)
	return ((m_State == START) ? 1 : 0);

    return VBUFSIZE - 1 - m_iDecpos + m_iPlaypos;
}

int IVideoDecoder::DecodeFrame(void* src, size_t size, framepos_t fpos,
			       double ftime, int is_keyframe, bool render)
{
    // can this affect the playback in m_Mode mode???
    if (!size)
	return 0;

    //cout << "Decoding " << fpos << "   " << ftime << "   p " << m_iPlaypos << " d " << m_iDecpos << " state: " << m_State << endl;
    Locker locker(decoding);

    if (m_State != START)
    {
	cout << "VideoDecoder::DecodeFrame() decoder not started!" << endl;
        return -1;
    }

    m_bh->biSizeImage = size;

    if (m_Mode == DIRECT)
    {
	//cout << "USING DIRECT  " << m_pFrame->getData()->GetUserData()
	//    << "   " << (void*) m_pFrame->getData()->Data() << endl;
	CImage* im = (render) ? m_pFrame->getData() : 0;
	if (im && m_pDecoderImage)
            im = m_pDecoderImage;

	int r = DecodeInternal(src, size, is_keyframe, im);
	if (im && (im == m_pDecoderImage))
	{
	    //cout << "Conversion" << endl;
	    //im->GetFmt()->Print();
	    //m_pFrame->getData()->GetFmt()->Print();
	    m_pFrame->getData()->Convert(im);
	}
	m_pFrame->setTime(ftime);
	m_pFrame->setPos(fpos);
	m_pFrame->setDataQuality(m_fQuality);
	return r;
    }
    playposmut.Lock();
    for (;;)
    {
	decposmut.Lock();
	//cout << "P " << m_iPlaypos << "  D " << m_iDecpos << endl;
	// when disabled - skip frame if we don't have a room for new one
	// this is better then discarding the whole frame
	// as we could safely continue after pause in this case
	if ((m_iDecpos >= VBUFSIZE - 1) && !m_bEnabled && m_iPlaypos < 1)
	    m_iPlaypos++;

	while (m_iPlaypos > 0)
	{
	    frame* f = vbuf[0];
	    for (int i = 1; i < VBUFSIZE; i++)
		vbuf[i - 1] = vbuf[i];
	    vbuf[VBUFSIZE - 1] = f;

	    m_iPlaypos--;
	    if (m_iDecpos > 0)
		m_iDecpos--;
	}

	if (m_bFilling && (m_iDecpos - m_iPlaypos) >= QMARKLO)
	    m_bFilling = false;

	// one frame has to be left untouched in buffer
	// as this frames is being currently displayed
	// and we use the same memory all the time
	if (m_iDecpos < VBUFSIZE - 1)
	    break;  // note: we hold decposmut here

	decposmut.Unlock();
	playposcond.Wait(playposmut, 0.1);
    }
    playposmut.Unlock();

    bool lockedRenderer = false;
    CImage* im = vbuf[m_iDecpos]->getData();
    if (0 && im->GetUserData())
    {
	if (m_pVideoRenderer)
	{
	    m_pVideoRenderer->Lock();
	    lockedRenderer = true;
	}
        else cout << "DIRECT USER DATA & NO RENDERER ???" << endl;
    }

    //cout << "IMDATA " << im->GetUserData() << endl;
    if (m_bLastNeeded && m_pFrame->getData()
	&& im->Data() != m_pFrame->getData()->Data())
	// in case decoder doesn't remember last frame itself current one
	memcpy(im->Data(), m_pFrame->getData()->Data(), m_pFrame->getData()->Bytes());

    if (im)
	// increase ref counter -> noone could remove this image
        // and we don't have to hold decpos mutex
	im->AddRef();

    vbuf[m_iDecpos]->setTime(ftime);
    vbuf[m_iDecpos]->setPos(fpos);
    int lastdecpos = m_iDecpos;
    decposmut.Unlock();

    // this work as a barrier - it will not start new decoding while
    // some large memory transfer is in progress
    // but won't block any new transfer
    memory_lock();
    memory_unlock();

    CImage* ci = im;
    if (ci && m_pDecoderImage)
	ci = m_pDecoderImage;

    int hr = DecodeInternal(src, size, is_keyframe, ci);

    if (ci == m_pDecoderImage)
        im->Convert(ci);

    //memory_unlock();

    m_pFrame->setData(im); // replace previously remembered image

    if (lockedRenderer)
	m_pVideoRenderer->Unlock();

    // get decpos mutex again
    // we release this lock so if something bad happens inside
    // DLL codecs we could still continue as long as noone else needs
    // decoder mutex
    decposmut.Lock();

    // FIXME: but STOP/START might crash this
    // we make this simple check
    if (lastdecpos == m_iDecpos)
    {
	vbuf[m_iDecpos]->setDataQuality(m_fQuality);
	vbuf[m_iDecpos]->setDrop(false);

	if (m_iDecpos <= DMARKLO && !m_bFilling)
	{
	    srand (rand() / (m_iDecpos + 1));
	    // randomly sometimes image could go though... smart idea

	    // however this alghorithm doesn't seems to do
	    // a good job with empty cache

	    // another bigger problem is - this alghorithm is probably
	    // useless here - as dropping frames is more depended on
	    // the current timing of the player - situation might change
	    // with next frames rapidly

	    double prob = rand() / (RAND_MAX + 1.0);
	    //cout << "prob " << prob << " m_iDecpos " << m_iDecpos << " DMARKLO " << DMARKLO << endl;
	    if (prob < 0.1 + (DMARKLO - m_iDecpos) / (double) DMARKLO)
		vbuf[m_iDecpos]->setDrop(true);
	}

	m_iDecpos++;
    }
    decposcond.Broadcast();
    decposmut.Unlock();

    return hr;
}

// default action
void IVideoDecoder::Restart()
{
    if (m_State == START)
    {
	Stop();
	Start();
    }
}

int IVideoDecoder::SetDirectMemoryAccess(VideoRenderer* vr)
{
    int r = (vr == 0) ? 1 : -1;

    //cout << "MEM1 " << (void*)m_pVideoRenderer << "  " << (void*) vr << endl;
    if (m_pVideoRenderer != vr)
    {
	if (vbuf || m_Mode == DIRECT)
	{
	    IVideoDecoder::Stop();
	    m_pVideoRenderer = vr;
	    IVideoDecoder::Start();
	}
	else
	    m_pVideoRenderer = vr;

	// check if we have succesfully set DirectMode
	if ((vbuf && vbuf[VBUFSIZE - 1]->getData()->GetUserData())
	    || (!vbuf && m_pFrame->getData()->GetUserData()))
	    r = 0;
	else
            m_pVideoRenderer = 0;
	//cout << "SETTING DIRECT " << (void*) vr << "  " << r << endl;
    }

    return r;
}

IVideoDecoder::DecodingMode IVideoDecoder::GetDecodingMode()
{
    return m_Mode;
}

int IVideoDecoder::SetDecodingMode(DecodingMode mode)
{
    // we are calling non-virtually from this class
    // this is perfectly legal and we don't have to
    // restart codecs as only internal handling of
    // images have changed!!!
    if (mode != m_Mode)
    {
	//Locker locker(decoding);

	// check if we are running
	bool reinit = (m_pFrame != 0 || vbuf != 0);
	//cout << "SetRealtime:" << reinit << "  rt:" << rt << endl;

	if ((m_Mode != DIRECT)
	    && (mode == REALTIME_QUALITY_AUTO || mode == REALTIME))
	    // we don't have to reinitialize buffer in this case
	    reinit = false;

        //cout << "SETTING MODE " << mode << endl;
	if (reinit)
	{
	    IVideoDecoder::Stop();
	}

	switch (mode)
	{
	case DIRECT:
	case REALTIME:
	case REALTIME_QUALITY_AUTO:
	    m_Mode = mode;
	    //cout << "Setting mode " << mode << endl;
            break;
	default:
	    //cerr << "IVideoDecoder::SetDecodingMode() - Unknown mode: " << mode << endl;
            ;
	}

	if (reinit)
	    IVideoDecoder::Start();
    }

    return 0;
}

int IVideoDecoder::SetDirection(int d)
{
    m_obh.biHeight = d ? m_bh->biHeight : -m_bh->biHeight;

    return 0;
}

void IVideoDecoder::Start()
{
    Locker locker(decoding);
    if (m_State == START)
        return; // already started

    if (m_pDecoderImage)
    {
	m_pDecoderImage->Release();
        m_pDecoderImage = 0;
    }

    if (!m_pFrame)
	m_pFrame = new frame();

    CImage* ci = (m_pVideoRenderer) ? m_pVideoRenderer->GetData(0) : 0;

    if (!ci)
	ci = new CImage(&m_decoder);
    //else cout << "START DIRECTMEM " << (void*) ci << endl;

    if (!ci->IsFmt(&m_decoder))
	m_pDecoderImage = new CImage(&m_decoder);

    //cout << "START " << (void*) ci->GetUserData() << "  " << (void*) ci->Data()
    //    << "  userdata " << (void*) m_pVideoRenderer << endl;
    m_pFrame->setData(ci);

    //cout << "CIx " << ci->GetUserData() << endl;
    if (m_Mode != DIRECT)
    {

	/*if (!compat && m_pVideoRenderer)
          */
	// m_pOurOutput->SetFramePointer((char **)m_outFrame->Getaddr());
	// new fresh memory block
	if (!vbuf)
	{
	    vbuf = new (frame*)[VBUFSIZE];
            bool dirok = true;
	    for (int i = 0; i < VBUFSIZE; i++)
	    {
                //cout << "BUFFER " << i << endl;
		vbuf[i] = new frame();
		if (dirok && m_pVideoRenderer)
		{
		    //cout << "BUFFER " << i << endl;
		    ci = m_pVideoRenderer->GetData(i);
		    if (!ci)
		    {
			cout << "Sorry Direct Mode is not available for "
			    "non YUV in buffered mode!" << endl;
			// replace all currently existing buffers
			for (int j = 0; j < i; j++)
			    vbuf[j]->setData(new CImage(&m_decoder));
			dirok = false;
                        ci = new CImage(&m_decoder);
		    }
		}
		else
		    ci = new CImage(&m_decoder);
		vbuf[i]->setData(ci);
		//cout << "CIx " << ci->GetUserData() << "  " << i << endl;
	    }
	}
	m_bFilling = true;
    }

    StartInternal();

    m_State = START;
    //cout << "DECODER START" << endl;
}

// common code - should be called at the end of child's stop method
// how about using virtual method StopCodec
void IVideoDecoder::Stop()
{
    Locker locker(decoding);

    if (m_State == STOP)
        return; // already stopped

    //cout << "DECODER STOP" << endl;
    m_State = STOP;

    StopInternal();

    if (m_Mode != DIRECT)
    {
	FlushCache();
	//cout << "stop1" << endl;
	if (vbuf)
	{
	    for (int i = 0; i < VBUFSIZE; i++)
	    {
		delete vbuf[i];
		vbuf[i] = 0;
	    }
	    delete[] vbuf;
	    vbuf = 0;
	}
    }
}

void IVideoDecoder::SetEnabled(bool enabled)
{
    playposmut.Lock();
    decposmut.Lock();
    m_bEnabled = enabled;
    decposcond.Broadcast();
    playposcond.Broadcast();
    decposmut.Unlock();
    playposmut.Unlock();
}
