#include "capproc.h"
#include "dsp.h"
#include "ccap.h"
#include <avifile.h>
#include <videoencoder.h>
#include <image.h>
#include <fourcc.h>
#include <cpuinfo.h>
#include <utils.h>
#include <creators.h>
#include <unistd.h>
#include <iostream>
#include <stdio.h>
#include "vidcapreg.h"
#include <fcntl.h>
using namespace Creators;
using namespace std;

#define __MODULE__ "Capture Config"

void CaptureConfig::load()
{
    filename=RS("FileName", "./movie.avi");
    segment_size=RI("IsSegmented", 1)?RI("SegmentSize", 1000*1000):-1;
    compressor=RI("Compressor", fccIV50);
    quality=RI("Quality", 9500);
    keyframes=RI("Keyframe", 15);
    timelimit=RI("LimitTime", 0)?RI("TimeLimit", 3600):-1;
    sizelimit=RI("LimitSize", 0)?RI("SizLimit", 2000000):-1;
    fps=RF("FPS", 25.);
    colorspace=(enum Colorspaces)RI("Colorspace", 0);
    setDimensions((enum Resolutions)RI("Resolution", 0));
    setFrequency((enum Sound_Freqs)
	      (RI("HaveAudio", 1)?RI("Frequency", 0):NoAudio));
    if(frequency>0)
    {
	setSamplesize((enum Sample_Sizes)RI("SampleSize", 0));
	setChannels((enum Sound_Chans)RI("SndChannels", 0));
    }
}

void CaptureConfig::setDimensions(Resolutions res)
{
    int i = 0;
    while (restable[i].res != res && restable[i].res != WNONE)
    	i++;
    if (restable[i].res == res)
    {
    	res_w = restable[i].width;
	res_h = restable[i].height;
	cout<<"Resolution: "<<res_w<<" x "<<res_h<<endl;
    }
    else
    	throw FATAL("Unknown video resolution");
}

void CaptureConfig::setChannels(Sound_Chans c)
{
    switch(c)
    {
    case Mono:
        chan=1;
        break;
    case Stereo:
        chan=2;
        break;
    default:
        throw FATAL("Unknown channel number");
    }
}
void CaptureConfig::setSamplesize(Sample_Sizes ss)
{
    switch(ss)
    {
    case S16:
        this->samplesize=16;
        break;
    case S8:
        this->samplesize=8;
        break;
    default:
        throw FATAL("Unknown audio sample size");
    }
}

void CaptureConfig::setFrequency(Sound_Freqs freq)
{
    switch(freq)
    {
    case NoAudio:
	frequency=0;
	break;
    case F48:
	frequency=48000;
	break;
    case F44:
	frequency=44100;
	break;
    case F22:
	frequency=22050;
	break;
    case F11:
	frequency=11025;
	break;
    default:
	throw FATAL("Unknown frequency");
    }    
}
#undef __MODULE__
#define __MODULE__ "Capture Process"
class frame_allocator
{
    struct frame
    {
	char* data;
	int status;
    };
    int _w;
    int _h;
    std::vector<frame> frames;
    int _limit;
    int refs;
public:
    int used_frames;
public:
    frame_allocator(int w, int h, int limit, int bpp)
	:_w(w), _h(h), _limit(limit),refs(2), used_frames(0)
    {
	while(frames.size()<(unsigned)limit)
	{
    	    frame f;
    	    f.data=new char[_w*_h*bpp/8+4];
	    f.status=0;
	    frames.push_back(f);
	}
    }
    ~frame_allocator()
    {
	for(unsigned i=0; i<frames.size(); i++)
	    delete frames[i].data;
    }
    int get_limit() const { return _limit; }
    void release()
    {
	refs--;
	if(!refs)delete this;
    }
    char* alloc()
    {
	unsigned i;
	for(i=0; i<frames.size(); i++)
	{
	    if(frames[i].status==0)
	    {
		frames[i].status=1;
		used_frames++;
		*(int*)(frames[i].data)=i;
		return frames[i].data+4;
	    }
	}
	return 0;
    /*
	if ((int)frames.size( )>= _limit)
	    return 0;
	frame f;
	f.data=new char[_w*_h*3+4];
	f.status=1;
	frames.push_back(f);
	used_frames++;
	*(int*)(f.data)=i;
	return f.data+4;
    */	
    }
    void free(char* mem)
    {
	if (!mem)
	{
	    std::cerr<<"ERROR: Freeing 0!"<<std::endl;
	    return;
	}
	int id=*(int*)(mem-4);
	if (id < 0 || id >= (int)frames.size() || frames[id].data != (mem-4))
	{
	    std::cerr<<"ERROR: Freeing unknown memory!"<<std::endl;
	    return;
	}
	if (frames[id].status == 0)
	{
	    std::cerr<<"ERROR: Duplicate free()!"<<std::endl;
	    return;
	}
	used_frames--;
	frames[id].status=0;
    }
};

void CaptureProcess::capwriter(void* arg, const char* data)
{
    CaptureProcess& a=*(CaptureProcess*)arg;
//    printf("%s\n", data);
    if(a.ccfd<0)
	return;
    if(a.lastccstring == data)
	return;
    char s[4096];
    long long curtime=longcount();
    int frame1=(int) ((a.lastcctime-a.starttime)*a.m_conf.fps/(1000.*freq));
    int frame2=(int) ((curtime-a.starttime)*a.m_conf.fps/(1000.*freq));
    if(a.lastccstring.size()==0)
    {
	a.lastcctime=curtime;
	a.lastccstring=data;
	return;
    }
    sprintf(s, "{%d}{%d}", frame1, frame2-1);
    char* ptr=&s[strlen(s)];
    const char* pp=a.lastccstring.c_str();
    while(*pp)
    {
	if(*pp==0xa)
	{
	    *ptr++='|';
	    pp++;
	}
	else
	    *ptr++=*pp++;
    }
    *ptr++=0xa;
    *ptr++=0;
    write(a.ccfd, s, strlen(s));
    printf("%s", s);
    a.lastccstring=data;
    a.lastcctime=curtime;
}


CaptureProcess::~CaptureProcess()
{
    m_quit=1;
    pthread_join(m_writer,0);
    if(m_conf.frequency>=0)
	pthread_join(m_audc,0);
    pthread_join(m_vidc,0);
    if(m_ccap)
	m_ccap->remove_callback(capwriter, (void*)this);
    if(ccfd)
	close(ccfd);
    std::cerr<<"All threads exited"<<std::endl;
    while(m_audq.size())
    {
	chunk z=m_audq.front();
	m_audq.pop();
	if(z.data)delete z.data;
    }
}


void* CaptureProcess::vidcap()
{
    const float fps=m_conf.fps;
    cnt=0;
    cap_drop=0;
        
    int w=m_conf.res_w;
    int h=m_conf.res_h;
    
    while(!m_quit)
    {
	int t1,t2,t3;
	long long currenttime=longcount();
	char* z=0;
//	cerr<<currenttime<<" "<<starttime<<" "<<fps<<std::endl;
//	cerr<<to_float(currenttime, starttime)*fps<<" "<<cnt<<std::endl;
//	double freq=550000.;
	double dist=double(currenttime-starttime)/(freq*1000.);
//	double dist=to_float(currenttime, starttime);
//	cerr<<dist<<" "<<freq<<std::endl;
	if(dist*fps<cnt)
	{
	    avi_usleep(10000);
//	    std::cerr<<"Sleeping"<<std::endl;
	    continue;
	}
	chunk ch;
	if(dist*fps<(cnt+1))
	{
	    z=m_v4l->grabCapture(false);
//	    char* tmpframe=new char[w*h*3];
	    char* tmpframe=m_pAllocator->alloc();
//	    printf("%f %x %x\n", dist, z, tmpframe);
	    int bpl, i;
	    switch(m_conf.colorspace)
	    {
	    case 0:
	    default:
		bpl=3*w;
	        if(tmpframe)
		    for(i=0; i<h; i++)
			memcpy(tmpframe+i*bpl, z+(h-i-1)*bpl, bpl);
		break;
	    case 1:
		if(tmpframe)
		    memcpy(tmpframe, z, 2*w*h);		    
		break;
	    case 2:
		if(tmpframe)
		    memcpy(tmpframe, z, 3*w*h/2);
		break;
	    }
	    ch.data=tmpframe;
	}
	else
	{
	    ch.data=0;
	    cap_drop++;
	}
	ch.timestamp=dist;
        m_vidq.push(ch);
	cnt++;
//	if(cnt%100==0)
//	    cerr<<"Capture thread: read "<<cnt<<" frames, dropped "<<cap_drop<<" frames"<<std::endl;
    }
    m_pAllocator->release();
    std::cerr<<"Capture thread exiting"<<std::endl;
    return 0;
}
int audioblock=0;
void* CaptureProcess::audcap()
{
    float abps=m_conf.samplesize*m_conf.chan*m_conf.frequency/8;
    char* buf=0;
    int bufsize=0;
    int blocksize=m_pDsp->getBufSize();
    audioblock=blocksize;
    int tim=0;
    while(!m_quit)
    {
//	if(buf==0)
//	{
//	    buf=new char[audioblock];
//	    bufsize=0;
//	}
	buf=new char[audioblock];
	memcpy(buf, m_pDsp->readBuf(), audioblock);
	long long ct=longcount();
	chunk ch;
	ch.data=buf;
//	double freq=550000.;
	if(starttime)
	    ch.timestamp=double(ct-starttime)/(freq*1000.);
//	    ch.timestamp=to_float(ct, starttime);
	else
	    ch.timestamp=-1;
	m_audq.push(ch);
	bufsize+=blocksize;
	tim++;
	if(tim%50==0)
//	    std::cerr<<"Audio thread: read "<<float(tim*blocksize)/88200.<<" seconds"<<std::endl;
	    std::cerr<<"Audio buffering: "<<m_pDsp->getSize()<<"/"<<blocksize<<endl;
	if(blocksize/abps>.1)
	    avi_usleep(50000);
//	if(bufsize==audioblock)
//	{
//	    m_audq.push(buf);
//	    buf=0;
//	}
    }
//    if(buf)delete buf;
    delete m_pDsp;
    return 0;
}

void* CaptureProcess::writer()
{
    IAviWriteFile* file=0;
    IAviSegWriteFile* sfile=0;
    const CodecInfo* ci=0;

//    int p = getpriority(PRIO_PROCESS, 0);
    //attention: only root is allowed to lower priority
//    setpriority(PRIO_PROCESS, 0, (p - 3 > -20) ? p - 3 : -20);

    IAviVideoWriteStream* stream;
    IAviWriteStream* audioStream=0;
    int cs;
    switch(m_conf.colorspace)
    {
    case 0:
    default:
	cs=24;
	break;
    case 1:
	cs=fccYUY2;
	break;
    case 2:
	cs=fccYV12;
	break;
    }
    BitmapInfo bh(m_conf.res_w, m_conf.res_h, cs);

    const double fps=m_conf.fps;

//    usleep(100000000);
    try
    {
	if(m_conf.segment_size==-1)
	    file=CreateIAviWriteFile(m_conf.filename.c_str());
	else
	{
    	    sfile=CreateSegmentedFile(m_conf.filename.c_str(), m_conf.segment_size*1024LL);
	    file=sfile;
	}
//	FILE* zz=fopen("bin/uncompr.bmp", "rb");
	stream=file->AddVideoStream(m_conf.compressor, &bh, int(1000000./m_conf.fps));
//	stream=file->AddStream(AviStream::Video);
//	ve.Init(fccIV50, (const char*)&bh);
    }
    catch(FatalError& e)
    {
	e.Print();
	error=e.GetDesc();
	m_quit=1;
	return 0;
    }

    float abps=(m_conf.samplesize*m_conf.frequency*m_conf.chan)/8;

    WAVEFORMATEX wfm;
    wfm.wFormatTag=1;//PCM
    wfm.nChannels=m_conf.chan;
    wfm.nSamplesPerSec=m_conf.frequency;
    //wfm.nSamplesPerSec=frequency * chan;  CHECK THIS
    wfm.nAvgBytesPerSec=(int)abps;
    wfm.nBlockAlign=(m_conf.samplesize*m_conf.chan)/8;
    wfm.wBitsPerSample=m_conf.samplesize;
    wfm.cbSize=0;


//    ve.SetQuality(9500);
//    ve.Start();
    stream->SetQuality(m_conf.quality);
    stream->Start();
    std::cerr<<"Entering loop"<<std::endl;
//    BITMAPINFOHEADER obh=ve.QueryOutputFormat();
//    stream->SetFormat((const char*)&obh, sizeof obh);
    int cnt=0;
    long long audiodata=0LL;
    int videodata=0;
    double video_error=0;
    int hide_video=0;
    int dup_video=0;
    double snd_time = 0., vid_time = 0.;
    comp_drop=0;
    while(1)
    {
	int x1, x2;
	chunk ch;
	while(m_vidq.size()>50)
	{
	    ch=m_vidq.front();
	    m_vidq.pop();
	    vid_time=ch.timestamp;
	    cnt++;
	    if(ch.data)
		m_pAllocator->free(ch.data);
	    stream->AddFrame(0);
	    videodata++;
//	    stream->AddChunk(0, 0, AVIIF_KEYFRAME);
	    comp_drop++;
	    
	    if(m_pProgress)
		m_pProgress->update(longcount()-starttime, vid_time, snd_time, 
		    audiodata, file->FileSize(), videodata-cnt,
		    0, m_pAllocator->used_frames, cap_drop, comp_drop, file->FileName());
	}
	while((m_vidq.size()==0) && (m_audq.size()==0))
	{
	    if(m_quit) goto finish;
	    avi_usleep(10000);
	}
	if(m_vidq.size())
	{
	    ch=m_vidq.front();
	    m_vidq.pop();
	    vid_time=ch.timestamp;
	    cnt++;
	    if(!hide_video)
	    {
		videodata++;
		CImage* im=0;
		size_t uiSize;
		int iKeyframe;
		if (ch.data)
		    im=new CImage(&bh, (unsigned char*)ch.data, false);
		int result=stream->AddFrame(im, &uiSize, &iKeyframe);
		// fixme - handle errors
		if (!result)
		{
		    uiSize &= ~0x40000000;
		    if(iKeyframe)
			uiSize |= 0x40000000;
		}
	    if(m_pProgress)
		m_pProgress->update(longcount()-starttime, vid_time, snd_time, 
				    audiodata, file->FileSize(), videodata-cnt,
				    uiSize, m_pAllocator->used_frames, cap_drop,
				    comp_drop, file->FileName());
		if(dup_video)
		{
		    videodata++;
		    stream->AddFrame(im, &uiSize);
	    if(m_pProgress)
		    m_pProgress->update(longcount()-starttime, vid_time, snd_time, 
					audiodata, file->FileSize(), videodata-cnt,
					uiSize, m_pAllocator->used_frames, cap_drop,
					comp_drop, file->FileName());
		    video_error+=1./fps;
		}
		if(im)
		    im->Release();
	    }
	    else video_error-=1./fps;
	    dup_video=hide_video=0;
    	    if(ch.data)
		m_pAllocator->free(ch.data);

	}
	if(m_audq.size())
	{
	    if(audioStream==0)
	    {
		audioStream=file->AddStream(AviStream::Audio,
					    &wfm, sizeof(wfm),
					    1, //uncompressed PCM data
					    (int)abps, //bytes/sec
					    (m_conf.samplesize*m_conf.chan)/8
					    //bytes/sample
					   );
	    }
	    ch=m_audq.front();
	    m_audq.pop();
//	    std::cerr<<ch.timestamp-snd_time<<" "<<ch.timestamp-(audiodata+audioblock)/44100./2<<std::endl;
	    snd_time=ch.timestamp;
	    audioStream->AddChunk(ch.data, audioblock, AVIIF_KEYFRAME);
	    audiodata+=audioblock;
	    if(m_pProgress)
	    m_pProgress->update(longcount()-starttime, vid_time, snd_time, 
		    audiodata, file->FileSize(), videodata-cnt,
		    -1, m_pAllocator->used_frames, cap_drop, comp_drop, file->FileName());
	    double audio_error=audiodata/abps-ch.timestamp;
	    if(audio_error<video_error-5./fps)
		hide_video=1;
	    if(audio_error>video_error+5./fps)
		dup_video=1;
	    delete ch.data;
	}
	if(segment_flag && sfile)
	{
	    sfile->Segment();
	    segment_flag=0;
//	    vid_clear=aud_clear=0;
    	}
	if(m_conf.timelimit!=-1)
	{
	    if(snd_time>m_conf.timelimit)
		m_quit=1;
	    if(vid_time>m_conf.timelimit)
		m_quit=1;
	}
	if(m_conf.sizelimit!=-1)
	{
	    if(file->FileSize()>m_conf.sizelimit*1024LL)
		m_quit=1;
	}
    }
finish:
    delete file;
    m_pAllocator->release();
    std::cerr<<"Write thread exiting"<<std::endl;
    return 0;
}
extern unsigned int m_iMemory;

CaptureProcess::CaptureProcess(v4lxif* v4l,		// v4lx interface pointer
				CaptureConfig* conf,
			        ClosedCaption* ccap)
    : m_v4l(v4l), m_quit(0), m_pProgress(0), m_ccap(ccap), m_pAllocator(0), m_conf(*conf),
    error(""), segment_flag(0), vid_clear(0), aud_clear(0)
{
    starttime=longcount();

    if(ccap)
    {
	std::string ccname;
	if (m_conf.filename.size() >= 4
            && (strncasecmp(m_conf.filename.c_str() + m_conf.filename.size() - 4, ".avi", 4) == 0))
	    ccname=m_conf.filename.substr(0, m_conf.filename.size()-4)+".sub";
	else
	    ccname=m_conf.filename+".sub";
	ccfd=open(ccname.c_str(), O_WRONLY|O_CREAT|O_TRUNC, 00666);
	lastcctime=starttime;
	ccap->add_callback(capwriter, (void*)this);
    }
    else
	ccfd=-1;
    int bpp;
    
    switch(m_conf.colorspace)
    {
    case 0:
    default:
	m_v4l->grabSetParams(m_conf.res_w, m_conf.res_h, VIDEO_PALETTE_RGB24);
	bpp=24;
	break;
    case 1:
	m_v4l->grabSetParams(m_conf.res_w, m_conf.res_h, VIDEO_PALETTE_YUV422);
	bpp=16;
	break;
    case 2:
	m_v4l->grabSetParams(m_conf.res_w, m_conf.res_h, VIDEO_PALETTE_YUV420P);
	bpp=12;
	break;
    }
    int maxframes = m_iMemory/(m_conf.res_w * m_conf.res_h * bpp / 8);
    if(maxframes < 5)
	maxframes = 5;
    if(m_conf.frequency>0)
    {
	m_pDsp=new dsp();
	if(m_pDsp->open(m_conf.samplesize, m_conf.chan, m_conf.frequency)==0)//returns file descriptor
    	    throw FATAL("Failed to open audio device");
	pthread_create(&m_audc, 0, CaptureProcess::audcap_starter, this);
    }
    try
    {
	m_pAllocator=new frame_allocator(m_conf.res_w,m_conf.res_h,maxframes,bpp);
    }
    catch(...)
    {
	throw FATAL("Out of memory");
    }
    
    pthread_create(&m_vidc, 0, CaptureProcess::vidcap_starter, this);
    pthread_create(&m_writer, 0, CaptureProcess::writer_starter, this);
}

     void CaptureProcess::setMessenger(CaptureProgress* pProgress)
     {
	 m_pProgress=pProgress;
	 m_pProgress->init(
	    m_conf.filename.c_str(), m_conf.fps, m_conf.res_w, m_conf.res_h, 
		m_conf.frequency, m_conf.samplesize, m_conf.chan, m_pAllocator->get_limit());
     }
