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

	AviWriteStream/AviWriteFile class implementation
	Copyright 2000 Eugene Kuznetsov  (divx@euro.ru)

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

#define _LARGEFILE64_SOURCE
#include <fcntl.h>
#include <cstdio>
#include <iostream>
#include "AviWrite.h"


using namespace std;

#if defined(__FreeBSD__) || defined(__NetBSD__)
#define lseek64 lseek
#define O_LARGEFILE 0
#endif
Registrator Registrator::Reg;

AviWriteFile::AviWriteFile(const char* name, int flags, int mask):m_filename(name), m_fd(0)
{
    try
    {
        m_status=0;
	m_fd=new FileBuffer(name, O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE, mask);

	const int junk_size=0x800;
        char* junk=new char[junk_size];
	memset(junk, 0, junk_size);
        m_fd->write(junk, junk_size);
	delete junk;
        m_status=1;

	memset(&m_header, 0, sizeof(m_header));
        m_header.dwFlags=flags;
	Registrator::Reg.push_back(this);
    }
    catch(FatalError&)
    {
	delete m_fd;
	throw;
    }
}

IAviWriteStream* AviWriteFile::AddStream(AviStream::StreamType type,
					 const void* format,
					 size_t format_size,
					 fourcc_t handler, int frame_rate,
					 size_t samplesize, int quality,
					 int flags)
{
    int ckid;
    ckid=MAKEAVICKID((type==AviWriteStream::Video)?cktypeDIBcompressed:cktypeWAVEbytes, m_streams.size());

    AviWriteStream* result=new AviWriteStream(this, ckid, type,
					      format, format_size,
					      handler, frame_rate,
					      samplesize,
					      quality, flags);

    m_streams.push_back(result);
    return result;
}

IAviVideoWriteStream* AviWriteFile::AddVideoStream(fourcc_t fourcc,
						   BITMAPINFOHEADER* srchdr,
						   int frame_rate, int flags)
{
    int ckid;
    ckid=MAKEAVICKID(cktypeDIBcompressed, m_streams.size());

    AviVideoWriteStream* result=new AviVideoWriteStream(this, ckid, fourcc,
							srchdr,	frame_rate, flags);
    if(result==0)
    	throw FATAL("Failed to create new video stream");

//    result->Create(fourcc, srchdr);
    m_streams.push_back(result);
    return result;
}
IAviAudioWriteStream* AviWriteFile::AddAudioStream(fourcc_t fourcc,
						   WAVEFORMATEX* fmt,
						   int bitrate, int flags)
{
    int ckid = MAKEAVICKID(cktypeWAVEbytes, m_streams.size());

    AviAudioWriteStream* result = new AviAudioWriteStream(this, ckid, fourcc,
							  fmt, bitrate, flags);
    if (!result)
    	throw FATAL("Failed to create new video stream");

    m_streams.push_back(result);
    return result;
}


#define write_int(X) m_fd->write_int(X)
//    temp=(X);
//    write(m_fd, &temp, 4);
AviWriteFile::~AviWriteFile()
{
    if (!m_status)
	return;

    try
    {
	offset_t videoendpos=m_fd->lseek(0, SEEK_CUR);
	if(videoendpos&1)
	{
	    int junk=-1;
	    m_fd->write( &junk, 1);
	    videoendpos++;
	}
	WriteHeaders();

	write_int(ckidAVINEWINDEX);
	write_int(m_index.size()*sizeof(AVIINDEXENTRY));
	m_fd->write( &m_index[0], m_index.size()*sizeof(AVIINDEXENTRY));

	videoendpos=m_fd->lseek(0, SEEK_END);
	m_fd->lseek(4, SEEK_SET);
	write_int(videoendpos-8);
	_destruct();
    }
    catch(...)
    {
	_destruct();
	throw;
    }
    if (!Registrator::Reg._destroying)
	Registrator::Reg.remove(this);
}

void AviWriteFile::_destruct()
{
    if (m_fd)
	delete m_fd;
    m_fd = 0;

    for(unsigned i=0; i<m_streams.size(); i++)
        delete m_streams[i];
    m_streams.clear();
    m_index.clear();
    m_status=0;
}

void AviWriteFile::AddChunk(offset_t offset, size_t size, Unsigned id, int flags)
{
//    m_index=(AVIINDEXENTRY*)realloc(m_index, (m_indsize+1)*sizeof(AVIINDEXENTRY));
    AVIINDEXENTRY entry;
    entry.ckid=id;
    entry.dwFlags=flags;
    entry.dwChunkOffset=offset;
    entry.dwChunkLength=size;
    try
    {
	m_index.push_back(entry);
    }
    catch(...)
    {
	throw FATAL("Chunk table reallocation failure");
    }
    if (m_index.size()%1000==0)
	WriteHeaders();
}

int64_t AviWriteFile::FileSize()
{
    return m_fd->lseek(0, SEEK_CUR);
}

AviWriteStream::AviWriteStream(AviWriteFile* file, int ckid,
			       enum AviStream::StreamType type,
			       const void* format, size_t format_size,
			       fourcc_t handler, int frame_rate,
			       size_t samplesize,
			       int quality, int flags)
    :m_file(file), m_ckid(ckid)
{
    if (format == 0)
        throw FATAL("Bad format");
    if (type == Other)
        throw FATAL("Unsupported stream type");

    m_type=type;

    m_fd=file->m_fd;
    m_header.dwLength=0;

    memset(&m_header, 0, sizeof(m_header));
    m_header.fccType=((m_type==Video)?streamtypeVIDEO:streamtypeAUDIO);
    m_header.fccHandler=handler;
    m_header.dwFlags=flags;
    if(m_type==Video)
    {
        m_header.dwRate=1000000;
        m_header.dwScale=frame_rate;
    }
    else
    {
        m_header.dwRate=frame_rate;
        m_header.dwScale=samplesize;
    }
    m_header.dwSampleSize=samplesize;
    m_header.dwQuality=quality;
    m_header.dwFlags=flags;
    m_format = new uint8_t[format_size];
    m_forsize = format_size;
    memcpy(m_format, format, format_size);
    if (m_type == Video)
    {
        m_header.rcFrame.right=*(const int*)(m_format+4);
	m_header.rcFrame.bottom=*(const int*)(m_format+8);
    }
}
AviWriteStream::AviWriteStream(AviWriteFile* file, int ckid,
			       enum AviStream::StreamType type,
			       fourcc_t handler, int frame_rate, int flags)
    :m_file(file), m_format(0), m_ckid(ckid)
{
    m_fd = file->m_fd;

    memset(&m_header, 0, sizeof(m_header));

    m_header.fccHandler=handler;
    m_header.dwFlags=flags;

    switch(type)
    {
    case Other:
	throw FATAL("Bad type");
    case Video:
	m_type=Video;
        m_header.fccType=streamtypeVIDEO;
	m_header.dwRate=1000000;
	m_header.dwScale=frame_rate;
	break;
    case Audio:
	m_type=Audio;
        m_header.fccType=streamtypeAUDIO;
	m_header.dwRate=frame_rate;
	m_header.dwScale=1;
	break;
    }
}

HRESULT AviWriteStream::AddChunk(const void* chunk, size_t size, int flags)
{
    if((chunk==0) && (size!=0))
    {
	cerr<<"Invalid argument to AviWriteStream::AddChunk()"<<endl;
	return -1;
    }
    offset_t offset=m_fd->lseek(0, SEEK_CUR);
    if (offset>0x7f000000)
	return -1;
    m_fd->write(&m_ckid, 4);
    m_fd->write(&size, 4);
    if (chunk)
    {
	m_fd->write( chunk, size);
	if(size&1)m_fd->write( chunk, 1);
    }
//    fsync(m_fd);
    m_file->AddChunk(offset-0x7FC, size, m_ckid, flags);
    if (m_header.dwSampleSize==0)
    	m_header.dwLength++;
    else
	m_header.dwLength += size/m_header.dwSampleSize;
    return 0;
}

HRESULT AviWriteFile::Reserve(size_t size)
{
    return -1;
}

HRESULT AviWriteFile::WriteChunk(fourcc_t fourcc, void* data, size_t size)
{
    return -1;
}

AviWriteStream::~AviWriteStream()
{
    if (m_format)
	delete m_format;
}

IAviWriteStream::~IAviWriteStream(){}
IAviVideoWriteStream::~IAviVideoWriteStream(){}
IAviWriteFile::~IAviWriteFile(){}

IAviWriteFile* CreateIAviWriteFile(const char* name, int flags, int mask)
{
    return new AviWriteFile(name, flags, mask);
}

void AviWriteFile::WriteHeaders()
{
    //cerr<<"**** Writing headers ****"<<endl;
    if(m_status==0)
	return;
    m_header.dwFlags |= AVIF_HASINDEX | AVIF_TRUSTCKTYPE;
    m_header.dwPaddingGranularity=0;
    m_header.dwTotalFrames=0;
    for(unsigned i=0; i<m_streams.size(); i++)
    {
	if(m_streams[i]->GetType()==AviWriteStream::Video)
	{
	    m_header.dwTotalFrames=m_streams[i]->GetLength();
	    m_header.dwMicroSecPerFrame=m_streams[i]->m_header.dwScale;
	    m_header.dwWidth=m_streams[i]->m_header.rcFrame.right;
	    m_header.dwHeight=labs(m_streams[i]->m_header.rcFrame.bottom);
	    break;
	}
    }
    if (m_header.dwTotalFrames==0)
	if(m_streams.size())
	{
	    m_header.dwTotalFrames=m_streams[0]->GetLength();
	    m_header.dwWidth=m_header.dwHeight=0;
	}
    m_header.dwStreams=m_streams.size();
    offset_t endpos=m_fd->lseek( 0, SEEK_END);
    m_fd->lseek( 0, SEEK_SET);
    write_int(mmioFOURCC('R', 'I', 'F', 'F'));
    write_int(endpos-8);
    write_int(formtypeAVI);// Here goes chunk with all headers
    write_int(mmioFOURCC('L', 'I', 'S', 'T'));
    int hdr_pos=0x10;
    write_int(0);//here header chunk size hdr_size will be
    int hdr_size = 12 + sizeof(MainAVIHeader);
    write_int(listtypeAVIHEADER);
    write_int(ckidAVIMAINHDR);
    write_int(sizeof(MainAVIHeader));

    m_fd->write(&m_header, sizeof(MainAVIHeader));
    for (unsigned j = 0; j < m_streams.size(); j++)
    {
	write_int(mmioFOURCC('L', 'I', 'S', 'T'));
	write_int(20+sizeof(AVIStreamHeader)+m_streams[j]->m_forsize+(m_streams[j]->m_forsize&1));
        write_int(listtypeSTREAMHEADER);
	write_int(ckidSTREAMHEADER);
        write_int(sizeof(AVIStreamHeader));
      	m_fd->write(&m_streams[j]->m_header, sizeof(AVIStreamHeader));
	write_int(ckidSTREAMFORMAT);
	write_int(m_streams[j]->m_forsize);
	m_fd->write( m_streams[j]->m_format, m_streams[j]->m_forsize);
	if (m_streams[j]->m_forsize&1)
	    m_fd->write( &m_fd, 1);
	hdr_size+=28+sizeof(AVIStreamHeader)+m_streams[j]->m_forsize+(m_streams[j]->m_forsize&1);
    }

    if (hdr_size > 0x700)
	throw FATAL("Too large header. Aborting");

    int curpos=m_fd->lseek(0, SEEK_CUR);
    write_int(ckidAVIPADDING);
    write_int(0x7F4 - (curpos + 8));

    m_fd->lseek(0x7F4, SEEK_SET);
    write_int(mmioFOURCC('L', 'I', 'S', 'T'));
    write_int(endpos-0x7FC);
    write_int(listtypeAVIMOVIE);

    m_fd->lseek(hdr_pos, SEEK_SET);
    write_int(hdr_size);
    m_fd->lseek(0, SEEK_END);
}
