/*********************************************************************
 * This file is part of the PRAPI library.
 *
 * Copyright (C) 2001-2002 Topi Mäenpää
 * Copyright (C) 2001 Jaakko Viertola
 * All rights reserved.
 *
 * This program is free software. You can redistribute and/or modify
 * it under the terms of the free software licence found in the
 * accompanying file "COPYING". The licence terms must always be
 * redistributed with this source file. The above copyright notice
 * must be reproduced in all modified and unmodified copies of this
 * source file.
 *
 * $Revision: 1.9 $
 *********************************************************************/

#include "RasterCodec.h"
#include <stdlib.h>
#include <netinet/in.h>

using namespace util;
using namespace std;
using namespace util::io;

namespace prapi
{
	RasterCodec::RasterCodec()
	{
		_bPacked = _bUseColorMap = false;
	}
	
	void RasterCodec::encodeMatrix(ostream& out, const Matrix<int>& mat) throw (MatrixException&, IOException&)
	{
		RasterHeader header;

		int rows = mat.getRows(), columns = mat.getColumns();
		
		header.ras_magic = htonl(RAS_MAGIC);
		header.ras_width = htonl(columns);
		header.ras_height = htonl(rows);
		header.ras_depth = htonl(8);
		int length = rows * columns + ((columns & 1) ? rows : 0);
		header.ras_length = htonl(length);
		header.ras_type = htonl(_bPacked ? RT_BYTE_ENCODED : RT_STANDARD);
		if (!_bUseColorMap)
			{
				header.ras_maptype = htonl(RMT_NONE);
				header.ras_maplength = 0;
			}
		else
			{
				header.ras_maptype = htonl(RMT_EQUAL_RGB);
				header.ras_maplength = htonl(_colorMap.getLength()*3);
			}

		unsigned char* data = new unsigned char[length];
		const int *matrixData = mat.getData();
		for (int i=rows*columns,index=0;i--;)
			data[index++] = *(matrixData++);
		
		if (_bPacked)
			{
				int size = rows*columns, index = 0;
				unsigned char *newData = new unsigned char[int(float(size)*1.5f+1.0f)];
				int i=0;
				while (i<size)
					{
						int j=1;
						while (i+j<size && j<256 && data[i] == data[i+j]) j++;
						if (j>2 || (data[i] == 0x80 && j == 2))
							{
								newData[index++] = 0x80;
								newData[index++] = (unsigned char)(j-1);
								newData[index++] = data[i];
								i+=j;
							}
						else if (j == 2)
							{
								newData[index++] = data[i];
								newData[index++] = data[i];
								i+=2;
							}
						else if (data[i] == 0x80)
							{
								newData[index++] = 0x80;							
								newData[index++] = 0;
								i++;
							}
						else
							newData[index++] = data[i++];
					}

				//unsigned char *newData2 = new unsigned char[index];
				//memcpy(newData2,newData,index);
				header.ras_length = htonl(index);
				try
					{
						outWrite(out,(char*)&header,sizeof(RasterHeader));
						if (header.ras_maplength != 0)
							writeClrMap(out);
						outWrite(out,newData,index);
					}
				catch (...)
					{
						delete[] newData;
						delete[] data;
						throw;
					}
				
				delete[] newData;
			}
		else
			{
				unsigned char zero = 0;
				try
					{
						outWrite(out,(char*)&header,sizeof(RasterHeader));
						if (header.ras_maplength != 0)
							writeClrMap(out);
						for (int i=0;i<rows;i++)
							{
								outWrite(out,data+i*columns,columns);
								if (columns & 1)
									outWrite(out,&zero,1);
							}
					}
				catch (...)
					{
						delete[] data;
						throw;
					}
			}

		delete[] data;
	}
	

	void RasterCodec::writeClrMap(ostream& out)
	{
		UnsignedCharList tmp(_colorMap.getLength());
		for (int i=0;i<3;i++)
			{
				tmp = _colorMap.getChannel(i);
				//cerr << tmp;
				out.write((char*)tmp.getData(),tmp.getLength());
			}
	}
	
	void RasterCodec::decodeMatrix(istream& in, Matrix<int>& mat) throw (MatrixException&, IOException&)
	{
		RasterHeader header;
		inRead(in,(char*)&header,sizeof(RasterHeader));
		//Fix byte order
		header.ras_magic = htonl(header.ras_magic);
		header.ras_width = htonl(header.ras_width);
		header.ras_height = htonl(header.ras_height);
		header.ras_depth = htonl(header.ras_depth);
		header.ras_length = htonl(header.ras_length);
		header.ras_type = htonl(header.ras_type);
		header.ras_maptype = htonl(header.ras_maptype);
		header.ras_maplength = htonl(header.ras_maplength);
		if (header.ras_magic != RAS_MAGIC)
			throw MatrixException("RasterCodec::decodeMatrix(istream&, Matrix&): Not a sunraster.");
		if ((header.ras_type != RT_STANDARD && header.ras_type != RT_BYTE_ENCODED) || header.ras_depth != 8)
			throw MatrixException("RasterCodec::decodeMatrix(istream&, Matrix&): Unsupported raster type.");
		bool useMap = header.ras_maplength != 0;
		int colorCount = 0;
		int clrMap[256];
		if (useMap)
			{
				unsigned char* clrData = new unsigned char[header.ras_maplength];
				inRead(in,clrData,header.ras_maplength);
				colorCount = header.ras_maplength/3;
				for (int i=0;i<colorCount;i++)
					{
						RGBColor<int> clr(clrData[i],clrData[i+colorCount],clrData[i+(colorCount<<1)]);
						clrMap[i] = (int)clr;
					}
				delete[] clrData;
			}

		unsigned char* data = NULL;
		int size = header.ras_width * header.ras_height, index = 0;

		if (header.ras_type == RT_STANDARD)
			{
				try
					{
						int size = header.ras_length;
						if (header.ras_width & 1)
							size -= header.ras_height;
						data = new unsigned char[size];
						for (int i=0;i<header.ras_height;i++)
							{
								inRead(in,data+i*header.ras_width,header.ras_width);
								if (header.ras_width & 1)
									inIgnore(in,1);
							}
					}
				catch (...)
					{
						delete[] data;
						throw;
					}
			}
		else
			{
				data = new unsigned char[header.ras_length];
				unsigned char *newData = new unsigned char[size];
				try
					{
						inRead(in,data,header.ras_length);
		
						int i=0;
						while (i<header.ras_length)
							{
								if (data[i] == 0x80)
									{
										if (i == header.ras_length-1)
											throw MatrixException("RasterCodec::decodeMatrix(istream&, Matrix&): Invalid run-length code");
										if (data[++i] == 0)
											newData[index++] = 0x80;
										else
											{
												if (i == header.ras_length-1)
													throw MatrixException("RasterCodec::decodeMatrix(istream&, Matrix&): Invalid run-length code");
												int repeat = data[i++];
												for (int j=0;j<=repeat;j++)
													newData[index++] = data[i];
											}
									}
								else
									newData[index++] = data[i];
								i++;
							}
					}
				catch (...)
					{
						delete[] data;
						delete[] newData;
						throw;
					}
				
				delete[] data;
				data = newData;
			}

		mat.setSize(header.ras_height,header.ras_width);
		if (!useMap)
			{
				int *targetData = mat.getData();
				unsigned char *sourceData = data;
				for (int i=header.ras_height*header.ras_width;i--;targetData++, sourceData++)
					*targetData = *sourceData;
			}
		else
			{
				int *targetData = mat.getData();
				unsigned char *sourceData = data;
				for (int i=header.ras_height*header.ras_width;i--;targetData++, sourceData++)
					*targetData = clrMap[*sourceData];
			}

		delete[] data;
	}
}
