#include "ProxyHeaders.hpp"

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Proxies::WriteTargaFile(
		const char* filename,
		int width,
		int height,
		DynamicArrayOf<TargaColor> &data
	)
{
	Check_Pointer(filename);
	Check_Object(&data);

	//
	//--------------------------------------------- 
	// Get the size of the image we will be copying
	//---------------------------------------------
	//
	TargaHeader targa_header;
	memset(&targa_header, 0, sizeof(targa_header));
	targa_header.widthLow = static_cast<unsigned char>(width & 255);
	targa_header.widthHigh = static_cast<unsigned char>(width >> 8);
	targa_header.heightLow = static_cast<unsigned char>(height & 255);
	targa_header.heightHigh = static_cast<unsigned char>(height >> 8);
	targa_header.imageType = TargaHeader::RLERGB;
	targa_header.pixelSize = 24;
	targa_header.flags = TargaHeader::YReversed;

	//
	//---------------------
	// Write out the header
	//---------------------
	//
	Check_Object(FileStreamManager::Instance);
	FileStream targa_file(filename, FileStream::WriteOnly);
	targa_file.WriteBytes(&targa_header, sizeof(targa_header));

	//
	//----------------------------------------------
	// See if we need to deal with the alpha channel
	//----------------------------------------------
	//
	unsigned channel_size = height * width;
	Verify(channel_size == data.GetLength());
	unsigned pixel_size = 3;
	unsigned buffer_size = channel_size * pixel_size;
	DynamicArrayOf<BYTE> buffer(buffer_size);
	unsigned p = 0;
	unsigned max_row = Min(width, 128);

	//
	//-------------------------------------
	// Start trying to make an RLE encoding
	//-------------------------------------
	//
	int i=0;
	while (i<channel_size)
	{
		unsigned rle_count;
		int rle_type = 0;
		unsigned j = i+1;
		unsigned base = i;
		unsigned max_run = max_row - (i%max_row);
		for (rle_count = 1; rle_count<max_run && j<channel_size; ++rle_count, ++j)
		{
			//
			//-------------------------------------
			// See if the next pixel matches or not
			//-------------------------------------
			//
			bool match =
				data[j].red == data[base].red
				 && data[j].blue == data[base].blue
				 && data[j].green == data[base].green;

			//
			//----------------------------------------------------------------
			// If it matches, we break the run if this is a singleton run.  We
			// will always try to match matching pixels in a run
			//----------------------------------------------------------------
			//
			if (match)
			{
				if (rle_type == -1)
				{
					--j;
					--rle_count;
					break;
				}

				//
				//----------------------------------------------------
				// Matching runs can go one longer than singleton runs
				//----------------------------------------------------
				//
				else if (!rle_type)
					rle_type = 1;
			}

			//
			//-------------------------------------------------------
			// It didn't match, so we better not be in a matching run.  If we
			// aren't, reset the base to this pixel
			//-------------------------------------------------------
			//
			else
			{
				if (rle_type == 1)
					break;
				base = j;
				rle_type = -1;
			}
		}

		//
		//----------------------------------------------------------------------
		// Write out the run based upon its type after making sure that there is
		// enough room in the buffer
		//----------------------------------------------------------------------
		//
		if (rle_type == 1)
		{
			if (buffer_size-p < pixel_size+1)
				goto No_RLE;
			Verify(rle_count > 0 && rle_count <= 128);
			buffer[p++] = static_cast<BYTE>(rle_count + 127);
			buffer[p++] = data[i].blue;
			buffer[p++] = data[i].green;
			buffer[p++] = data[i].red;
			i = j;
		}

		//
		//-------------------------------------------------------------
		// This is a singleton run, so check out its space requirements
		//-------------------------------------------------------------
		//
		else
		{
			if (buffer_size-p < (pixel_size*rle_count)+1)
				goto No_RLE;

			//
			//---------------
			// Write them out
			//---------------
			//
			Verify(rle_count > 0 && rle_count <= 128);
			buffer[p++] = static_cast<BYTE>(rle_count-1);
			for (; i<j; ++i)
			{
				buffer[p++] = data[i].blue;
				buffer[p++] = data[i].green;
				buffer[p++] = data[i].red;
			}
		}
	}
	goto Write_File;

	//
	//-----------------------------------------------------------------------
	// Interleave the channels w/no RLE.  This should only be used if the RLE
	// takes more space than the buffer allows
	//-----------------------------------------------------------------------
	//
No_RLE:
	targa_file.Close();
	targa_file.Open(filename, FileStream::WriteOnly);
	targa_header.imageType = TargaHeader::RGB;
	targa_file.WriteBytes(&targa_header, sizeof(targa_header));
	for (i=0,p=0; i<channel_size; ++i)
	{
		buffer[p++] = data[i].blue;
		buffer[p++] = data[i].green;
		buffer[p++] = data[i].red;
	}
	goto Write_File;

	//
	//------------------------------------
	// Write out the buffer and discard it
	//------------------------------------
	//
Write_File:
	Verify(i == channel_size);
	Verify(p <= buffer_size);
	targa_file.WriteBytes(&buffer[0], p);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Proxies::WriteTargaFile(
		const char* filename,
		int width,
		int height,
		DynamicArrayOf<TargaColorA> &data
	)
{
	Check_Pointer(filename);
	Check_Object(&data);

	//
	//---------------------------------------------
	// Get the size of the image we will be copying
	//---------------------------------------------
	//
	TargaHeader targa_header;
	memset(&targa_header, 0, sizeof(targa_header));
	targa_header.widthLow = static_cast<unsigned char>(width & 255);
	targa_header.widthHigh = static_cast<unsigned char>(width >> 8);
	targa_header.heightLow = static_cast<unsigned char>(height & 255);
	targa_header.heightHigh = static_cast<unsigned char>(height >> 8);
	targa_header.imageType = TargaHeader::RLERGB;
	targa_header.pixelSize = 32;
	targa_header.flags = TargaHeader::YReversed;

	//
	//---------------------
	// Write out the header
	//---------------------
	//
	Check_Object(FileStreamManager::Instance);
	FileStream targa_file(filename, FileStream::WriteOnly);
	targa_file.WriteBytes(&targa_header, sizeof(targa_header));

	//
	//----------------------------------------------
	// See if we need to deal with the alpha channel
	//----------------------------------------------
	//
	unsigned channel_size = height * width;
	Verify(channel_size == data.GetLength());
	unsigned pixel_size = 4;
	unsigned buffer_size = channel_size * pixel_size;
	DynamicArrayOf<BYTE> buffer(buffer_size);
	unsigned p = 0;
	unsigned max_row = Min(width, 128);

	//
	//-------------------------------------
	// Start trying to make an RLE encoding
	//-------------------------------------
	//
	int i=0;
	while (i<channel_size)
	{
		unsigned rle_count;
		int rle_type = 0;
		unsigned j = i+1;
		unsigned base = i;
		unsigned max_run = max_row - (i%max_row);
		for (rle_count = 1; rle_count<max_run && j<channel_size; ++rle_count, ++j)
		{
			//
			//-------------------------------------
			// See if the next pixel matches or not
			//-------------------------------------
			//
			bool match =
				data[j].red == data[base].red
				 && data[j].blue == data[base].blue
				 && data[j].green == data[base].green
				 && data[j].alpha == data[base].alpha;

			//
			//----------------------------------------------------------------
			// If it matches, we break the run if this is a singleton run.  We
			// will always try to match matching pixels in a run
			//----------------------------------------------------------------
			//
			if (match)
			{
				if (rle_type == -1)
				{
					--j;
					--rle_count;
					break;
				}

				//
				//----------------------------------------------------
				// Matching runs can go one longer than singleton runs
				//----------------------------------------------------
				//
				else if (!rle_type)
					rle_type = 1;
			}

			//
			//-------------------------------------------------------
			// It didn't match, so we better not be in a matching run.  If we
			// aren't, reset the base to this pixel
			//-------------------------------------------------------
			//
			else
			{
				if (rle_type == 1)
					break;
				base = j;
				rle_type = -1;
			}
		}

		//
		//----------------------------------------------------------------------
		// Write out the run based upon its type after making sure that there is
		// enough room in the buffer
		//----------------------------------------------------------------------
		//
		if (rle_type == 1)
		{
			if (buffer_size-p < pixel_size+1)
				goto No_RLE;
			Verify(rle_count > 0 && rle_count <= 128);
			buffer[p++] = static_cast<BYTE>(rle_count + 127);
			buffer[p++] = data[i].blue;
			buffer[p++] = data[i].green;
			buffer[p++] = data[i].red;
			buffer[p++] = data[i].alpha;
			i = j;
		}

		//
		//-------------------------------------------------------------
		// This is a singleton run, so check out its space requirements
		//-------------------------------------------------------------
		//
		else
		{
			if (buffer_size-p < (pixel_size*rle_count)+1)
				goto No_RLE;

			//
			//---------------
			// Write them out
			//---------------
			//
			Verify(rle_count > 0 && rle_count <= 128);
			buffer[p++] = static_cast<BYTE>(rle_count-1);
			for (; i<j; ++i)
			{
				buffer[p++] = data[i].blue;
				buffer[p++] = data[i].green;
				buffer[p++] = data[i].red;
				buffer[p++] = data[i].alpha;
			}
		}
	}
	goto Write_File;

	//
	//-----------------------------------------------------------------------
	// Interleave the channels w/no RLE.  This should only be used if the RLE
	// takes more space than the buffer allows
	//-----------------------------------------------------------------------
	//
No_RLE:
	targa_file.Close();
	targa_file.Open(filename, FileStream::WriteOnly);
	targa_header.imageType = TargaHeader::RGB;
	targa_file.WriteBytes(&targa_header, sizeof(targa_header));
	for (i=0,p=0; i<channel_size; ++i)
	{
		buffer[p++] = data[i].blue;
		buffer[p++] = data[i].green;
		buffer[p++] = data[i].red;
		buffer[p++] = data[i].alpha;
	}
	goto Write_File;

	//
	//------------------------------------
	// Write out the buffer and discard it
	//------------------------------------
	//
Write_File:
	Verify(i == channel_size);
	Verify(p <= buffer_size);
	targa_file.WriteBytes(&buffer[0], p);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Proxies::ReadTargaHeader(
		TargaHeader *header,
		MemoryStream *file,
		int *width,
		int *height,
		unsigned *red_depth,
		unsigned *green_depth,
		unsigned *blue_depth,
		unsigned *alpha_depth
	)
{
	Check_Pointer(header);
	Check_Object(file);
	Check_Pointer(width);
	Check_Pointer(height);
	Check_Pointer(red_depth);
	Check_Pointer(green_depth);
	Check_Pointer(blue_depth);
	Check_Pointer(alpha_depth);

	//
	//---------------------------------------------
	// Get the size of the image we will be copying
	//---------------------------------------------
	//
	file->ReadBytes(header, sizeof(*header));
	*width =
		static_cast<int>(header->widthLow)
		 + (static_cast<int>(header->widthHigh)<<8);
	*height =
		static_cast<int>(header->heightLow)
		 + (static_cast<int>(header->heightHigh)<<8);
	Verify(header->pixelSize == 24 || header->pixelSize == 32);
	*red_depth = 8;
	*green_depth = 8;
	*blue_depth = 8;
	*alpha_depth = (header->pixelSize == 24) ? 0 : 8;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Proxies::ReadTargaChannels(
		MemoryStream *file,
		TargaHeader *header,
		DynamicArrayOf<TargaColor> *data
	)
{
	Check_Object(file);
	Check_Pointer(header);
	Check_Object(data);

	//
	//----------------------------
	// Read the file into a buffer
	//----------------------------
	//
	unsigned len = file->GetBytesRemaining();
	DynamicArrayOf<BYTE> buffer(len);
	file->ReadBytes(&buffer[0], len);

	//
	//-----------------------------
	// Set up the process variables
	//-----------------------------
	//
	bool
		x_reversed = ((header->flags & TargaHeader::XReversed) != 0),
		y_reversed = ((header->flags & TargaHeader::YReversed) != 0),
		compressed = (header->imageType == TargaHeader::RLERGB);
	unsigned width =
		static_cast<int>(header->widthLow)
		 + (static_cast<int>(header->widthHigh)<<8);
	unsigned height =
		static_cast<int>(header->heightLow)
		 + (static_cast<int>(header->heightHigh)<<8);
	BYTE
		run_count = 0,
		copy_count = 0,
		control_byte = 0,
		red = 0,
		green = 0,
		blue = 0;
	unsigned
		dest = ((y_reversed) ? 0 : width * (height-1)),
		source = 0;
	if (x_reversed)
		dest += width;

	//
	//------------------------
	// Spin through each texel
	//------------------------
	//
	for (unsigned y=0; y<height; ++y)
	{
		for (unsigned x=0; x<width; ++x)
		{
			Verify(
				dest == ((y_reversed)?y:(height-1-y))*width + (x_reversed)?(width-x):x
			);

			//
			//-----------------------------------------
			// Handle the control bytes for compression
			//-----------------------------------------
			//
			if (compressed)
			{
				if (!copy_count && !run_count)
					control_byte=buffer[source++];
				if (!run_count)
				{
					blue = buffer[source++];
					green = buffer[source++];
					red = buffer[source++];
				}
				if (!copy_count && !run_count)
				{
					if (control_byte >= 128)
						run_count = static_cast<BYTE>(control_byte-128);
					else
						copy_count = control_byte;
				}
				else if (run_count)
					run_count--;
				else if (copy_count)
					copy_count--;
			}

			//
			//-----------------------------------
			// Otherwise, just get the next color
			//-----------------------------------
			//
			else
			{
				blue = buffer[source++];
				green = buffer[source++];
				red = buffer[source++];
			}

			//
			//------------------------------
			// Put the color in the channels
			//------------------------------
			//
			if (x_reversed)
			{
				(*data)[--dest].red = red;
				(*data)[dest].green = green;
				(*data)[dest].blue = blue;
			}
			else
			{
				(*data)[dest].red = red;
				(*data)[dest].green = green;
				(*data)[dest++].blue = blue;
			}
		}

		//
		//-----------------------------
		// Move the destination pointer
		//-----------------------------
		//
		if (x_reversed == y_reversed)
			dest += ((y_reversed) ? 2 : -2) * width;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Proxies::ReadTargaChannels(
		MemoryStream *file,
		TargaHeader *header,
		DynamicArrayOf<TargaColorA> *data
	)
{
	Check_Object(file);
	Check_Pointer(header);
	Check_Object(data);

	//
	//----------------------------
	// Read the file into a buffer
	//----------------------------
	//
	unsigned len = file->GetBytesRemaining();
	DynamicArrayOf<BYTE> buffer(len);
	file->ReadBytes(&buffer[0], len);

	//
	//-----------------------------
	// Set up the process variables
	//-----------------------------
	//
	bool
		x_reversed = ((header->flags & TargaHeader::XReversed) != 0),
		y_reversed = ((header->flags & TargaHeader::YReversed) != 0),
		compressed = (header->imageType == TargaHeader::RLERGB);
	unsigned width =
		static_cast<int>(header->widthLow)
		 + (static_cast<int>(header->widthHigh)<<8);
	unsigned height =
		static_cast<int>(header->heightLow)
		 + (static_cast<int>(header->heightHigh)<<8);
	BYTE
		run_count = 0,
		copy_count = 0,
		control_byte = 0,
		red = 0,
		green = 0,
		blue = 0,
		alpha = 0;
	unsigned
		dest = ((y_reversed) ? 0 : width * (height-1)),
		source = 0;
	if (x_reversed)
		dest += width;

	//
	//------------------------
	// Spin through each texel
	//------------------------
	//
	for (unsigned y=0; y<height; ++y)
	{
		for (unsigned x=0; x<width; ++x)
		{
			Verify(
				dest == ((y_reversed)?y:(height-1-y))*width + (x_reversed)?(width-x):x
			);

			//
			//-----------------------------------------
			// Handle the control bytes for compression
			//-----------------------------------------
			//
			if (compressed)
			{
				if (!copy_count && !run_count)
					control_byte=buffer[source++];
				if (!run_count)
				{
					blue = buffer[source++];
					green = buffer[source++];
					red = buffer[source++];
					alpha = buffer[source++];
				}
				if (!copy_count && !run_count)
				{
					if (control_byte >= 128)
						run_count = static_cast<BYTE>(control_byte-128);
					else
						copy_count = control_byte;
				}
				else if (run_count)
					run_count--;
				else if (copy_count)
					copy_count--;
			}

			//
			//-----------------------------------
			// Otherwise, just get the next color
			//-----------------------------------
			//
			else
			{
				blue = buffer[source++];
				green = buffer[source++];
				red = buffer[source++];
				alpha = buffer[source++];
			}

			//
			//------------------------------
			// Put the color in the channels
			//------------------------------
			//
			if (x_reversed)
			{
				(*data)[--dest].red = red;
				(*data)[dest].green = green;
				(*data)[dest].blue = blue;
				(*data)[dest].alpha = alpha;
			}
			else
			{
				(*data)[dest].red = red;
				(*data)[dest].green = green;
				(*data)[dest].blue = blue;
				(*data)[dest++].alpha = alpha;
			}
		}

		//
		//-----------------------------
		// Move the destination pointer
		//-----------------------------
		//
		if (x_reversed == y_reversed)
			dest += ((y_reversed) ? 2 : -2) * width;
	}
}
