#include <qapp.h>

#include "recompressor.h"
#include "filters.h"

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

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

using namespace std;

void* RecKernel::RecompressThread(void* arg)
{
    try
    {
	RecKernel& kernel = *(RecKernel*)arg;

	IAviWriteFile* wf = 0;
	IAviWriteStream* a_rws = 0; //audio raw write stream
	IAviWriteStream* v_rws = 0; //video raw write stream
	IAviAudioWriteStream* aws = 0;
	IAviVideoWriteStream* vws = 0;

	streamid_t& as = kernel.as;
	streamid_t& vs = kernel.vs;
	vector<AudioEncoderInfo>& afmt=kernel.afmt;
	vector<VideoEncoderInfo>& vfmt=kernel.vfmt;

	vector<full_stream> v_str=kernel.v_str;
	string& fn=kernel._fn;

	IRecompressCallback* _cb=kernel._cb;
	IImageControl* _ctl=kernel._ctl;
	IAviReadFile* _rf=kernel._rf;

	int& rec_status=kernel.rec_status;
	int& pause_status=kernel.pause_status;

	IAviReadStream* ars = 0;
	if (as && v_str[vs].mode != Remove)
	{
	    ars = v_str[vs].stream;
	    ars->SeekTime(v_str[vs].startpos);
	}

	IAviReadStream* vrs=0;
	if (vs && v_str[0].mode != Remove)
	{
	    vrs = v_str[0].stream;
	    vrs->SeekTime(v_str[0].startpos);
	}
	if (!vrs && !ars)
	    return 0; // nothing to do

	try
	{
	    if (ars && v_str[vs].mode == Recompress)
		ars->StartStreaming();

	    if (vrs && v_str[0].mode == Recompress)
	    {
		vrs->StopStreaming();
		vrs->SetDirection(true);
		vrs->StartStreaming();

		//cout << "STARTPOS " << v_str[0].startpos << endl;
		vrs->SeekTime(v_str[0].startpos);
		while(vrs->GetTime()<v_str[0].startpos && !vrs->Eof())
		    vrs->ReadFrame();
	    }
	}
	catch (FatalError& e)
	{
	    cerr<<"Cannot recompress this"<<endl;
	    e.Print();
	    return 0;
	}

	wf = CreateSegmentedFile(fn.c_str());

	WAVEFORMATEX wfmtx;
	char* ext = 0;

	size_t audioSamples = 0;
        double audioStartTime = 0.0;
	if (ars)
	{
	    audioSamples = ars->GetLength();
            audioStartTime = ars->GetTime();
	    switch(v_str[vs].mode)
	    {
	    case Recompress:
		ars->GetOutputFormat(&wfmtx, sizeof(wfmtx));
		//	if(afmt[0].fmt==0x55)
		//	{
		aws = wf->AddAudioStream(0x55, &wfmtx, 16000);
		if (aws)
		    aws->Start();
		//	}
		//	else
		//	    a_rws=wf->AddStream(AviStream::Audio, (char*)&wfmtx, 18, wfmtx.wFormatTag,
		//		wfmtx.nAvgBytesPerSec, wfmtx.nBlockAlign);
		break;
	    case Copy:
		ars->GetAudioFormatInfo(&wfmtx, 0);
		if (wfmtx.cbSize > 0)
		{
		    ext = new char[wfmtx.cbSize + 18];
		    ars->GetAudioFormatInfo(&wfmtx, &ext);
		}
		if (wfmtx.nBlockAlign <= 0)
		    wfmtx.nBlockAlign = 1;
		a_rws=wf->AddStream(AviStream::Audio,
				    ext?(uint8_t*)ext:(uint8_t*)&wfmtx,
				    ext?(wfmtx.cbSize + 18):18, wfmtx.wFormatTag,
				    wfmtx.nAvgBytesPerSec, wfmtx.nBlockAlign);
		break;
	    case Remove:
		break;
	    }
	}
	delete ext;

	BITMAPINFOHEADER bh;
	size_t videoFrames = 0;
        double videoStartTime = 0.0;
	if (vrs)
	{
	    videoFrames = vrs->GetLength();
            videoStartTime = vrs->GetTime();
	    switch (v_str[0].mode)
	    {
	    case Recompress:
		vrs->GetOutputFormat(&bh, sizeof(bh));
		try
		{
		    for (unsigned i = 0; i < kernel.getFilterCount(); i++)
		    {
			Filter* fi = kernel.getFilterAt(i);
			if (FormatChanger* fch = dynamic_cast<FormatChanger*>(fi))
			    fch->adjust(bh);
		    }
		    vws = wf->AddVideoStream(vfmt[0].compressor, &bh,
					     (unsigned int) (1000000.*vrs->GetFrameTime()));
		    vws->Start();
		}
		catch(FatalError& e)
		{
		    e.Print();
		    vws=0;
		}
		break;
	    case Copy:
                vrs->GetVideoFormatInfo(&bh, sizeof(bh));
		v_rws = wf->AddStream(AviStream::Video, &bh, sizeof(bh),
				      bh.biCompression,
				      (unsigned int) (1000000.*vrs->GetFrameTime()));
		break;
	    case Remove:
		break;
	    }
	}

	uint8_t zz[44100];
	int comp_frame_size=abs(bh.biWidth * bh.biHeight) * 4;
	uint8_t* comp_frame=0;
	if (v_str[0].mode != Remove)
	    comp_frame = new uint8_t[comp_frame_size];
	double bpf;
	if(vrs)
	{
	    if(aws)
		bpf=vrs->GetFrameTime()*(wfmtx.nSamplesPerSec*wfmtx.nChannels*wfmtx.wBitsPerSample)/8;
	    else
		bpf=vrs->GetFrameTime()*wfmtx.nAvgBytesPerSec;
	}
	else
	    bpf=0;

	_cb->setTotal(videoFrames, audioSamples, videoStartTime, audioStartTime);
	framepos_t written_frames=0;
	//    while((!ars->Eof()) && (!vrs->Eof()))

	int64_t time_start = longcount();
	int64_t last_progress = 0;
        int64_t time_current;
	size_t written_audio = 0;
	for(;;)
	{
	    if (ars && (ars->Eof() || ars->GetTime() >= v_str[vs].endpos))
	    {
		ars = 0;
		continue;
	    }

	    if (vrs && (vrs->Eof() || vrs->GetTime() >= v_str[0].endpos))
	    {
		vrs = 0;
		continue;
	    }

	    if ((!ars && !vrs) || !kernel.rec_status)
		break;

	    if (kernel.pause_status)
	    {
		avi_usleep(100000);
		//	    qApp->processEvents(100);
		//	    qApp->wakeUpGuiThread();
		continue;
	    }

	    size_t samples_read, bytes_read;
            size_t written_asnap = written_audio;
	    if (ars)
	    {
		if (aws)
		{
		    int64_t excess = (int64_t) (bpf * (written_frames + 1) - written_audio);
		    cerr<<"Excess "<<excess<<", written audio "<<written_audio<<", written frames "<<written_frames<<", bpf "<<bpf<<endl;
		    if (vrs)
		    {
			while ((excess>0) && (!ars->Eof()))
			{
			    ars->ReadFrames(zz, (excess>sizeof(zz)) ? excess : sizeof(zz),
					    sizeof(zz), samples_read, bytes_read);
			    written_audio+=bytes_read;
			    excess-=bytes_read;
			    aws->AddData(zz, bytes_read);
			}
		    }
		    else
		    {
			ars->ReadFrames(zz, sizeof(zz), sizeof(zz),
					samples_read, bytes_read);
			written_audio+=bytes_read;
			aws->AddData(zz, bytes_read);
		    }
		}
		else if (a_rws)
		{
		    int64_t excess = (int64_t) (bpf*(written_frames+1)-written_audio);
		    // cerr<<"Excess "<<excess<<", written audio "<<written_audio<<", written frames "<<written_frames<<", bpf "<<bpf<<endl;
		    if (vrs)
		    {
			while((excess>0) && (!ars->Eof()))
			{
			    ars->ReadDirect(zz, (excess > sizeof(zz)) ? excess : sizeof(zz),
					    (sizeof(zz)/wfmtx.nBlockAlign) * wfmtx.nBlockAlign,
					    samples_read, bytes_read);
			    written_audio+=bytes_read;
			    excess-=bytes_read;
			    int flags=0;
			    ars->GetFrameFlags(&flags);
			    a_rws->AddChunk(zz, bytes_read, flags);
			}
		    }
		    else
		    {
			// excess=v_str[vs].endpos-ars->GetTime();
			// if(excess>sizeof zz)excess=sizeof zz;
			if (v_str[vs].endpos <= ars->GetTime())
			    excess=0;
			else
			    excess=sizeof(zz);

			if (excess < wfmtx.nBlockAlign)
			{
			    ars = 0;
			    continue;
			}
			if (excess < wfmtx.nBlockAlign)
			{
			    ars = 0;
			    continue;
			}
			ars->ReadDirect(zz, excess,
					(excess/wfmtx.nBlockAlign)*wfmtx.nBlockAlign,
					samples_read, bytes_read);
			written_audio += bytes_read;
			excess -= bytes_read;
			int flags=0;
			ars->GetFrameFlags(&flags);
			a_rws->AddChunk(zz, bytes_read, flags);
		    }
		}
		_cb->addAudio(ars->GetPos(), written_audio - written_asnap, ars->GetTime());
	    }

	    if (vrs)
	    {
		size_t vsize;
		int iskeyframe;
		bool show_progress = false;

		time_current=longcount();
		if (to_float(time_current, last_progress) > 1.0)
		{
		    last_progress = time_current;
		    show_progress = true;
		}

		if (vws)
		{
		    vrs->ReadFrame();
		    CImage* ptr=vrs->GetFrame();

		    if (!ptr)
		    {
			cerr<<"WARNING: zero frame"<<endl;
			vws->AddFrame(0);
			//    vrs=0;
		    }
		    else
		    {
			CImage* im = new CImage(ptr);
			if (_ctl && show_progress)
			    _ctl->setSourcePicture(im);

			for (unsigned i = 0; i < kernel.getFilterCount(); i++)
			{
			    Filter* fi = kernel.getFilterAt(i);
			    CImage* new_im = fi->process(im, vrs->GetPos());
			    im->Release();
			    im = new_im;
			}
			if (!im->GetFmt()->IsRGB())
			    im->ToRGB();

			if (_ctl && show_progress)
			{
			    CImage* new_im = new CImage(im);
			    if (new_im)
			    {
				if (kernel._recf)
				{
                                    // store compressed image
				    CImage* new_im_2=kernel._recf->process(new_im, 0);
				    if (new_im_2)
				    {
					new_im->Release();
					new_im=new_im_2;
				    }
				}
				_ctl->setDestPicture(new_im);
				new_im->Release();
			    }
			}
			vws->AddFrame(im, &vsize, &iskeyframe);
			im->Release();
		    }
		}
		else
		{
		    uint32_t samples_read;
		    vrs->ReadDirect(comp_frame, comp_frame_size, 1,
				    samples_read, vsize);
		    vrs->GetFrameFlags(&iskeyframe);
		    if (!samples_read)
		    {
			cerr<<"ERROR: Failed to read video frame"<<endl;
			vrs=0;
		    }
		    else
			v_rws->AddChunk(comp_frame, vsize, iskeyframe);
		}
		written_frames++;
		_cb->addVideo(written_frames, vsize, vrs->GetTime(), iskeyframe);
		if (show_progress)
		{
		    double percent=(vrs->GetTime()-v_str[0].startpos)/(min(vrs->GetLengthTime(), v_str[0].endpos)-v_str[0].startpos);
		    _cb->setNewState(percent,
				     to_float(time_current, time_start),
				     to_float(time_current, time_start)*(1-percent)/percent,
				     wf->FileSize());
		}
	    }
	}
	_cb->finished();
	delete wf;
	return 0;

    }
    catch(FatalError& e)
    {
	e.PrintAll();
	return 0;
    }
}

int RecKernel::start_recompress()
{
    rec_status=1;
    pause_status=0;
    pthread_create(&rec_thread, 0, RecKernel::RecompressThread, this);
    return 0;
}

int RecKernel::pause_recompress()
{
    pause_status = !pause_status;
    return pause_status;
}

int RecKernel::stop_recompress()
{
    rec_status = 0;
    pthread_join(rec_thread, 0);
    return 0;
}
