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

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



//////////////////////////////////////////////////////
// tga header                                      //
////////////////////////////////////////////////////

struct HeaderTGA
{
	uint8		idfield_length;
	uint8		colormap_type;
	uint8		data_type;
	uint16		colormap_origin;
	uint16		colormap_length;
	uint8		colormap_bitsize;
	uint16		image_origin_x;
	uint16		image_origin_y;
	uint16		image_width;
	uint16		image_height;
	uint8		pixelsize;
	uint8		descriptor;
};


static bool ReadHeader(HeaderTGA& header, Stream& stream)
{
	// read header
	header.idfield_length = ReadLittleEndian<uint8>(stream);
	header.colormap_type = ReadLittleEndian<uint8>(stream);
	header.data_type = ReadLittleEndian<uint8>(stream);
	header.colormap_origin = ReadLittleEndian<uint16>(stream);
	header.colormap_length = ReadLittleEndian<uint16>(stream);
	header.colormap_bitsize = ReadLittleEndian<uint8>(stream);
	header.image_origin_x = ReadLittleEndian<uint16>(stream);
	header.image_origin_y = ReadLittleEndian<uint16>(stream);
	header.image_width = ReadLittleEndian<uint16>(stream);
	header.image_height = ReadLittleEndian<uint16>(stream);
	header.pixelsize = ReadLittleEndian<uint8>(stream);
	header.descriptor = ReadLittleEndian<uint8>(stream);

	// validate header
	switch ( header.data_type )
	{
		case 1:
		case 2:
		case 9:
		case 10: break;
		default: return false;
	}
	switch ( header.pixelsize )
	{
		case 8:
		case 16:
		case 24:
		case 32: break;
		default: return false;
	}

	if ( header.colormap_type > 1 )
		return false;

	if (( header.data_type == 1 || header.data_type == 9 ) && 
		( header.colormap_bitsize != 24 || header.colormap_length > 256 ))
		return false;

	// everything seems to be in order
	return true;
}


static void WriteHeader(Stream& stream, const HeaderTGA& header)
{
	WriteLittleEndian<uint8>( stream, header.idfield_length );
	WriteLittleEndian<uint8>( stream, header.colormap_type );
	WriteLittleEndian<uint8>( stream, header.data_type );
	WriteLittleEndian<uint16>( stream, header.colormap_origin );
	WriteLittleEndian<uint16>( stream, header.colormap_length );
	WriteLittleEndian<uint8>( stream, header.colormap_bitsize );
	WriteLittleEndian<uint16>( stream, header.image_origin_x );
	WriteLittleEndian<uint16>( stream, header.image_origin_y );
	WriteLittleEndian<uint16>( stream, header.image_width );
	WriteLittleEndian<uint16>( stream, header.image_height );
	WriteLittleEndian<uint8>( stream, header.pixelsize );
	WriteLittleEndian<uint8>( stream, header.descriptor );
}


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

static int NumEXT()
{
	return 1;
}


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


static bool IsDecoder()
{
	return true;
}


static bool IsEncoder()
{
	return true;
}


static void UnpackRLE(char* dest, Stream& stream, int width, int depth)
{
	while ( width > 0 )
	{
		uint8 sample = ReadLittleEndian<uint8>(stream);
		uint8 count = (sample & 0x7f) + 1;

		if ( sample & 0x80 )
		{
			char color[4];
			stream.Read(color,depth);
			for ( int i=0; i<(int)count; i++ )
				for ( int j=0; j<depth; j++ )
					*dest++ = color[j];
		}
		else
		{
			stream.Read(dest,count*depth);
			dest += count*depth;
		}

		width -= count;
	}
}


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

	// read header
	HeaderTGA header;
	if ( !ReadHeader(header,stream) )
		return false;

	// skip idfield
	stream.Seek(header.idfield_length,Stream::CURRENT);

	// information
	PixelFormat format;
	switch ( header.pixelsize )
	{
		case 8:		format = PixelFormat( PALETTE8(NULL) ); break;
		case 16:	format = PixelFormat( ARGB1555 ); break;
		case 24:	format = PixelFormat( RGB888 ); break;
		case 32:	format = PixelFormat( ARGB8888 ); break;
	}

	// read palette
	switch ( header.data_type )
	{
		case 2:
		case 10:
		{
			int delta = header.colormap_length * ((header.colormap_bitsize + 1) >> 3);
			stream.Seek(delta,Stream::CURRENT);
			break;
		}

		case 1:
		case 9:
		{
			Color32* palette = format.GetPalette();
			for ( int i=0; i<(int)header.colormap_length; i++ )
			{
				palette[i].b = ReadLittleEndian<uint8>(stream);
				palette[i].g = ReadLittleEndian<uint8>(stream);
				palette[i].r = ReadLittleEndian<uint8>(stream);
				palette[i].a = 0xff;
			}
			break;
		}
	}

	// setup info
	int width = header.image_width;
	int height = header.image_height;
	int depth = format.GetBytes();
	int pitch = width * depth;

	// set image
	char* image = new char[ height * pitch ];
	target.SetImage( width, height, format, image );

	// image orientation
	char* scan = (header.descriptor & 32) ? image : image + (height-1)*pitch;
	int nextscan = (header.descriptor & 32) ? pitch : -pitch;

	// read image
	for ( int i=0; i<height; i++, scan+=nextscan )
	{
		// decode scanline
		switch ( header.data_type )
		{
			case 1: stream.Read(scan,pitch); break; // indexed 8bit (linear)
			case 2:	stream.Read(scan,pitch); break; // rgb 16,24,32bit (linear)
			case 9: UnpackRLE(scan,stream,width,depth); break; // indexed 8bit (rle)
			case 10: UnpackRLE(scan,stream,width,depth); break; // rgb 16,24,32bit (rle)
		}
	}

	return true;
}


static bool Encode(Stream& target, Surface& surface)
{
	// choose pixelformat
	PixelFormat format = surface.GetFormat();
	if ( !format.IsIndexed() )
	{
		// supported DirectColor formats
		switch ( format.GetBits() )
		{
			case 8:
			case 16:	format = PixelFormat(ARGB1555);	break;
			case 24:	format = PixelFormat(RGB888); break;
			case 32:	format = PixelFormat(ARGB8888);	break;
		}
	}

	// information
	uint16 width = surface.GetWidth();
	uint16 height = surface.GetHeight();
	uint8 bits = format.GetBits();
	uint8 alpha = (bits==32) ? 8 : 0;
	Color32* palette = format.IsIndexed() ? format.GetPalette() : NULL;

	// create encode bitmap
	Bitmap tga(width,height,format);
	tga.Blit(surface,Surface::BLIT_COPY);

	// setup header
	HeaderTGA header;
	if ( palette )
	{
		header.colormap_type = 1;
		header.data_type = 1;
		header.colormap_origin = 0;
		header.colormap_length = 256;
		header.colormap_bitsize = 24;
	}
	else
	{
		header.colormap_type = 0;
		header.data_type = 2;
		header.colormap_origin = 0;
		header.colormap_length = 0;
		header.colormap_bitsize = 0;
	}
	header.idfield_length = 0;
	header.image_origin_x = 0;
	header.image_origin_y = 0;
	header.image_width = width;
	header.image_height = height;
	header.pixelsize = bits;
	header.descriptor = 0x20 | alpha;

	// write header
	WriteHeader(target,header);

	// write palette
	if ( palette )
	{
		for ( int i=0; i<256; i++ )
		{
			WriteLittleEndian<uint8>( target, palette->b );
			WriteLittleEndian<uint8>( target, palette->g );
			WriteLittleEndian<uint8>( target, palette->r );
			++palette;
		}
	}

	// write image
	target.Write( tga.GetImage(), width*height*format.GetBytes() );

	return true;
}


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

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