/*
	Twilight Prophecy 3D/Multimedia SDK
	A multi-platform development system for virtual reality and multimedia.

	Copyright (C) 1997-2001 by Twilight 3D Finland Oy Ltd.

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

	Please read the file LICENSE.TXT for additional details.


	source: 
		BitmapCodec "bmp"

	revision history:
		Dec/02/2000 - Jukka Liimatta - initial revision
		Jan/24/2001 - Jukka Liimatta - renaissance build

	todo:
	- 4bit win32 rle decoder
	- topdown images (negative height)
*/
#include <prcore/prcore.hpp>
using namespace prcore;



//////////////////////////////////////////////////////
// codec                                           //
////////////////////////////////////////////////////

static int NumEXT()
{
	return 1;
}


static const char* GetEXT(int index)
{
	return "bmp";
}


static bool IsDecoder()
{
	return true;
}


static bool IsEncoder()
{
	return false;
}


static bool Decode(Bitmap& target, Stream& stream)
{
	// reset stream
	stream.Seek(0,Stream::START);

	// read header
	uint16 signature = ReadLittleEndian<uint16>(stream);
	if ( signature != PRCORE_CODE16('B','M') )
		return false;

	ReadLittleEndian<uint32>(stream); // filesize
	ReadLittleEndian<uint32>(stream); // reserved
	uint32 dataoffset = ReadLittleEndian<uint32>(stream);

	uint32 size = ReadLittleEndian<uint32>(stream);
	uint32 width = 0;
	uint32 height = 0;
	uint16 planes = 0;
	uint16 bitcount = 0;

	// compression
	enum bi_type
	{
		bi_rgb = 0,
		bi_rle4 = 1,
		bi_rle8 = 2,
		bi_bitfields = 3
	};
	uint32 compression = bi_rgb;

	bool OS2 = false;
	if ( size == 40 )
	{
		// win32 bmp
		width = ReadLittleEndian<uint32>(stream);
		height = ReadLittleEndian<uint32>(stream);
		planes = ReadLittleEndian<uint16>(stream);
		bitcount = ReadLittleEndian<uint16>(stream);
		compression = ReadLittleEndian<uint32>(stream);

		ReadLittleEndian<uint32>(stream); // imagesize
		ReadLittleEndian<uint32>(stream); // xres
		ReadLittleEndian<uint32>(stream); // yres
		ReadLittleEndian<uint32>(stream); // colorused
		ReadLittleEndian<uint32>(stream); // colorimportant
	}
	else if ( (size == 12) || (size == 240) )
	{
		// OS/2 bmp
		OS2 = true;
		width = ReadLittleEndian<uint16>(stream);
		height = ReadLittleEndian<uint16>(stream);
		planes = ReadLittleEndian<uint16>(stream);
		bitcount = ReadLittleEndian<uint16>(stream);
	}
	else
	{
		return false;
	}



	int palsize = 0;
	int pitch = 0;

	// pixelformat
	PixelFormat pxf;
	switch ( bitcount )
	{
		case 1:
			pxf = PixelFormat(PALETTE8(NULL));
			palsize = 2;
			pitch = (width+7) >> 3;
			break;

		case 4:
			pxf = PixelFormat(PALETTE8(NULL));
			palsize = 16;
			pitch = (width+3) >> 2;
			break;

		case 8:
			pxf = PixelFormat(PALETTE8(NULL));
			palsize = 256;
			pitch = width;
			break;

		case 16:
			pxf = PixelFormat(RGB565);
			palsize = 0;
			pitch = width * 2;
			break;

		case 24:
			pxf = PixelFormat(RGB888);
			palsize = 0;
			pitch = width * 3;
			break;

		case 32:
			pxf = PixelFormat(ARGB8888);
			palsize = 0;
			pitch = width * 4;
			break;

		default:
			return false;
	}

	// read colormap
	Color32* palette = pxf.GetPalette();
	for ( int i=0; i<palsize; i++ )
	{
		Color32 color;
		color.b = ReadLittleEndian<uint8>(stream);
		color.g = ReadLittleEndian<uint8>(stream);
		color.r = ReadLittleEndian<uint8>(stream);
		color.a = OS2 ? 0xff : ReadLittleEndian<uint8>(stream);

		if ( palette )
			*palette++ = color;
	}

	// 16bit and 32bit color masks ( optional field in the stream )
	if ( (compression!=bi_rgb) && (bitcount==16 || bitcount==32) )
	{
		uint32 rmask = ReadLittleEndian<uint32>(stream);
		uint32 gmask = ReadLittleEndian<uint32>(stream);
		uint32 bmask = ReadLittleEndian<uint32>(stream);
		uint32 amask = ~(rmask|gmask|bmask);

		pxf = PixelFormat(bitcount,rmask,gmask,bmask,amask);
	}

	// initialize decode target
	target = Bitmap(width,height,pxf);

	// 32bit alignment adjust
	int align = ((pitch+3) & 0xfffffffc) - pitch;

	// seek to image
	stream.Seek(dataoffset,Stream::START);

	// read image
	if ( compression != bi_rgb )
	{
		// ================================
		// compression: RLE
		// ================================
		switch ( bitcount )
		{
			case 4:
			{
				//TODO: write support for 4bit RLE compressed bmp's
				break;
			}

			case 8:
			{
				memset( target.GetImage(), 0, width*height );
				int y = (height-1) * 2 - 1;
				while ( y > 0 )
				{
					int x = 0;
					while ( x < (int)width )
					{
						uint16 data;
						stream.Read(&data,sizeof(data));
						uint8 n = data & 0xff;
						uint8 c = data >> 8;

						if ( n )
						{
							char* buffer = (char*)target.GetImage() + pitch*(y/2) + x;
							for ( int i=0; i<n; i++ )
								*buffer++ = c;
							x += n;
						}
						else
						{
							switch ( c )
							{
								case 0:
									x = width;
									break;
		
								case 1:
									y = 0;
									x = width;
									break;

								case 2:
								{
									uint16 delta;
									stream.Read(&delta,sizeof(delta));
									x += (delta & 0xff);
									y -= (delta >> 8);
									break;
								}

								default:
								{
									char* buffer = (char*)target.GetImage() + pitch*(y/2) + x;
									stream.Read(buffer,c);
									x += c;

									if ( c & 1 )
										stream.Seek(1,Stream::CURRENT);
									break;
								}
							}
						}
					}
					--y;
				}
				break;
			}

			default:
				return false;
		}
	}
	else
	{
		// ================================
		// compression: LINEAR
		// ================================
		switch ( bitcount )
		{
			case 1:
			case 4:
			{
				for ( int y=0; y<(int)height; y++ )
				{
					char* dest = (char*)target.GetImage() + (height-y-1)*target.GetWidth();

					int bits = bitcount;
					uint32 data = 0;
					uint32 mask = (1 << bitcount) - 1;
					for ( int x=0; x<(int)width; x++ )
					{
						bits -= bitcount;
						if ( !bits )
						{
							stream.Read(&data,sizeof(data));
							ByteSwap(data);
							bits = 32;
						}
						*dest++ = (data>>(bits-bitcount)) & mask;
					}
				}
				break;
			}

			case 8:
			case 16:
			case 24:
			case 32:
			{
				for ( int y=0; y<(int)height; y++ )
				{
					char* dest = (char*)target.GetImage() + (height-y-1)*pitch;
					stream.Read(dest,pitch);

					if ( align )
						stream.Seek(align,Stream::CURRENT);
				}
				break;
			}

			default:
				return false;
		}
	}

	return true;
}


static bool Encode(Stream& target, Surface& surface)
{
	return false;
}


//////////////////////////////////////////////////////
// factory                                         //
////////////////////////////////////////////////////

BitmapCodec CreateCodecBMP()
{
	BitmapCodec codec;
	
	codec.NumEXT = NumEXT;
	codec.GetEXT = GetEXT;
	codec.IsDecoder = IsDecoder;
	codec.IsEncoder = IsEncoder;
	codec.Decode = Decode;
	codec.Encode = Encode;
	
	return codec;
}
