/*
	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 "pcx"

	revision history:
		Jan/24/2001 - Jukka Liimatta - initial revision/renaissance build
*/
#include <prcore/prcore.hpp>
using namespace prcore;



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

static int NumEXT()
{
	return 1;
}


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


static bool IsDecoder()
{
	return true;
}


static bool IsEncoder()
{
	return false;
}


static bool Decode(Bitmap& target, Stream& stream)
{
	
	struct HeaderPCX
	{
		uint8		Manufacturer;
		uint8		Version;
		uint8		Encoding;
		uint8		BitsPerPixel;
		uint16		Xmin, Ymin, Xmax, Ymax;
		uint16		HDpi, VDpi;
		uint8		Colormap[48];
		uint8		Reserved;
		uint8		NPlanes;
		uint16		BytesPerLine;
		uint16		PaletteInfo;
		uint16		HscreenSize;
		uint16		VscreenSize;
		uint8		Filler[54];
	};

	// reset stream
	stream.Seek(0,Stream::START);

	// read header
	HeaderPCX header;
	header.Manufacturer = ReadLittleEndian<int8>(stream);
	header.Version = ReadLittleEndian<int8>(stream);
	header.Encoding = ReadLittleEndian<int8>(stream);
	header.BitsPerPixel = ReadLittleEndian<int8>(stream);
	header.Xmin = ReadLittleEndian<int16>(stream);
	header.Ymin = ReadLittleEndian<int16>(stream);
	header.Xmax = ReadLittleEndian<int16>(stream);
	header.Ymax = ReadLittleEndian<int16>(stream);
	header.HDpi = ReadLittleEndian<int16>(stream);
	header.VDpi = ReadLittleEndian<int16>(stream);
	stream.Read(header.Colormap,48);
	stream.Seek(1,Stream::CURRENT);
	header.NPlanes = ReadLittleEndian<int8>(stream);
	header.BytesPerLine = ReadLittleEndian<int16>(stream);
	header.PaletteInfo = ReadLittleEndian<int16>(stream);
	header.HscreenSize = ReadLittleEndian<int16>(stream);
	header.VscreenSize = ReadLittleEndian<int16>(stream);
	stream.Seek(54,Stream::CURRENT);

	// verify header
	if ( (header.Manufacturer!=10) || (header.NPlanes!=1 && header.NPlanes!=3) )
		return false;

	// dimensions
	int width = header.Xmax - header.Xmin + 1;
	int height = header.Ymax - header.Ymin + 1;

	// decode 8bit
	if ( header.NPlanes == 1 )
	{
		target = Bitmap(width,height,PixelFormat(PALETTE8(NULL)));
		uint8* dest = target.GetImage();

		// read image
		for ( int y=0; y<height; y++ )
		{
			// decode rle
			uint8* scanend = dest + width;
			for ( uint8* scan=dest; scan<scanend; )
			{
				uint8 sample = ReadLittleEndian<uint8>(stream);
				if ( sample < 0xc0 )
					*scan++ = sample;
				else
				{
					int count = sample & 0x3f;
					sample = ReadLittleEndian<uint8>(stream);
					memset(scan, sample, count);
					scan += count;
				}
			}
			dest += width;
		}

		// read palette
		ReadLittleEndian<uint8>(stream);

		const PixelFormat& pxf = target.GetFormat();
		Color32* palette = pxf.GetPalette();

		for ( int i=0; i<256; i++ )
		{
			palette[i].r = ReadLittleEndian<uint8>(stream);
			palette[i].g = ReadLittleEndian<uint8>(stream);
			palette[i].b = ReadLittleEndian<uint8>(stream);
			palette[i].a = 0xff;
		}
	}

	// decode 24bit
	if ( header.NPlanes == 3 )
	{
		target = Bitmap(width,height,PixelFormat(RGB888));
		uint8* dest = target.GetImage();
		uint8* scanbuf = new uint8[width * 3];

		// read image
		for ( int y=0; y<height; y++ )
		{
			// decode interleaved rle
			uint8* scanend = scanbuf + width * 3;
			for ( uint8* scan=scanbuf; scan<scanend; )
			{
				uint8 sample = ReadLittleEndian<uint8>(stream);
				if ( sample < 0xc0 )
				{
					*scan++ = sample;
				}
				else
				{
					int count = sample & 0x3f;
					sample = ReadLittleEndian<uint8>(stream);
					memset(scan, sample, count);
					scan += count;
				}
			}

			// interleaved -> packed conversion
			for ( uint8* src = scanbuf; src<(scanbuf+width); src++,dest+=3 )
			{
				dest[0] = src[width*2];
				dest[1] = src[width];
				dest[2] = src[0];
			}
		}

		delete[] scanbuf;
	}

	return true;
}


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


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

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