/*  $Id: pcx_provider_generic.cpp,v 1.9 2004/01/24 21:52:07 mbn Exp $
**
**  ClanLib Game SDK
**  Copyright (C) 2003  The ClanLib Team
**  For a total list of contributers see the file CREDITS.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This library 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
**  Lesser General Public License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
*/

#include "Display/display_precomp.h"
#include "pcx_provider_generic.h"
#include "API/Core/System/error.h"

/////////////////////////////////////////////////////////////////////////////
// CL_PCXProvider_Generic construction:

CL_PCXProvider_Generic::CL_PCXProvider_Generic(
	std::string _name, 
	CL_InputSourceProvider *_provider)
{
	CL_InputSourceProvider *provider = _provider != NULL ? _provider->clone() : CL_InputSourceProvider::create_file_provider(".");
	cl_assert(provider != NULL);
	
	image = NULL;

	CL_InputSource *datafile = provider->open_source(_name.c_str());
	try
	{
		read_pcx(_name, datafile);
	}
	catch (...)
	{
		delete datafile;
		delete provider;
		throw;
	}
	delete datafile;
	delete provider;
}

CL_PCXProvider_Generic::~CL_PCXProvider_Generic()
{
	delete[] image;
	image = NULL;
}

/////////////////////////////////////////////////////////////////////////////
// CL_PCXProvider_Generic implementation:

void CL_PCXProvider_Generic::read_pcx(
	std::string _name, 
	CL_InputSource *_datafile)
{
	// make sure its valid
	cl_assert(_datafile != NULL);
	
	// Read the file header and initialize the variables
	//: This method was created to breakup the PCX decoding code
	//: so the initializing code was separated from the actual
	//: decoding algorithm

	unsigned char header[128];  

	{
		int read = _datafile->read(header, 128);
		if (read != 128)
			throw CL_Error("File not big enough to read the PCX header");
	}

	// only the useful fields that will be used more than once
	struct PCXHeader {
		unsigned char version;
		unsigned char encoding;
		unsigned char bits_per_pixel_per_plane;
		short xmin;
		short ymin;
		short xmax;
		short ymax;
		unsigned char color_map[48];
		unsigned char nplanes;
		unsigned short bytes_per_line;
	} pcx_header;

	if (header[0] != 10)
		throw CL_Error("Not a PCX file");

	pcx_header.version = header[1];
	pcx_header.encoding = header[2];
	pcx_header.bits_per_pixel_per_plane = header[3];
	pcx_header.xmin = (header[5] << 8) | header[4];	
	pcx_header.ymin = (header[7] << 8) | header[6];
	pcx_header.xmax = (header[9] << 8) | header[8];
	pcx_header.ymax = (header[11] << 8) | header[10];

	const int width = (pcx_header.xmax - pcx_header.xmin) + 1;
	const int height = (pcx_header.ymax - pcx_header.ymin) + 1;

	memcpy(pcx_header.color_map, &header[16], sizeof(pcx_header.color_map));
	pcx_header.nplanes = header[65];
	pcx_header.bytes_per_line = (header[67] << 8) | header[66];

	// both 8bit with palette and 24bit rgb modes require version 5
	if (pcx_header.version < 5)
		throw CL_Error("PCX version unsupported");

	const int total_line_bytes = pcx_header.bytes_per_line * pcx_header.nplanes;

	int pitch = 0;

	// decide which pixelformat to use (8 bit palette or 24 bit)
	if (pcx_header.nplanes == 3 && pcx_header.bits_per_pixel_per_plane == 8)
	{
		// 24 bit RGB mode
		pitch = width * 3;
		image = new unsigned char[pitch * height];
		cl_assert(image != NULL);

		unsigned char *p = image;
		const int extra = total_line_bytes - pitch;

		// get the data
		if (!pcx_header.encoding)
		{
			// uncompressed
			for (int y = 0; y < height; y++)
			{
				_datafile->read(p, pitch);
				if (extra > 0)
					_datafile->seek(extra, CL_InputSource::seek_cur);
				p += pitch;
			}
		}
		else
		{
			// compressed
			for (int y = 0; y < height; y++)
			{
				int count;
				// first red component
				int x = 0, xx = 0, comp = 0;
				while (x < total_line_bytes)
				{
					int ch = _datafile->read_uint8();
					if ((ch & 0xC0) == 0xC0)
					{
						count = ch & 0x3F;
						ch = _datafile->read_uint8();
					}
					else
						count = 1;

					while (count--)
					{
						if (xx < width)
							image[(y * pitch) + ((xx * 3) + comp)] = ch;
						x++;
						if (x == pcx_header.bytes_per_line)
						{
							// blue component now
							comp = 1;
							xx = 0;
						}
						else if (x == pcx_header.bytes_per_line*2)
						{
							// green component now
							comp = 2;
							xx = 0;
						}
						else
							xx++;
					}
				}
			}
		}
		CL_PixelBuffer_Generic::format.enable_colorkey(false);
		CL_PixelBuffer_Generic::format.set_colorkey(0);
		CL_PixelBuffer_Generic::format.set_depth(24);
		CL_PixelBuffer_Generic::format.set_red_mask(0x0000ff);
		CL_PixelBuffer_Generic::format.set_green_mask(0x00ff00);
		CL_PixelBuffer_Generic::format.set_blue_mask(0xff0000);
		CL_PixelBuffer_Generic::format.set_alpha_mask(0);
		CL_PixelBuffer_Generic::pitch = pitch;
		CL_PixelBuffer_Generic::width = width;
		CL_PixelBuffer_Generic::height = height;
	}
	else if (pcx_header.nplanes == 1 && pcx_header.bits_per_pixel_per_plane == 8)
	{
		// 8 bit indexed mode
		pitch = width;
		image = new unsigned char[pitch * height];
		cl_assert(image != NULL);

		unsigned char *p = image;
		const int extra = total_line_bytes - pitch;

		// get the data
		if (!pcx_header.encoding)
		{
			// uncompressed
			for (int y = 0; y < height; y++)
			{
				_datafile->read(p, pitch);
				if (extra > 0)
					_datafile->seek(extra, CL_InputSource::seek_cur);
				p += pitch;
			}
		}
		else
		{
			// compressed
			for (int y = 0; y < height; y++)
			{
				int count;
				int x = 0;
				while (x < total_line_bytes)
				{
					int ch = _datafile->read_uint8();
					if ((ch & 0xC0) == 0xC0)
					{
						count = ch & 0x3F;
						ch = _datafile->read_uint8();
					}
					else
						count = 1;
					
					while (count--)
					{
						if (x < width)
						{
							*p = ch;
							p++;
						}
						x++;
					}
				}
			}
		}

		// read the palette
		if (_datafile->read_int8() != 12)
		{
			delete[] image;
			throw CL_Error("Palette not found");
		}
		for (int i = 0; i < 256; i++)
		{
			const int r = _datafile->read_uint8();
			const int g = _datafile->read_uint8();
			const int b = _datafile->read_uint8();
			palette[i].set_color(r, g, b);
		}

		CL_PixelBuffer_Generic::format.enable_colorkey(false);
		CL_PixelBuffer_Generic::format.set_colorkey(0);
		CL_PixelBuffer_Generic::format.set_depth(8);
		CL_PixelBuffer_Generic::format.set_type(pixelformat_index);
		CL_PixelBuffer_Generic::pitch = pitch;
		CL_PixelBuffer_Generic::width = width;
		CL_PixelBuffer_Generic::height = height;
	}
	else
		throw CL_Error("Unsupported PCX format");
}
