//---------------------------------------------------------------------------
//	AVFile - library for audio and video files processing
//	2002 - 2007 Stanislav Sumec <sumec@fit.vutbr.cz>
//
//	bmpfile.h
//	bmp files sequence reader and writer
//
//	28.1.2004
//		- first release
//---------------------------------------------------------------------------

#ifdef _WIN32
#include <io.h>
#else
#include <unistd.h>
#endif
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#pragma hdrstop
#include "bmpfile.h"

//---------------------------------------------------------------------------
TBMPFile::TBMPFile()
{
	RegisterFileType("bmp");
	Description = "BMP file input and output";
	Implemented = iGetFrameA + iGetFrameA2 + iDIBGetFrameA;
	Bitmap = NULL;
	FrameBuffer = NULL;
}

//---------------------------------------------------------------------------
TBMPFile::~TBMPFile()
{
	Close();
}

//---------------------------------------------------------------------------
void TBMPFile::OpenA(const char *FileName, int Mode, int Type, bool ThrowExceptions)
{
	Close();

	if (Mode == omRead)
	{
		if (Type & otVideo)
		{
			Bitmap = ReadBMP(FileName);
			if (Bitmap != NULL)
			{
				VideoInfo.Width = Bitmap->bmiHeader.biWidth;
				VideoInfo.Height = Bitmap->bmiHeader.biHeight;
				VideoInfo.FrameRate.Rate = 25;
				VideoInfo.FrameRate.Scale = 1;

				ScanLineWidth = VideoInfo.Width * 4;
				AlignedScanLineWidth = DIB_SCAN_LINE(ScanLineWidth);

				FrameBuffer = (BYTE*)GetMem(ScanLineWidth * VideoInfo.Height);
				if (FrameBuffer == NULL)
				{
					if (ThrowExceptions)
					{
						Close();
						throw AVFileErrorMemory();
					}
				}
				else
				{
					FrameInBuffer = 0;
					VideoPos = 0;
					OpenMode = Mode;
					OpenType |= otVideo;

					const char *Extension = FileName + strlen(FileName) - 1;
					while (Extension >= FileName)
					{
						if (Extension[0] == '.')
						{
							break;
						}
						Extension--;
					}
					if (Extension < FileName)
					{
						Extension = NULL;
					}
					char *Counter;
					if (Extension == NULL)
					{
						BaseFileExtension = "";
						Counter = (char*)FileName + strlen(FileName);
					}
					else
					{
						BaseFileExtension = Extension;
						Counter = (char*)Extension - 1;
					}

					BaseFileNumbers = 0;
					StartNumber = 0;
					unsigned int E = 1;
					while (Counter >= FileName && isdigit(Counter[0]))
					{
						StartNumber += E * (Counter[0] - '0');
						E *= 10;
						Counter--;
						BaseFileNumbers++;
					}

					if (Counter < FileName)
					{
						BaseFileName = "";
					}
					else
					{
						BaseFileName.assign(FileName, Counter - FileName + 1);
					}

					unsigned int IniLength = 0, IniStartNumber = 0;
					bool IniLengthSet = false, IniStartNumberSet = false;

					FILE *f = fopen((BaseFileName + ".ini").c_str(), "r");
					if (f != NULL)
					{
						int c, s = 0;
						std::string t = "", v = "";
						while ((c = getc(f)) != EOF)
						{
							switch (s)
							{
							case 0:
								if (c == '\n')
								{
									t = "";
								}
								else if (c != '=')
								{
									t += char(c);
								}
								else
								{
									s = 1;
								}
								break;

							case 1:
								if (c != '\n')
								{
									v += char(c);
								}
								else
								{
									if (t == "First")
									{
										IniStartNumber = atoi(v.c_str());
										IniStartNumberSet = true;
									}
									else if (t == "Length")
									{
										IniLength = atoi(v.c_str());
										IniLengthSet = true;
									}
									s = 0;
									t = "";
									v = "";
								}
								break;
							}
						}
						fclose(f);
					}

					if (IniStartNumberSet)
					{
						if (StartNumber != IniStartNumber)
						{
							FrameInBuffer = ~0;
							StartNumber = IniStartNumber;
						}
					}
					if (IniLengthSet)
					{
						Length = IniLength;
					}
					else
					{
						Length = 1;
						if (BaseFileNumbers > 0)
						{
							while (!access(GetFileName(Length).c_str(), 4))
							{
								Length++;
							}
						}
					}
				}
			}
		}
	}
	else if (Mode == omWrite)
	{
		if (Type & otVideo)
		{
			ScanLineWidth = VideoInfo.Width * 4;
			AlignedScanLineWidth = DIB_SCAN_LINE(ScanLineWidth);
			unsigned int BitmapSize = sizeof(BITMAPINFOHEADER) + VideoInfo.Height * AlignedScanLineWidth;
			Bitmap = (BITMAPINFO*)GetMem(BitmapSize);
			if (Bitmap == NULL)
			{
				if (ThrowExceptions)
				{
					Close();
					throw AVFileErrorMemory();
				}
			}
			else
			{
				Bitmap->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
				Bitmap->bmiHeader.biWidth = VideoInfo.Width;
				Bitmap->bmiHeader.biHeight = VideoInfo.Height;

				Bitmap->bmiHeader.biPlanes = 1;
				Bitmap->bmiHeader.biBitCount = 32;
				Bitmap->bmiHeader.biCompression = BI_RGB;
				Bitmap->bmiHeader.biSizeImage = AlignedScanLineWidth * VideoInfo.Height;
				Bitmap->bmiHeader.biXPelsPerMeter = 0;
				Bitmap->bmiHeader.biYPelsPerMeter = 0;
				Bitmap->bmiHeader.biClrUsed = 0;
				Bitmap->bmiHeader.biClrImportant = 0;

				BaseFileNumbers = 9;
				StartNumber = 0;

				const char *Extension = FileName + strlen(FileName) - 1;
				while (Extension >= FileName)
				{
					if (Extension[0] == '.')
					{
						break;
					}
					Extension--;
				}
				if (Extension < FileName)
				{
					Extension = NULL;
				}

				if (Extension == NULL)
				{
#ifdef AVFILE_STRICT_EXTS
					BaseFileExtension = "";
#else
					BaseFileExtension = ".bmp";
#endif
					BaseFileName = FileName;
				}
				else
				{
#ifdef AVFILE_STRICT_EXTS
					BaseFileExtension = Extension;
#else
					BaseFileExtension = ".bmp";
#endif					
					if (BaseFileExtension != FileName)
					{
						BaseFileName.assign(FileName, Extension - FileName);
					}
					else
					{
						BaseFileName = "";
					}
				}

				VideoPos = 0;
				Length = 0;
				OpenMode = Mode;
				OpenType |= otVideo;
			}
		}
	}

	if (ThrowExceptions)
	{
		if (Mode != OpenMode)
		{
			Close();
			throw AVFileErrorOpenInvalidMode();
		}
		if ((Type & otVideo) && !(OpenType & otVideo))
		{
			Close();
			throw AVFileErrorOpenVideo();
		}
		if ((Type & otAudio) && !(OpenType & otAudio))
		{
			Close();
			throw AVFileErrorOpenAudio();
		}
	}
}

//---------------------------------------------------------------------------
void TBMPFile::Close()
{
	if (OpenMode == omWrite && OpenType != otNone && Length > 0)
	{
		FILE *f = fopen((BaseFileName + ".ini").c_str(), "w");
		if (f != NULL)
		{
			fprintf(f, "[Parameters]\n");
			fprintf(f, "First=%d\n", StartNumber);
			fprintf(f, "Length=%d\n", Length);
			fprintf(f, "Rate=%d\n", VideoInfo.FrameRate.Rate);
			fprintf(f, "Scale=%d\n", VideoInfo.FrameRate.Scale);
			fprintf(f, "Width=%d\n", VideoInfo.Width);
			fprintf(f, "Height=%d\n", VideoInfo.Height);
			fclose(f);
		}
	}

	OpenMode = omClosed;
	OpenType = otNone;

	if (Bitmap != NULL)
	{
		FreeMem(Bitmap);
		Bitmap = NULL;
	}

	if (FrameBuffer != NULL)
	{
		FreeMem(FrameBuffer);
		FrameBuffer = NULL;
	}
}

//---------------------------------------------------------------------------
BITMAPINFO* TBMPFile::ReadBMP(const char *FileName)
{
	FILE *f = fopen(FileName, "rb");

	if (f == NULL)
	{
		return NULL;
	}

	BITMAPFILEHEADER FileHeader = {0};
	if (sizeof(BITMAPFILEHEADER) != 14)
	{
		if (fread((void*)&FileHeader.bfType, 1, sizeof(WORD), f) != sizeof(WORD))
		{
			fclose(f);
			return NULL;
		}
		if (fread((void*)&FileHeader.bfSize, 1, sizeof(DWORD), f) != sizeof(DWORD))
		{
			fclose(f);
			return NULL;
		}
		if (fread((void*)&FileHeader.bfReserved1, 1, sizeof(WORD), f) != sizeof(WORD))
		{
			fclose(f);
			return NULL;
		}
		if (fread((void*)&FileHeader.bfReserved2, 1, sizeof(WORD), f) != sizeof(WORD))
		{
			fclose(f);
			return NULL;
		}
		if (fread((void*)&FileHeader.bfOffBits, 1, sizeof(DWORD), f) != sizeof(DWORD))
		{
			fclose(f);
			return NULL;
		}
	}
	else
	{
		if (fread((void*)&FileHeader, 1, sizeof(FileHeader), f) != sizeof(FileHeader))
		{
			fclose(f);
			return NULL;
		}
	}

	if (memcmp(&FileHeader.bfType, "BM", 2))
	{
		fclose(f);
		return NULL;
	}

	BITMAPINFOHEADER BitmapHeader = {0};
	if (fread(&BitmapHeader, 1, sizeof(BitmapHeader), f) != sizeof(BitmapHeader))
	{
		fclose(f);
		return NULL;
	}

	if (BitmapHeader.biSize != sizeof(BitmapHeader) ||
		BitmapHeader.biPlanes != 1 ||
		(BitmapHeader.biBitCount != 32 && BitmapHeader.biBitCount != 24) ||
		BitmapHeader.biCompression != BI_RGB)
	{
		fclose(f);
		return NULL;
	}

	if (fseek(f, FileHeader.bfOffBits, SEEK_SET) < 0)
	{
		fclose(f);
		return NULL;
	}

	int OrigBitCount = BitmapHeader.biBitCount;
	BitmapHeader.biBitCount = 32;
	BitmapHeader.biSizeImage = BitmapHeader.biHeight * DIB_SCAN_LINE(BitmapHeader.biWidth * 4);
	unsigned int BitmapSize = sizeof(BitmapHeader) + BitmapHeader.biSizeImage;
	BYTE *Bitmap = (BYTE*)GetMem(BitmapSize);
	if (Bitmap == NULL)
	{
		fclose(f);
		return NULL;
	}
	memcpy(Bitmap, &BitmapHeader, sizeof(BitmapHeader));

	if (OrigBitCount == 24)
	{
		int TempBitmapSize = BitmapHeader.biHeight * DIB_SCAN_LINE(BitmapHeader.biWidth * 3);
		BYTE *TempBitmap = (BYTE*)GetMem(BitmapSize);
		if (TempBitmap == NULL)
		{
			FreeMem(Bitmap);
			fclose(f);
			return NULL;
		}

		if (fread(TempBitmap, 1, TempBitmapSize, f) != (size_t)TempBitmapSize)
		{
			FreeMem(TempBitmap);
			FreeMem(Bitmap);
			fclose(f);
			return NULL;
		}

		int *Dst = (int*)(Bitmap + sizeof(BitmapHeader));
		BYTE TransparentColor[3];
		memcpy(TransparentColor, TempBitmap, 3);
		for (int y = 0; y < BitmapHeader.biHeight; y++)
		{
			BYTE *Src = TempBitmap + y * DIB_SCAN_LINE(BitmapHeader.biWidth * 3);
			for (int x = 0; x < BitmapHeader.biWidth; x++)
			{
				BYTE Alpha = memcmp(Src, TransparentColor, 3) ? 0 : 255;
				*Dst = Src[0] | (Src[1] << 8) | (Src[2] << 16) | (Alpha << 24);
				Src += 3;
				Dst++;
			}
		}
		FreeMem(TempBitmap);
	}
	else
	{
		if (fread(Bitmap + sizeof(BitmapHeader), 1, BitmapHeader.biSizeImage, f) != (size_t)BitmapHeader.biSizeImage)
		{
			FreeMem(Bitmap);
			fclose(f);
			return NULL;
		}
	}

	fclose(f);
	return (BITMAPINFO*)Bitmap;
}

//---------------------------------------------------------------------------
bool TBMPFile::WriteBMP(const char *FileName, BITMAPINFO *Bitmap)
{
	FILE *f = fopen(FileName, "wb");

	if (f == NULL)
	{
		return false;
	}

	BITMAPFILEHEADER FileHeader = {0};
	unsigned int BitmapSize = Bitmap->bmiHeader.biSize + Bitmap->bmiHeader.biSizeImage;

	if (sizeof(BITMAPFILEHEADER) != 14)
	{
		WORD w;
		DWORD dw;
		memcpy(&w, "BM", 2);
		if (fwrite(&w, 1, sizeof(WORD), f) != sizeof(WORD))
		{
			fclose(f);
			return false;
		}
		dw = sizeof(FileHeader) + BitmapSize;
		if (fwrite(&dw, 1, sizeof(DWORD), f) != sizeof(DWORD))
		{
			fclose(f);
			return false;
		}
		w = 0;
		if (fwrite(&w, 1, sizeof(WORD), f) != sizeof(WORD))
		{
			fclose(f);
			return false;
		}
		if (fwrite(&w, 1, sizeof(WORD), f) != sizeof(WORD))
		{
			fclose(f);
			return false;
		}
		dw = sizeof(FileHeader) + Bitmap->bmiHeader.biSize;
		if (fwrite(&dw, 1, sizeof(DWORD), f) != sizeof(DWORD))
		{
			fclose(f);
			return false;
		}
	}
	else
	{
		memcpy(&FileHeader.bfType, "BM", 2);
		FileHeader.bfSize = (DWORD)(sizeof(FileHeader) + BitmapSize);
		FileHeader.bfReserved1 = (WORD)0;
		FileHeader.bfReserved2 = (WORD)0;
		FileHeader.bfOffBits = (DWORD)(sizeof(FileHeader) + Bitmap->bmiHeader.biSize);

		if (fwrite(&FileHeader, 1, sizeof(FileHeader), f) != sizeof(FileHeader))
		{
			fclose(f);
			return false;
		}
	}

	if (fwrite(Bitmap, 1, BitmapSize, f) != (size_t)BitmapSize)
	{
		fclose(f);
		return false;
	}

	fclose(f);
	return true;
}

//---------------------------------------------------------------------------
std::string TBMPFile::GetFileName(unsigned int Position)
{
	std::string FileName;
	if (BaseFileNumbers > 0)
	{
		unsigned int Number = StartNumber + Position;

		unsigned int Count = 0;
		while (true)
		{
			Count++;
			FileName = char('0' + Number % 10) + FileName;
			if (Number < 10)
			{
				break;
			}
			Number /= 10;
		}

		while (Count < BaseFileNumbers)
		{
			FileName = '0' + FileName;
			Count++;
		}
	}

	FileName = BaseFileName + FileName + BaseFileExtension;
	return FileName;
}

//---------------------------------------------------------------------------
unsigned int TBMPFile::GetVideoLength()
{
	CheckOpened();
	CheckVideoStream();
	return Length;
}

//---------------------------------------------------------------------------
BITMAPINFO* TBMPFile::DIBGetFrameA()
{
	CheckRead();
	CheckVideoStream();

	if (Bitmap != NULL)
	{
		if (VideoPos != FrameInBuffer)
		{
			FreeMem(Bitmap);
			Bitmap = NULL;
		}
	}

	if (Bitmap == NULL)
	{
		BITMAPINFO *NewBitmap = ReadBMP(GetFileName(VideoPos).c_str());
		if (NewBitmap == NULL || VideoInfo.Width != NewBitmap->bmiHeader.biWidth || VideoInfo.Height != NewBitmap->bmiHeader.biHeight)
		{
			if (NewBitmap != NULL)
			{
				FreeMem(NewBitmap);
			}
			throw AVFileErrorGetVideo();
		}
		Bitmap = NewBitmap;
		FrameInBuffer = VideoPos;
	}

	VideoPos++;
	return Bitmap;
}

//---------------------------------------------------------------------------
BYTE* TBMPFile::GetFrameA()
{
	BITMAPINFO *Bitmap = DIBGetFrameA();
	BYTE *Src = (BYTE*)Bitmap + Bitmap->bmiHeader.biSize + Bitmap->bmiHeader.biSizeImage;
	BYTE *Dst = FrameBuffer;
	for (int i = 0; i < VideoInfo.Height; i++)
	{
		Src -= AlignedScanLineWidth;
		memcpy(Dst, Src, ScanLineWidth);
		Dst += ScanLineWidth;
	}
	return FrameBuffer;
}

//---------------------------------------------------------------------------
void TBMPFile::GetFrameA2(BYTE *Buffer)
{
	BITMAPINFO *Bitmap = DIBGetFrameA();
	BYTE *Src = (BYTE*)Bitmap + Bitmap->bmiHeader.biSize + Bitmap->bmiHeader.biSizeImage;
	BYTE *Dst = Buffer;
	for (int i = 0; i < VideoInfo.Height; i++)
	{
		Src -= AlignedScanLineWidth;
		memcpy(Dst, Src, ScanLineWidth);
		Dst += ScanLineWidth;
	}
}

//---------------------------------------------------------------------------
void TBMPFile::PutFrame(BYTE *Buffer)
{
	CheckWrite();
	CheckVideoStream();

	BYTE *Src = Buffer;
	BYTE *Dst = (BYTE*)Bitmap + Bitmap->bmiHeader.biSize + Bitmap->bmiHeader.biSizeImage;
	for (int i = 0; i < VideoInfo.Height; i++)
	{
		Dst -= AlignedScanLineWidth;
		if (AlignedScanLineWidth > ScanLineWidth)
		{
			memcpy(Dst, Src, ScanLineWidth);
			memset(Dst + ScanLineWidth, 0, AlignedScanLineWidth - ScanLineWidth);
		}
		else
		{
			memcpy(Dst, Src, AlignedScanLineWidth);
		}
		Src += ScanLineWidth;
	}

	if (!WriteBMP(GetFileName(VideoPos).c_str(), Bitmap))
	{
		throw AVFileErrorPutVideo();
	}

	VideoPos++;
	if (VideoPos >= Length)
	{
		Length = VideoPos;
	}
}

