#define USE_311_DECODER
#include <encore2.h>
#include <decore.h>

#include <videodecoder.h>
#include <videoencoder.h>
#include <fourcc.h>
#include <configfile.h>
#include <plugin.h>
#include <Locker.h>

extern "C" void clear_timer();
extern "C" void clear_etimer();
extern "C" void write_timer();
extern "C" void write_etimer();

using namespace std;

#define MAX_QUALITY 6

struct timer_holder
{
    timer_holder();
    ~timer_holder();
};
static timer_holder th;
timer_holder::timer_holder()
{
    clear_timer();
    clear_etimer();
}
timer_holder::~timer_holder()
{
    write_timer();
    write_etimer();
}
string error;
extern "C" const double frequency=700000.;
extern "C" int quiet_encore;
#include <stdio.h>

class DIVX_VideoDecoder: public IVideoDecoder, public IRtConfig
{
    int m_iState;
    int m_iLastPPMode;
    bool m_bCompat311;
    PthreadMutex m_Pm;
public:
    DIVX_VideoDecoder(const CodecInfo& info, const BITMAPINFOHEADER& bh, int flip)
	:IVideoDecoder(info,bh), m_iState(0), m_iLastPPMode(0)
    {
	switch(info.fourcc)
	{
#ifdef USE_311_DECODER
	case mmioFOURCC('d', 'i', 'v', '3'):
	case mmioFOURCC('D', 'I', 'V', '3'):
	case mmioFOURCC('d', 'i', 'v', '4'):
	case mmioFOURCC('D', 'I', 'V', '4'):
	case fccMP41:
	case fccMP43:
	    m_bCompat311=true;
	    break;
#endif
	default:
	    m_bCompat311=false;
	    break;
	}
//	m_bh=bh;
	m_obh=bh;
	if(!flip)
	    m_obh.biHeight*=-1;
	m_obh.SetBits(24);
	m_decoder=BitmapInfo(m_obh.biWidth, m_obh.biHeight, 24);
    }
    virtual ~DIVX_VideoDecoder()
    {
	Stop();
    }
    virtual void StartInternal()
    {
	DEC_PARAM param;
	int csp=m_obh.biCompression;
//	printf("csp: 0x%x bitcount: %d height: %d\n", csp, m_obh.biBitCount, m_obh.biHeight);
	if(csp)
	switch(csp)
	{
	case fccYV12:
	    param.output_format=DEC_YV12;
	    m_decoder.biHeight=m_obh.biHeight;
	    break;
	case fccYUY2:
	    param.output_format=DEC_YUV2;
	    m_decoder.biHeight=m_obh.biHeight;
	    break;
	case 3:
	    param.output_format=(m_obh.biHeight>0)?DEC_RGB555:DEC_RGB555_INV;
	    m_decoder.biHeight=m_obh.biHeight;
	default:
	    return;
	}
	else
	{
	switch(m_obh.biBitCount)
	{
	case 15:
	    param.output_format=(m_obh.biHeight>0)?DEC_RGB555:DEC_RGB555_INV;
	    break;
	case 16:
	    param.output_format=(m_obh.biHeight>0)?DEC_RGB565:DEC_RGB565_INV;
	    break;
	case 24:
	    param.output_format=(m_obh.biHeight>0)?DEC_RGB24:DEC_RGB24_INV;
	    break;
	case 32:
	    param.output_format=(m_obh.biHeight>0)?DEC_RGB32:DEC_RGB32_INV;
	    break;
	default:
	    return;
	}
	m_decoder.biHeight=m_obh.biHeight;
	}
	param.x_dim=m_obh.biWidth;
	param.y_dim=abs(m_obh.biHeight);
	param.time_incr = 15;
	// DEC_MEM_REQS dmr;
	// decore((unsigned long)this, DEC_OPT_MEMORY_REQS, &dmr, 0);
	memset(&param.buffers, 0, sizeof(param.buffers));
	decore((unsigned long)this, DEC_OPT_INIT, &param, 0);
        m_iState = 1;
	DEC_SET set;
	set.postproc_level=Registry::ReadInt("opendivx", "Post-processing", 0)*10;
	if(decore((unsigned long)this, DEC_OPT_SETPP, &set, 0)==DEC_OK)
	    m_iLastPPMode=set.postproc_level;
    }
    virtual CAPS GetCapabilities() const
    {
	return CAPS(CAP_YV12|CAP_YUY2);
    }
    virtual void StopInternal()
    {
	Locker locker(m_Pm);
	if(!m_iState)return;
	// FIXME - is it usable without MEMORY_REQS ???
	decore((unsigned long)this, DEC_OPT_RELEASE, 0, 0);
        m_iState = 0;
    }
    virtual int DecodeInternal(void* src, unsigned int size, int is_keyframe, CImage* pImage)
    {
	{
	    Locker locker(m_Pm);
	    if(!size || !m_iState)
		return 0;
	    DEC_FRAME param;
	    param.bitstream=src;
	    unsigned char* pc[3];
	    param.bmp=pImage?pImage->Data():0;
	    param.length=size;
	    param.render_flag=pImage?1:0;
	    param.stride=pImage?pImage->Width():0;

#ifdef USE_311_DECODER
	    decore((unsigned long)this, m_bCompat311?DEC_OPT_FRAME_311:DEC_OPT_FRAME, &param, 0);
#else
	    decore((unsigned long)this, DEC_OPT_FRAME, &param, 0);
#endif
	    m_fQuality = (float) m_iLastPPMode / MAX_QUALITY;
	}
	if (m_Mode == IVideoDecoder::REALTIME_QUALITY_AUTO)
	{
	    //cout << m_iLastPPMode << endl;
	    // adjust Quality - depends on how many cached frames we have
	    int buffered = m_iDecpos - m_iPlaypos;

	    //cout << "qual " << q << "  " << buffered << endl;
	    if (buffered < (m_iLastPPMode + QMARKLO - 1)
		|| buffered > ((m_iLastPPMode + 1) + QMARKLO))
	    {
		// removed old code which was present here
		// and replaced with this new uptodate one

		int to = (buffered - QMARKLO);
		if (to < 0)
		    to = 0;
		else if (to > MAX_QUALITY)
		    to = MAX_QUALITY;
		if (m_iLastPPMode != to)
		{
                    SetValue("Post-processing", to);
#ifndef QUIET
		    //cout << "Switching quality " << m_iLastPPMode <<  " -> " << to << "  b:" << buffered << endl;
#endif
		}
	    }
	}
        return 0;
    }
    virtual int SetDestFmt(int bits = 24, fourcc_t csp = 0)
    {
	DEC_PARAM param;
	m_Pm.Lock();
	if(csp)
	switch(csp)
	{
	case fccYV12:
	    param.output_format=DEC_YV12;
	    break;
	case fccYUY2:
	    param.output_format=DEC_YUV2;
	    break;
	default:
	    return -1;
	}
	else
	switch(bits)
	{
	case 15:
	    param.output_format=DEC_RGB555_INV;
	    break;
	case 16:
	    param.output_format=DEC_RGB565_INV;
	    break;
	case 24:
	    param.output_format=DEC_RGB24_INV;
	    break;
	case 32:
	    param.output_format=DEC_RGB32_INV;
	    break;
	default:
	    m_Pm.Unlock();
	    return -1;
	}
//	if(m_iState)
	decore((unsigned long)this, DEC_OPT_SETOUT, &param, 0);
	if(csp)
	{
	    m_obh.SetSpace(csp);
	    m_decoder.SetSpace(csp);
	}
	else
	{
	    m_obh.SetBits(bits);
	    m_decoder.SetBits(bits);
	}
	m_Pm.Unlock();
	if (m_iState)
	    Restart();
	return 0;
    }
    virtual HRESULT GetValue(const char* name, int& value)
    {
	if (strcmp(name, "Post-processing"))
	    return -1;
	value=m_iLastPPMode;
	return 0;
    }
    virtual HRESULT SetValue(const char* name, int value)
    {
	if (strcmp(name, "Post-processing"))
	    return -1;
	DEC_SET set;
	Locker locker(m_Pm);
	set.postproc_level=value*10;
	if(decore((unsigned long)this, DEC_OPT_SETPP, &set, 0)==DEC_OK)
	{
	    m_iLastPPMode=value;
	    return 0;
	}
	return -1;
    }
};

class DIVX_VideoEncoder: public IVideoEncoder
{
    BITMAPINFOHEADER m_bh;
    BITMAPINFOHEADER m_obh;
    int m_iState;
    void* m_pHandle;
    bool m_bRtMode;
    int m_iQuant;
public:
    DIVX_VideoEncoder(const CodecInfo& info, int compressor, const BITMAPINFOHEADER& bh)
	:IVideoEncoder(info), m_bh(bh), m_obh(bh), m_iState(0), m_bRtMode(false)
    {
	if((m_bh.biCompression==0) && (m_bh.biBitCount!=24))error="Unsupported input bit depth";
	switch(m_bh.biCompression)
	{
	case 0:
	case fccYUY2:
	case fccYV12:
	    break;
	default:
	    error="Unsupported input format";
	    break;
	}
	m_obh.biCompression=mmioFOURCC('D', 'I', 'V', 'X');
	m_obh.biHeight=abs(m_obh.biHeight);
	quiet_encore=1;
    }
    virtual ~DIVX_VideoEncoder(){}
    virtual int SetQuality(int quality)
    {
	Registry::WriteInt("opendivx", "quality2", quality);
	return 0;
    }
    virtual int GetQuality()
    {
	return Registry::ReadInt("opendivx", "quality2", 8500);

    }
    virtual void Start()
    {
	ENC_PARAM param;
	memset(&param, 0, sizeof(param));
	param.x_dim=m_bh.biWidth;
	param.y_dim=abs(m_bh.biHeight);
	param.framerate=30;//frames/sec
	param.bitrate=Registry::ReadInt("opendivx", "BitRate", 800000);;//bits/sec
	param.rc_period=Registry::ReadInt("opendivx", "rc_period", 200);
	param.max_quantizer=Registry::ReadInt("opendivx", "max_quantizer", 16);//just guess
	param.min_quantizer=Registry::ReadInt("opendivx", "min_quantizer", 1);
	param.quality=Registry::ReadInt("opendivx", "quality", 0);
        param.use_bidirect=0;
        param.deinterlace=0;
	if(param.quality==1)
	{
	    m_bRtMode=true;
	    int quality=GetQuality();
	    m_iQuant=int(1+.003*(10000-quality));
	    if(m_iQuant>31)
		m_iQuant=31;
	    if(m_iQuant<1)
		m_iQuant=1;
	}
	else
	    m_bRtMode=false;
//	param.flip=1;
	encore(0, ENC_OPT_INIT, &param, 0);
	m_pHandle=param.handle;
	m_iState=1;
    }
    virtual void Stop()
    {
	if(!m_iState)return;
	encore(m_pHandle, ENC_OPT_RELEASE, 0, 0);
	m_iState=0;
	m_pHandle=0;
    }
    virtual int QueryOutputSize() const
    {
	return abs(m_bh.biWidth*m_bh.biHeight)*4;
    }
    virtual const BITMAPINFOHEADER& QueryOutputFormat() const
    {
	return m_obh;
    }
    virtual int EncodeFrame(CImage* sRc, void* dest, int* is_keyframe, size_t* size, int* lpckid=0)
    {
	ENC_FRAME param;
	ENC_RESULT result;
	param.bitstream=dest;
	switch(m_bh.biCompression)
	{
	case 0:
	    param.colorspace=ENC_CSP_RGB24;
	    break;
	case fccYUY2:
	    param.colorspace=ENC_CSP_YUY2;
	    break;
	case fccYV12:
	    param.colorspace=ENC_CSP_I420;
	    break;
	}
	param.image=sRc->Data();
	param.mvs=0;
	if(m_bRtMode)
	{
	    param.quant=m_iQuant;
	    param.intra=-1;
	    encore(m_pHandle, ENC_OPT_ENCODE_VBR, &param, &result);
	}
	else
	    encore(m_pHandle, ENC_OPT_ENCODE, &param, &result);
	if(is_keyframe)*is_keyframe=result.is_key_frame?16:0;
	if(size)*size=param.length;
	return 0;
    }
};

static int div3_codecs[]=
{
    mmioFOURCC('d', 'i', 'v', '3'),
    mmioFOURCC('D', 'I', 'V', '3'),
    mmioFOURCC('d', 'i', 'v', '4'),
    mmioFOURCC('D', 'I', 'V', '4'),
    fccMP41, /* untested, may work */
    fccMP43,
    0
};
static int divx_codecs[]=
{
    mmioFOURCC('D', 'I', 'V', 'X'),
    mmioFOURCC('d', 'i', 'v', 'x'),
    mmioFOURCC('d', 'v', 'x', '1'),
    4,
    0
};
static char divx_about[]=
"DivX 4.0";
static vector<CodecInfo> _plugin_props;

extern "C" const vector<CodecInfo>& RegisterPlugin()
{
    if(!_plugin_props.size())
    {
        vector<AttributeInfo> ds;
	ds.push_back(AttributeInfo("Post-processing",
	 "Image post-processing mode ( 0-6, 6 slowest )",
	     AttributeInfo::Integer, 0, 6));
	vector<AttributeInfo> vs;
	vs.push_back(AttributeInfo("BitRate",
	 "Desired stream bit-rate in bits/second", AttributeInfo::Integer));
	vs.push_back(AttributeInfo("rc_period",
	 "Unknown", AttributeInfo::Integer));
	vs.push_back(AttributeInfo("min_quantizer",
	 "Unknown", AttributeInfo::Integer));
	vs.push_back(AttributeInfo("max_quantizer",
	 "Unknown", AttributeInfo::Integer));
	vs.push_back(AttributeInfo("quality",
	 "Performance/quality balance", AttributeInfo::Integer));
	_plugin_props.push_back(
	CodecInfo(divx_codecs, "DivX 4.0", "", divx_about,
	    CodecInfo::Plugin, CodecInfo::Video, CodecInfo::Both, 0,
		vs, ds));
#ifdef USE_311_DECODER
	_plugin_props.push_back(
	CodecInfo(div3_codecs, "OpenDivX 3.11 compatible decoder", "", divx_about,
	    CodecInfo::Plugin, CodecInfo::Video, CodecInfo::Decode, 0,
		vs, ds));
#endif
    }
    return _plugin_props;
}
extern "C" IVideoEncoder* CreateVideoEncoder(const CodecInfo& info, fourcc_t compressor, const BITMAPINFOHEADER& bh)
{
    error="";
    IVideoEncoder* result=new DIVX_VideoEncoder(info, compressor, bh);
    if(error.size()){delete result; return 0;}
    return result;
}
extern "C" IVideoDecoder* CreateVideoDecoder(const CodecInfo& info, const BITMAPINFOHEADER& bh, int flip)
{
    error="";
    IVideoDecoder* result=new DIVX_VideoDecoder(info, bh, flip);
    if(error.size()){delete result; return 0;}
    return result;
}
extern "C" string GetError(){return error;}
extern "C" int GetPluginVersion(){return PLUGIN_API_VERSION;}

extern "C" int GetAttrInt(const CodecInfo& info, const char* attribute, int& value)
{
    if(strcmp(attribute, "Post-processing")==0)
	value=Registry::ReadInt("opendivx", "Post-processing", 0);
    else
    if(strcmp(attribute, "BitRate")==0)
	value=Registry::ReadInt("opendivx", "BitRate", 800000);
    else
    if(strcmp(attribute, "rc_period")==0)
	value=Registry::ReadInt("opendivx", "rc_period", 200);
    else
    if(strcmp(attribute, "min_quantizer")==0)
	value=Registry::ReadInt("opendivx", "min_quantizer", 1);
    else
    if(strcmp(attribute, "max_quantizer")==0)
	value=Registry::ReadInt("opendivx", "max_quantizer", 16);
    else
    if(strcmp(attribute, "quality")==0)
	value=Registry::ReadInt("opendivx", "quality", 0);
    else return -1;
    return 0;
}

extern "C" int SetAttrInt(const CodecInfo& info, const char* attribute, int value)
{
    int retval=-1;
    if(strcmp(attribute, "Post-processing")==0)
	retval=Registry::WriteInt("opendivx", "Post-processing", value);
    else
    if(strcmp(attribute, "BitRate")==0)
	retval=Registry::WriteInt("opendivx", "BitRate", value);
    else
    if(strcmp(attribute, "rc_period")==0)
	retval=Registry::WriteInt("opendivx", "rc_period", value);
    else
    if(strcmp(attribute, "min_quantizer")==0)
	retval=Registry::WriteInt("opendivx", "min_quantizer", value);
    else
    if(strcmp(attribute, "max_quantizer")==0)
	retval=Registry::WriteInt("opendivx", "max_quantizer", value);
    else
    if(strcmp(attribute, "quality")==0)
	retval=Registry::WriteInt("opendivx", "quality", value);
    else retval=-1;
    return retval;
}
