
#include <videodecoder.h>
#include <videoencoder.h>
#include <cpuinfo.h>
#include <creators.h>

#include <unistd.h>
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;
using namespace Creators;

#include <qapp.h>
#include "recompressor.h"
#include "filters.h"
#include "conf.h"

#define Debug if(0)
#define __MODULE__ "Recompress filter"

/*************************
    Work with filters.
 *************************/
RecompressFilter::RecompressFilter(const VideoEncoderInfo& in)
    :info(in)
{
    ve = 0;
    vd = 0;
    comp_frame = 0;
    info.header.biHeight = -1;
    //lazy init - in process
}

RecompressFilter::~RecompressFilter()
{
    Debug cout << "RecompressFilter::~RecompressFilter()" << endl;
    FreeVideoEncoder(ve);
    FreeVideoDecoder(vd);
    delete comp_frame;
}

CImage* RecompressFilter::process(CImage* im, int pos)
{
    BITMAPINFOHEADER bm(*(im->GetFmt()));
    bm.biHeight = abs(bm.biHeight);
    if (bm.biHeight != info.header.biHeight
	|| bm.biWidth != info.header.biWidth
	|| bm.biBitCount != info.header.biBitCount)
    {
	try
	{
	    info.header = bm;
	    BitmapInfo bh(bm);
	    bh.Print();
	    BitmapInfo bh1(info.header);
	    bh1.Print();
	    printf("RecompressFilter: Decompress: 0x%x  \"%.4s\"  Compressor: 0x%x \"%.4s\" %s\n",
		   info.header.biCompression, (char*)&info.header.biCompression,
		   info.compressor, (char*)&info.compressor, info.cname.c_str());

	    FreeVideoEncoder(ve);
	    ve = CreateVideoEncoder(info);
            cout << "Encoder created" << endl;
	    if (!ve)
		throw FATAL(GetError().c_str());
	    ve->SetKeyFrame(1);

	    FreeVideoDecoder(vd);
	    const BITMAPINFOHEADER& obh = ve->QueryOutputFormat();
	    vd = CreateVideoDecoder(obh, 24, 1);
	    if (!vd)
		throw FATAL(GetError().c_str());
	    delete comp_frame;
	    comp_frame = new uint8_t[abs(obh.biWidth * obh.biHeight) * 4];
        }
        catch(FatalError& e)
        {
	    e.PrintAll();
	    FreeVideoDecoder(vd);
	    FreeVideoEncoder(ve);
	    delete comp_frame;
	    vd = 0;
	    ve = 0;
	    comp_frame = 0;
	    throw;
	}
    }

    Debug cout << "RecompressFilter::process() start" << endl;

    im->AddRef();
    ve->Start();
    int is_keyframe;
    size_t size;
    int result = ve->EncodeFrame(im, comp_frame, &is_keyframe, &size);
    //ve->Stop();
    cerr << "Encoded to " << size << " bytes, error " << result << endl;
    vd->Start();
    result = vd->DecodeFrame(comp_frame, size, 0, 0, 16);
    if (result != 0)
	cerr << "Decoded result: " << result << endl;
    CImage* tmp = vd->GetFrame();
    //memcpy(im->Data(), tmp->Data(), im->Bytes());
    im->Convert(tmp);
    tmp->Release();
    //vd->Stop(); //currently causes crash of the player
    Debug cout << "RecompressFilter::process() stop" << endl;
    return im;
    //return tmp;
}

string RecompressFilter::save()
{
    return string("");
}

void RecompressFilter::load(string)
{
}

//creates filter registered under id
RecKernel::RecKernel()
:_cb(0), _ctl(0), _rf(0), _wf(0), _recf(0), vs(0), as(0), rec_status(0), pause_status(0)
{
}

RecKernel::~RecKernel()
{
    delete _recf;
    _destruct();
}

Filter* RecKernel::getFilter(streamid_t id)
{
    switch(id)
    {
    case 0:
	return new GammaFilter(this);
    case 1:
	return new BlurFilter(this);
    case 2:
	return new RefractFilter(this);
    case 3:
	return new NoiseFilter(this);
    case 4:
	return new MoveFilter(this);
    case 5:
	return new ScaleFilter(this);
/*    case 5:
	return new SwapFilter(this);*/
    default:
	return 0;
    }

}

//returns filter at pos id in current list
Filter* RecKernel::getFilterAt(streamid_t id)
{
    if (id >= filter_list.size())
	return 0;
    Filter* fi = filter_list[id];
    fi->addref();

    return fi;
}

//synonym
Filter* RecKernel::operator[](streamid_t id)
{
    return getFilterAt(id);
}

//size of current list
unsigned int RecKernel::getFilterCount() const
{
    return filter_list.size();
}

//adds filter in the end of list
int RecKernel::addFilter(Filter* fi)
{
    filter_list.push_back(fi);
    fi->addref();
    redraw();
    return 0;
}

int RecKernel::removeFilter(streamid_t id)
{
    if (id >= filter_list.size())
    {
	cout << "removeFilter: weird id " << id << endl;
        return 0;
    }
    else if (!filter_list.size())
	return 0;

    Filter* fi = filter_list[id];
    swap(filter_list[id], filter_list[filter_list.size() - 1]);
    filter_list.pop_back();
    delete fi;
    redraw();
    return 0;
}

int RecKernel::moveUpFilter(streamid_t id)
{
    if (id >= filter_list.size())
	return 0;
    swap(filter_list[id], filter_list[id - 1]);

    return 0;
}

int RecKernel::moveDownFilter(streamid_t id)
{
    if (filter_list.size() < 1 || id >= (filter_list.size()-1))
	return 0;
    swap(filter_list[id], filter_list[id + 1]);

    return 0;
}

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

	Work with files.

********************************/
int RecKernel::openFile(const char* fn)
{
    if (_rf)
	_destruct();
    try
    {
	_rf=CreateIAviReadFile(fn);
	_readfn=fn;
	int i;
	vs = as = 0;
	for(i = 0; i < 16; i++)
	{
	    IAviReadStream* s = _rf->GetStream(i, AviStream::Video);
	    if (!s)
		break;
	    /**
		IV32 has serious problems without this line
		Since we can draw upside-down pictures, who cares.
	    **/
	    s->SetDirection(true);
	    full_stream fs;
	    fs.stream = s;
	    fs.startpos = 0;
	    fs.endpos = s->GetLength();
	    fs.mode = Copy;
	    v_str.push_back(fs);

	    vs++;

	    VideoEncoderInfo vi;
	    BITMAPINFOHEADER bh;
	    s->GetVideoFormatInfo(&bh, sizeof(bh));
	    vi.compressor = bh.biCompression;
	    AVIStreamHeader h;
	    s->GetHeader(&h, sizeof(h));
	    if(h.dwQuality >= 0)
		vi.quality = h.dwQuality;
	    else
    		vi.quality = 9500;
	    vi.keyfreq = 15;
	    vfmt.push_back(vi);
	}
	for (i = 0; i < 16; i++)
	{
	    IAviReadStream* s=_rf->GetStream(i, AviStream::Audio);
	    if (!s)
		break;
	    full_stream fs;
	    fs.stream = s;
	    fs.startpos = 0;
	    fs.endpos = s->GetLength();
	    fs.mode = Copy;
	    v_str.push_back(fs);
	    as++;

	    AudioEncoderInfo ai;
	    WAVEFORMATEX wf;
	    s->GetAudioFormatInfo(&wf, 0);
	    if (wf.wFormatTag == 0x55)
	    {
		ai.fmt = 0x55;
		ai.bitrate=wf.nAvgBytesPerSec;
	    }
	    else
		ai.fmt=1;
	    afmt.push_back(ai);
	}
	return 0;
    }
    catch (FatalError& e)
    {
	strLastError = e.GetModule() + string(": ") + e.GetDesc();
	return -1;
    }
}

int RecKernel::setDestFile(const char* fn)
{
    IAviWriteFile* wf;
    try
    {
	wf = CreateSegmentedFile(fn);
	_fn = fn;
	delete wf;
        wf = 0;
	return 0;
    }
    catch (FatalError& e)
    {
	e.Print();
	return -1;
    }
}

//first all video streams, then audio
string RecKernel::aboutStream(streamid_t id) const
{
    if (!isStream(id))
	return "";
    char s[1024];
    IAviReadStream* stream = v_str[id].stream;
    if (isAudioStream(id))
    {
	WAVEFORMATEX wf;
	stream->GetAudioFormatInfo(&wf, 0);
//	char* enc;
//        char dummy[128];
	const char* enc;
        char dummy[128];
	const CodecInfo* info = CodecInfo::match(wf.wFormatTag, CodecInfo::Audio);
	if (info)
	    enc = info->text.c_str();
	else
	{
	    sprintf(dummy, "ID %d ( unsupported) ", wf.wFormatTag);
	    enc = dummy;
	}
	AVIStreamHeader hdr;
	stream->GetHeader(&hdr, sizeof(hdr));
	sprintf(s, "Audio stream #%d. Length: %d samples ( %.2f seconds ).\n"
		"Encoding: %s, %d bytes/s. Sample size %d bytes",
		(int)id-vs, (int)stream->GetLength(), stream->GetLengthTime(),
		enc, hdr.dwRate, hdr.dwScale);
	return string(s);
    }
    if (isVideoStream(id))
    {
	BITMAPINFOHEADER bh;
	AVIStreamHeader hdr;
	stream->GetHeader(&hdr, sizeof(hdr));
	stream->GetVideoFormatInfo(&bh, sizeof(bh));
	const CodecInfo* ci=CodecInfo::match(bh.biCompression);
	string encoding;
	if (ci)
	    encoding = ci->text;
	else
	{
	    char dummy[128];
	    sprintf(dummy, "unknown %X='%.4s'", bh.biCompression,
		    (char *)bh.biCompression);
	    encoding = dummy;
	}
	sprintf(s, "Video stream #%d. %dx%dx%.2f fps. \n"
		"Length: %d frames ( %.2f seconds ). Encoding: %s.",
		(int)id, bh.biWidth, bh.biHeight, 1./stream->GetFrameTime(),
		(int)stream->GetLength(), stream->GetLengthTime(),
		encoding.c_str());
    	return string(s);
    }
    return 0;
}

string RecKernel::lastError() const
{
    return strLastError;
}

bool RecKernel::isVideoStream(streamid_t id) const
{
    if (id < v_str.size())
	return (v_str[id].stream->GetType() == AviStream::Video);
    return false;
}
bool RecKernel::isAudioStream(streamid_t id) const
{
    if (id < v_str.size())
	return (v_str[id].stream->GetType() == AviStream::Audio);
    return false;
}

AviStream::StreamType RecKernel::stream_type(streamid_t id) const
{
    if (id < v_str.size())
	return v_str[id].stream->GetType();
    return AviStream::Other;
}

int RecKernel::getCompress(streamid_t id, VideoEncoderInfo& vi) const
{
    if (!isVideoStream(id))
	return -1;
    vi = vfmt[id];
    return v_str[id].stream->GetOutputFormat(&vi.header, sizeof vi.header);
}

int RecKernel::getAudioCompress(streamid_t id, AudioEncoderInfo& wf) const
{
    if (!isAudioStream(id))
	return -1;
    wf = afmt[id-vs];
    return 0;
}

int RecKernel::setCompress(streamid_t id, const VideoEncoderInfo& vi)
{
    if (!isVideoStream(id))
	return -1;
    vfmt[id] = vi;
    if (id == 0)
    {
	try
	{
	    VideoEncoderInfo info;
	    RecompressFilter* nrecf = (getCompress(0, info) != 0)
		? 0 : new RecompressFilter(info);
	    //cout << "***1****COMPRESSOR " << info.cname << endl;
            //cout << "***2****COMPRESSOR " << vi.cname << endl;
	    if (nrecf)
	    {
		delete _recf;
		_recf = nrecf;
	    }
	}
	catch(FatalError& e)
	{
	    e.Print();
	}
	redraw();
    }
    return 0;
}

int RecKernel::setAudioCompress(streamid_t id, const AudioEncoderInfo& wf)
{
    if (!isAudioStream(id))
	return -1;
    afmt[id - vs] = wf;
    return 0;
}

//nonzero if stream
bool RecKernel::isStream(streamid_t id) const
{
    return (id < v_str.size());
}

int RecKernel::setStreamMode(streamid_t id, KernelMode mode)
{
    if (!isStream(id))
	return -1;
    cout << "SETSTREAMMODE " << mode << endl;
    v_str[id].mode = mode;
    redraw();
    return 0;
}

int RecKernel::getStreamMode(streamid_t id, KernelMode& mode) const
{
    if (!isStream(id))
	return -1;
    mode = v_str[id].mode;
    return 0;
}

int RecKernel::getSelection(streamid_t id, double& start, double& end) const
{
    if (!isStream(id))
	return -1;
    start = v_str[id].startpos;
    end = v_str[id].endpos;
    return 0;
}

int RecKernel::setSelection(streamid_t id, double start, double end)
{
    if (!isStream(id))
	return -1;
    v_str[id].startpos = start;
    v_str[id].endpos = end;
    return 0;
}

int RecKernel::setSelectionStart(streamid_t id, double start)
{
    if (!isStream(id))
	return -1;
    v_str[id].startpos=start;
    return 0;
}

int RecKernel::setSelectionEnd(streamid_t id, double end)
{
    if (!isStream(id))
	return -1;
    v_str[id].endpos=end;
    return 0;
}

double RecKernel::getFrameTime(streamid_t id) const
{
    if (!isStream(id))
	return 0;
    return v_str[id].stream->GetFrameTime();
}

double RecKernel::getTime(streamid_t id) const
{
    if (!isStream(id))
	return 0;

    return v_str[id].stream->GetTime();
}

//should set size & pictures if ctl!=0 & file is opened
void RecKernel::setImageControl(IImageControl* ctl)
{
    if (vs)
    {
	try
	{
	    v_str[0].stream->StopStreaming();
	    v_str[0].stream->StartStreaming();
	    v_str[0].stream->Seek((framepos_t) 0);
	    v_str[0].stream->ReadFrame();
	    BITMAPINFOHEADER bh;
	    v_str[0].stream->GetVideoFormatInfo(&bh, sizeof(bh));
	    _ctl = ctl;
	    _ctl->setSize(bh.biWidth, bh.biHeight);

	    delete _recf;
    	    VideoEncoderInfo info;

	    _recf = (getCompress(0, info) != 0)
		? 0 : new RecompressFilter(info);
            //cout << "-----------------" << info.cname << endl;

	    redraw();
	}
	catch (FatalError& e)
	{
	    e.Print();
	}
    }
    return;
}

string RecKernel::getStreamName(streamid_t id) const
{
    if (!isStream(id))
	return "";
    string s;
    if (isAudioStream(id))
    {
	s = "Audio";
	id -= vs;
    }
    else
	s = "Video";
    char q[64];
    sprintf(q, " #%d", id);
    s += q;
    return s;
}

unsigned int RecKernel::getStreamCount() const
{
    return v_str.size();
}

//in frames
//negative seeks are very slow
// FIXME: implement frame caching : store frame every second

framepos_t RecKernel::seek(int delta)
{
    if (!vs)
	return 0;

    IAviReadStream* stream = v_str[0].stream;

    framepos_t cur_pos = stream->GetPos();
    framepos_t endpos = stream->GetLength();
    framepos_t newpos = cur_pos + delta;

    if (delta < 0 && cur_pos < (unsigned) -delta)
	newpos = 0;

    if (newpos >= endpos)
	newpos = endpos;

    if (newpos == cur_pos)
	return cur_pos;

    framepos_t next_kern = stream->GetPrevKeyFrame(newpos);

    //cout << "pos:" << cur_pos << " prev_keyfr:" << next_kern << " d:" << delta << " newpos:" << newpos << endl;

    if (delta < 0 || next_kern > cur_pos)
	stream->Seek(next_kern);

    framepos_t lpos, prevp = ~0U;
    for (;;)
    {
	lpos = stream->GetPos();
	//cout << "pos:" << pos << ", " << newpos << endl;
	if (lpos >= newpos || lpos == stream->ERR || prevp == lpos)
	    break;


	stream->ReadFrame();

	/*
	 FIXME would be cool to see this working
	CImage* pix = stream->GetFrame();
	if (!pix)
	{
	    cerr<<"ERROR: zero frame"<<endl;
	    return pos;
	}
	_ctl->setSourcePicture(pix);
	pix->Release();
        */
	// some error could happen - prevent deadlock loop
        // we are checking if the position is changing
        prevp = lpos;
    }
    redraw();
    return lpos;
}

//seek to nearest keyframe, return its position
framepos_t RecKernel::seek_pos(framepos_t pos)
{
    if (!vs)
	return pos;

    pos = v_str[0].stream->SeekToKeyFrame(pos);
    v_str[0].stream->ReadFrame();
    redraw();

    return v_str[0].stream->GetPos();
/*    if(v_str.size()==0)return pos;
    if(vs==0)
    {
	for(int i=0; i<v_str.size(); i++)
	    v_str[i].stream->Seek(pos);
	return pos;
    }
    else
    {
	pos=v_str[0].stream->SeekToKeyframe(pos);
	for(int i=1; i<v_str.size(); i++)
	    v_str[i].stream->Seek(pos);
	redraw();
	return pos;
    }
*/
}

framepos_t RecKernel::seekNextKeyFrame()
{
    if (!vs)
	return 0;

    IAviReadStream* stream = v_str[0].stream;
    framepos_t op = pos();

    //cout << "POS1 " << pos() << endl;
    if (stream->SeekToNextKeyFrame() != stream->ERR)
    {
	//cout << "POS-- " << pos() << endl;
	if (op != pos())
	    stream->ReadFrame();
	redraw();
    }
    return pos();
}

framepos_t RecKernel::seekPrevKeyFrame()
{
    if (!vs)
	return 0;

    IAviReadStream* stream = v_str[0].stream;
    framepos_t op = pos();

    if (stream->SeekToPrevKeyFrame() != stream->ERR)
    {
	if (op != pos())
	    stream->ReadFrame();
	redraw();
    }
    return pos();
}

framepos_t RecKernel::pos() const
{
    return (vs) ? v_str[0].stream->GetPos() : 0;
}

Unsigned RecKernel::getVideoLength() const
{
//    if(v_str.size()==0)return 0;
    if (!vs)
	return 0;
//    if(v_str[0].stream->GetType()!=AviStream::Video)
//	return 0;
    return (Unsigned) v_str[0].stream->GetLength();
}

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

	Actual recompression

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

int RecKernel::set_callback(IRecompressCallback* rec_cb)
{
    _cb=rec_cb;
    return 0;
}

void RecKernel::_destruct()
{
    //cout << "RecKernel::_destruct()" << endl;
    delete _rf;
    delete _wf;
    _rf = 0;
    _wf = 0;
    v_str.clear();
    vfmt.clear();
    afmt.clear();
}

void RecKernel::redraw()
{
    if (!vs || !_ctl)
	return;

//    IVideoDecoder* vd;
//    IVideoEncoder* ve;
//    BITMAPINFOHEADER bh, obh;
//    v_str[0].stream->GetOutputFormat(&bh, sizeof bh);
//    try
//    {
//    }
//    catch(...)
//    {
//    }
    //BITMAPINFOHEADER bh;
    //v_str[0].stream->GetVideoFormatInfo(&bh, sizeof(bh));
    CImage* pix = v_str[0].stream->GetFrame();

    if (!pix)
    {
	cerr<<"ERROR: zero frame"<<endl;
	return;
    }
    _ctl->setSourcePicture(pix);

    CImage* im = pix;

    if (_recf)
    {
	KernelMode m;
	if (getStreamMode(0, m) == 0)
	{
	    // show compressed image only when recompress is enabled
	    if (m == RecKernel::Recompress)
	    {
		im = new CImage(pix);
		CImage* new_im;
		for (unsigned i = 0; i < getFilterCount(); i++)
		{
		    Filter* fi = getFilterAt(i);
		    new_im = fi->process(im, v_str[0].stream->GetPos());
		    im->Release();
		    im = new_im;
		}

                if (!im->GetFmt()->IsRGB())
		    im->ToRGB();

		new_im = _recf->process(im, 0);
		if (new_im)
		{
		    im->Release();
		    im = new_im;
		}
	    }

	    if (!im->GetFmt()->IsRGB())
		im->ToRGB();
	}
    }

    _ctl->setDestPicture(im);
    im->Release();
    if (pix != im)
	pix->Release();
}

int RecKernel::loadConfig(const char* fn)
{
    ReadConfig cfg(fn);
//    cfg.Add("SrcFile", _readfn);
    string src=cfg.String("SrcFile");
    openFile(src.c_str());
    string dest=cfg.String("DestFile");
    setDestFile(dest.c_str());

//    cfg.Add("VideoStreams", vs);
//    cfg.Add("AudioStreams", as);
    char s[128];
    for (unsigned i=0; i<v_str.size(); i++)
    {
	sprintf(s, "Stream %d start", i);
	v_str[i].startpos=cfg.Double(s);
	sprintf(s, "Stream %d end", i);
	v_str[i].endpos=cfg.Double(s);
	sprintf(s, "Stream %d mode", i);
	v_str[i].mode=(KernelMode)cfg.Int(s);
        cout << "LOADCONFIG " << v_str[i].mode << endl;
	if (i < vs)
	{
	    sprintf(s, "Stream %d quality", i);
	    vfmt[i].quality=cfg.Int(s);
	    sprintf(s, "Stream %d keyfreq", i);
	    vfmt[i].keyfreq=cfg.Int(s);
	    sprintf(s, "Stream %d compressor", i);
	    vfmt[i].compressor=cfg.Int(s);
//	    v_str[i].stream->GetOutputFormat(&vfmt[i].header, sizeof vfmt[i].header);
	}
	else
	{
	    sprintf(s, "Stream %d fmt", i);
	    afmt[i-vs].fmt=cfg.Int(s);
	    sprintf(s, "Stream %d bitrate", i);
	    afmt[i-vs].bitrate=cfg.Int(s);
	}
    }
    int filters=cfg.Int("Filters");
//    cfg.Add("Filters", filter_list.size());
    for(int i=0; i<filters; i++)
    {

	sprintf(s, "Filter %d", i);
	Filter* f=getFilter(cfg.Int(s));
	if(!f)break;
	sprintf(s, "Filter %d info", i);
	string load=cfg.String(s);
	f->load(load);
	addFilter(f);
    }
    return 0;
}

int RecKernel::saveConfig(const char* fn)
{
    if (!_rf)
	return 0;
    WriteConfig cfg(fn);
    cfg.Add("SrcFile", _readfn);
    cfg.Add("DestFile", _fn);
    cfg.Add("VideoStreams", (int)vs);
    cfg.Add("AudioStreams", (int)as);

    char s[128];
    for (unsigned i = 0; i < v_str.size(); i++)
    {
	sprintf(s, "Stream %d start", i);
	cfg.Add(s, v_str[i].startpos);
	sprintf(s, "Stream %d end", i);
	cfg.Add(s, v_str[i].endpos);
	sprintf(s, "Stream %d mode", i);
	cfg.Add(s, (int)v_str[i].mode);
	if(i < vs)
	{
	    sprintf(s, "Stream %d quality", i);
	    cfg.Add(s, vfmt[i].quality);
	    sprintf(s, "Stream %d keyfreq", i);
	    cfg.Add(s, vfmt[i].keyfreq);
	    sprintf(s, "Stream %d compressor", i);
	    cfg.Add(s, (int) vfmt[i].compressor);
	}
	else
	{
	    sprintf(s, "Stream %d fmt", i);
	    cfg.Add(s, afmt[i-vs].fmt);
	    sprintf(s, "Stream %d bitrate", i);
	    cfg.Add(s, afmt[i-vs].bitrate);
	}
    }
    cfg.Add("Filters", (int)filter_list.size());
    for (unsigned i = 0; i < filter_list.size(); i++)
    {
	sprintf(s, "Filter %d", i);
	cfg.Add(s, filter_list[i]->id());
	sprintf(s, "Filter %d info", i);
	string save=filter_list[i]->save();
	cfg.Add(s, save);
    }
    return 0;
}
