#define AVIIF_KEYFRAME  0x00000010L // this frame is a key frame.

#include <string>
using namespace std;

#include <memory.h>
#include <mmsystem.h>
#include <vfw.h>

BOOL AVI_Init()
{
        /* first let's make sure we are running on 1.1 */
        WORD wVer = HIWORD(VideoForWindowsVersion());
        if (wVer < 0x010a){
             /* oops, we are too old, blow out of here */
             //MessageBeep(MB_ICONHAND);
             MessageBox(NULL, "Cant't init AVI File - Video for Windows version is to old", "Error", MB_OK|MB_ICONSTOP);
             return FALSE;
        }

        AVIFileInit();

        return TRUE;
}

BOOL AVI_FileOpenWrite(PAVIFILE * pfile, const char *filename)
{
        HRESULT hr = AVIFileOpen(pfile,           // returned file pointer
                       filename,                  // file name
                       OF_WRITE | OF_CREATE,      // mode to open file with
                       NULL);                     // use handler determined
                                                  // from file extension....
        if (hr != AVIERR_OK)
                return FALSE;

        return TRUE;
}

DWORD getFOURCC(const char* value)
{
	if(_stricmp(value, "DIB") == 0)
	{
		return mmioFOURCC(value[0],value[1],value[2],' ');
	}
	else if((_stricmp(value, "CVID") == 0)
		 || (_stricmp(value, "IV32") == 0)
		 || (_stricmp(value, "MSVC") == 0)
		 || (_stricmp(value, "IV50") == 0))
	{
		return mmioFOURCC(value[0],value[1],value[2],value[3]);
	}
	else
	{
		return NULL;
	}
}

// Fill in the header for the video stream....
// The video stream will run in rate ths of a second....
BOOL AVI_CreateStream(PAVIFILE pfile, PAVISTREAM * ps, int rate, // sample/second
                      unsigned long buffersize, int rectwidth, int rectheight,
					  const char* _compressor)
{
		AVISTREAMINFO strhdr;
		memset(&strhdr, 0, sizeof(strhdr));
        strhdr.fccType                = streamtypeVIDEO;// stream type
		strhdr.fccHandler             = getFOURCC(_compressor);
        //strhdr.fccHandler             = 0; // no compression!
		//strhdr.fccHandler             = mmioFOURCC('D','I','B',' '); // Uncompressed
		//strhdr.fccHandler             = mmioFOURCC('C','V','I','D'); // Cinpak
		//strhdr.fccHandler             = mmioFOURCC('I','V','3','2'); // Intel video 3.2
		//strhdr.fccHandler             = mmioFOURCC('M','S','V','C'); // Microsoft video 1
		//strhdr.fccHandler             = mmioFOURCC('I','V','5','0'); // Intel video 5.0
		//strhdr.dwFlags                = AVISTREAMINFO_DISABLED;
		//strhdr.dwCaps                 = 
		//strhdr.wPriority              = 
		//strhdr.wLanguage              = 
        strhdr.dwScale                = 1;
        strhdr.dwRate                 = rate;               // rate fps
		//strhdr.dwStart                =  
		//strhdr.dwLength               = 
		//strhdr.dwInitialFrames        = 
        strhdr.dwSuggestedBufferSize  = buffersize;
		strhdr.dwQuality              = -1; // use the default
		//strhdr.dwSampleSize           = 
        SetRect(&strhdr.rcFrame, 0, 0,              // rectangle for stream
            (int) rectwidth,
            (int) rectheight);
		//strhdr.dwEditCount            = 
		//strhdr.dwFormatChangeCount    =
		//strcpy(strhdr.szName, "Full Frames (Uncompressed)");

        // And create the stream;
        HRESULT hr = AVIFileCreateStream(pfile,             // file pointer
                                 ps,                // returned stream pointer
                                 &strhdr);          // stream header
        if (hr != AVIERR_OK) {
                return FALSE;
        }

        return TRUE;
}

string getFOURCCVAsString(DWORD value)
{
	string returnValue = "";
	if( value == 0 )
		return returnValue;

	DWORD ch0 = value & 0x000000FF;
	returnValue.push_back((char) ch0);
	DWORD ch1 = (value & 0x0000FF00)>>8;
	returnValue.push_back((char) ch1);
	DWORD ch2 = (value & 0x00FF0000)>>16;
	returnValue.push_back((char) ch2);
	DWORD ch3 = (value & 0xFF000000)>>24;
	returnValue.push_back((char) ch3);

	return returnValue;
}

string dumpAVICOMPRESSOPTIONS(AVICOMPRESSOPTIONS opts)
{
	char tmp[255];
	string returnValue = "Dump of AVICOMPRESSOPTIONS\n";

	returnValue += "DWORD  fccType = streamtype("; returnValue += getFOURCCVAsString(opts.fccType); returnValue += ")\n";
	returnValue += "DWORD  fccHandler = "; returnValue += getFOURCCVAsString(opts.fccHandler); returnValue += "\n";

	_snprintf(tmp, 255, "DWORD  dwKeyFrameEvery = %d\n", opts.dwKeyFrameEvery);
	returnValue += tmp;

	_snprintf(tmp, 255, "DWORD  dwQuality = %d\n", opts.dwQuality);
	returnValue += tmp;

	_snprintf(tmp, 255, "DWORD  dwBytesPerSecond = %d\n", opts.dwBytesPerSecond);
	returnValue += tmp;

	if((opts.dwFlags & AVICOMPRESSF_DATARATE) == AVICOMPRESSF_DATARATE){strcpy(tmp, "DWORD  fccType = AVICOMPRESSF_DATARATE\n");}
	else if((opts.dwFlags & AVICOMPRESSF_INTERLEAVE) == AVICOMPRESSF_INTERLEAVE){strcpy(tmp, "DWORD  fccType = AVICOMPRESSF_INTERLEAVE\n");}
	else if((opts.dwFlags & AVICOMPRESSF_KEYFRAMES) == AVICOMPRESSF_KEYFRAMES){strcpy(tmp, "DWORD  fccType = AVICOMPRESSF_KEYFRAMES\n");}
	else if((opts.dwFlags & AVICOMPRESSF_VALID) == AVICOMPRESSF_VALID){strcpy(tmp, "DWORD  fccType = AVICOMPRESSF_VALID\n");}
	else {_snprintf(tmp, 255, "DWORD  dwFlags = Unknown(%d)\n", opts.dwFlags);}
	returnValue += tmp;

	_snprintf(tmp, 255, "LPVOID lpFormat = %d\n", (int)opts.lpFormat);
	returnValue += tmp;

	_snprintf(tmp, 255, "DWORD  cbFormat = %d\n", opts.cbFormat);
	returnValue += tmp;

	_snprintf(tmp, 255, "LPVOID lpParms = %d\n", (int)opts.lpParms);
	returnValue += tmp;

	_snprintf(tmp, 255, "DWORD  cbParms = %d\n", opts.cbParms); 
	returnValue += tmp;

	_snprintf(tmp, 255, "DWORD  dwInterleaveEvery = %d\n", opts.dwInterleaveEvery);
	returnValue += tmp;

	return returnValue;
}

BOOL AVI_SetOptions(PAVISTREAM * ps, PAVISTREAM * psCompressed, LPBITMAPINFOHEADER lpbi,
					const char* _compressor)
{
         
        AVICOMPRESSOPTIONS opts;
        AVICOMPRESSOPTIONS FAR * aopts[1] = {&opts};

		memset(&opts, 0, sizeof(opts));
		opts.fccType = streamtypeVIDEO;
		opts.fccHandler             = getFOURCC(_compressor);
		//opts.fccHandler  = 0;
		//opts.fccHandler            = mmioFOURCC('D','I','B',' '); // Uncompressed
		//opts.fccHandler             = mmioFOURCC('C','V','I','D'); // Cinpak
		//opts.fccHandler             = mmioFOURCC('I','V','3','2'); // Intel video 3.2
		//opts.fccHandler             = mmioFOURCC('M','S','V','C'); // Microsoft video 1
		//opts.fccHandler             = mmioFOURCC('I','V','5','0'); // Intel video 5.0
		//opts.dwKeyFrameEvery = 5;
		//opts.dwQuality
		//opts.dwBytesPerSecond
		//opts.dwFlags                = AVICOMPRESSF_KEYFRAMES;
		//opts.lpFormat 
		//opts.cbFormat
		//opts.lpParms
		//opts.cbParms 
		//opts.dwInterleaveEvery

		/* display the compression options dialog box if specified compressor is unknown */
		if(getFOURCC(_compressor) == NULL)
		{
			if (!AVISaveOptions(NULL, 0, 1, ps, (LPAVICOMPRESSOPTIONS FAR *) &aopts))
			{
				return FALSE;
			}

			//printf("%s", dumpAVICOMPRESSOPTIONS(opts));
			//MessageBox(NULL, dumpAVICOMPRESSOPTIONS(opts).c_str(), "AVICOMPRESSOPTIONS", MB_OK);
		}		

        HRESULT hr = AVIMakeCompressedStream(psCompressed, *ps, &opts, NULL);
        if (hr != AVIERR_OK) {
                return FALSE;
        }

        hr = AVIStreamSetFormat(*psCompressed, 0,
                               lpbi,                    // stream format
                               lpbi->biSize             // format size
                                   + lpbi->biClrUsed * sizeof(RGBQUAD)
                                   );
        if (hr != AVIERR_OK) {
        return FALSE;
        }

        return TRUE;
}

BOOL AVI_SetText(PAVIFILE pfile, PAVISTREAM psText, char *szText, int width, int height, int TextHeight)
{
        // Fill in the stream header for the text stream....
        AVISTREAMINFO strhdr;
        DWORD dwTextFormat;
        // The text stream is in 60ths of a second....

		memset(&strhdr, 0, sizeof(strhdr));
        strhdr.fccType                = streamtypeTEXT;
        strhdr.fccHandler             = mmioFOURCC('D', 'R', 'A', 'W');
        strhdr.dwScale                = 1;
        strhdr.dwRate                 = 60;
        strhdr.dwSuggestedBufferSize  = sizeof(szText);
        SetRect(&strhdr.rcFrame, 0, (int) height,
            (int) width, (int) height + TextHeight); // #define TEXT_HEIGHT 20

        // ....and create the stream.
        HRESULT hr = AVIFileCreateStream(pfile, &psText, &strhdr);
        if (hr != AVIERR_OK) {
                return FALSE;
        }

        dwTextFormat = sizeof(dwTextFormat);
        hr = AVIStreamSetFormat(psText, 0, &dwTextFormat, sizeof(dwTextFormat));
        if (hr != AVIERR_OK) {
                return FALSE;
        }

        return TRUE;
}

BOOL AVI_AddFrame(PAVISTREAM psCompressed, int time, LPBITMAPINFOHEADER lpbi)
{
	int ImageSize = lpbi->biSizeImage;
	if (ImageSize == 0)
	{
		if (lpbi->biBitCount == 24)
		{
			ImageSize = lpbi->biWidth * lpbi->biHeight * 3;
		}
	}
	HRESULT hr = AVIStreamWrite(psCompressed, // stream pointer
		time, // time of this frame
		1, // number to write
		(LPBYTE) lpbi + // pointer to data
		lpbi->biSize +
		lpbi->biClrUsed * sizeof(RGBQUAD),
		ImageSize, // lpbi->biSizeImage, // size of this frame
		AVIIF_KEYFRAME, // flags....
		NULL,
		NULL);
	if (hr != AVIERR_OK)
	{
		char strMsg[255];
		_snprintf(strMsg, 255, "Error: AVIStreamWrite, error %d",hr);
		MessageBox(NULL, strMsg, "", MB_OK);
		return FALSE;
	}
	
	return TRUE;
}

BOOL AVI_AddText(PAVISTREAM psText, int time, char *szText)
{
        int iLen = (int)strlen(szText);

        HRESULT hr = AVIStreamWrite(psText,
                        time,
                        1,
                        szText,
                        iLen + 1,
                        AVIIF_KEYFRAME,
                        NULL,
                        NULL);
        if (hr != AVIERR_OK)
                return FALSE;

        return TRUE;
}

BOOL AVI_CloseStream(PAVISTREAM ps, PAVISTREAM psCompressed, PAVISTREAM psText)
{
        if (ps)
                AVIStreamClose(ps);

        if (psCompressed)
                AVIStreamClose(psCompressed);

        if (psText)
                AVIStreamClose(psText);



        return TRUE;
}

BOOL AVI_CloseFile(PAVIFILE pfile)
{
        if (pfile)
                AVIFileClose(pfile);
        
        return TRUE;
}

BOOL AVI_Exit()
{
        AVIFileExit();

        return TRUE;
}
















/* Here are the additional functions we need! */


static PAVIFILE pfile = NULL; 
static PAVISTREAM ps = NULL;
static PAVISTREAM psCompressed = NULL; 
static int count = 0;


// Initialization... 
bool START_AVI(const char* file_name)
{
    if(! AVI_Init())
	{
		//printf("Error - AVI_Init()\n");
		return false;
	}

    if(! AVI_FileOpenWrite(&pfile, file_name))
	{
		//printf("Error - AVI_FileOpenWrite()\n");
		return false;
	}

	return true;
}
 
bool ADD_FRAME_FROM_DIB_TO_AVI(const char* _compressor, int _frameRate, int width, int height, int bits, void* pdata)
{
	if(count == 0)
	{
		if(! AVI_CreateStream(pfile, &ps, _frameRate, 
			width*height/bits, 
			width,
			height, _compressor))
		{
			//printf("Error - AVI_CreateStream()\n");
			return false;
		} 

		BITMAPINFOHEADER bi;
		memset(&bi, 0, sizeof(bi));
		bi.biSize = sizeof(BITMAPINFOHEADER);
		bi.biWidth = width;
		bi.biHeight = height;
		bi.biPlanes = 1;
		bi.biBitCount = bits;
		bi.biCompression = BI_RGB;
		bi.biSizeImage = width * height * bits /8;
		if(! AVI_SetOptions(&ps, &psCompressed, &bi, _compressor))
		{
			return false;
		}
	}

	HRESULT hr = AVIStreamWrite(psCompressed, // stream pointer
		count, // time of this frame
		1, // number to write
		pdata,
		width*height/8, // lpbi->biSizeImage, // size of this frame
		AVIIF_KEYFRAME, // flags....
		NULL,
		NULL);
	if (hr != AVIERR_OK)
	{
		char strMsg[255];
		_snprintf(strMsg, 255, "Error: AVIStreamWrite, error %d",hr);
		MessageBox(NULL, strMsg, "", MB_OK);
		return FALSE;
	}
	
	count++;
	return true;
}

//Now we can add frames
// ie. ADD_FRAME_FROM_DIB_TO_AVI(yourDIB, "CVID", 25);
bool ADD_FRAME_FROM_DIB_TO_AVI(HANDLE dib, const char* _compressor, int _frameRate)
{
	LPBITMAPINFOHEADER lpbi;
	if(count == 0)
	{
		lpbi = (LPBITMAPINFOHEADER)GlobalLock(dib); 
		if(! AVI_CreateStream(pfile, &ps, _frameRate, 
			(unsigned long) lpbi->biSizeImage, 
			(int) lpbi->biWidth, 
			(int) lpbi->biHeight, _compressor))
		{
			//printf("Error - AVI_CreateStream()\n");
			GlobalUnlock(lpbi);
			return false;
		} 

		if(! AVI_SetOptions(&ps, &psCompressed, lpbi, _compressor))
		{
			//printf("Error - AVI_SetOptions()\n");
			GlobalUnlock(lpbi);
			return false;
		}

		GlobalUnlock(lpbi);
	}

	lpbi = (LPBITMAPINFOHEADER)GlobalLock(dib); 
	if(! AVI_AddFrame(psCompressed, count * 1, lpbi))
	{
		//printf("Error - AVI_AddFrame()\n");
		GlobalUnlock(lpbi);
		return false;
	}

	GlobalUnlock(lpbi); 
	count++;
	return true;
}

// The end... 
bool STOP_AVI()
{
     if(! AVI_CloseStream(ps, psCompressed, NULL))
	 {
		 //printf("Error - AVI_CloseStream()\n");
		 return false;
	 }

     if(! AVI_CloseFile(pfile))
	 {
		//printf("Error - AVI_CloseFile()\n");
		return false;
	 }
	 
     if(! AVI_Exit())
	 {
		//printf("Error - AVI_Exit()\n");
		return false;
	 }

	 return true;
} 

