/*********************************************************************
 * This file is part of the PRAPI library.
 *
 * Copyright (C) 2001 Topi Mäenpää and 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.10 $
 *********************************************************************/

#include "BmpCodec.h"
#include <io/IO.h>

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

#ifdef LITTLE_ENDIAN

#define htoles(A)				(A)
#define htolel(A)				(A)
#define letohs(A)				(A)
#define letohl(A)				(A)

#else

#define letohs(A)				((((short int)(A) & 0xff00) >> 8) | \
												(((short int)(A) & 0x00ff) << 8))
#define letohl(A)				((((int)(A) & 0xff000000) >> 24) | \
												(((int)(A) & 0x00ff0000) >> 8)  | \
												(((int)(A) & 0x0000ff00) << 8)  | \
												(((int)(A) & 0x000000ff) << 24))
#define htoles					letohs
#define htolel					letohl

#endif


namespace prapi
{
	void ColorBmpCodec::encodeMatrix(ostream& out, const Matrix<RGBColor<> >& mat) throw (MatrixException&, IOException&) 
	{
		//cerr << "1\n";
		BitmapFileHeader fileHeader;
		BitmapInfoHeader infoHeader;
		
		int rowLength = mat.getColumns() * 3;
		int remainder = rowLength & 3;
		if (remainder)
			rowLength += 4 - remainder;
		//cerr << "Encoding BMP, rowlength = " << rowLength << "\n";
		fileHeader.bfType = htoles(0x4d42);
		fileHeader.bfSize = 54 + rowLength * mat.getRows();
		fileHeader.bfSize = htolel(fileHeader.bfSize);
		fileHeader.bfReserved1 = 0;
		fileHeader.bfReserved2 = 0;
		fileHeader.bfOffBits = htolel(54);

		infoHeader.biSize = htolel(40);
		infoHeader.biWidth =  htolel(mat.getColumns());
		infoHeader.biHeight = htolel(mat.getRows());
		infoHeader.biPlanes = htoles(1);
		infoHeader.biBitCount = htoles(24);
		infoHeader.biCompression = 0;
		infoHeader.biSizeImage = 0;
		infoHeader.biXPelsPerMeter = htolel(6000);
		infoHeader.biYPelsPerMeter = htolel(6000);
		infoHeader.biClrUsed = 0;
		infoHeader.biClrImportant = 0;

		unsigned char* data = new unsigned char[mat.getColumns()*mat.getRows()*3];
		int index = 0, r, c;
		AllItems(r,c,mat)
			{
				RGBColor<>  clr = mat(r,c);
				data[index++] = (unsigned char)clr.getBlue();
				data[index++] = (unsigned char)clr.getGreen();
				data[index++] = (unsigned char)clr.getRed();
			}

		//cerr << "Data read from image.\n";

		rowLength = mat.getColumns() * 3;
		char zeros[4] = {0,0,0,0};

		//out.write((char*)fileHeader,14); //sizeof(BitmapFileHeader));
		out.write((char*)&fileHeader.bfType,2);
		out.write((char*)&fileHeader.bfSize,4);
		out.write((char*)&fileHeader.bfReserved1,2);
		out.write((char*)&fileHeader.bfReserved2,2);
		out.write((char*)&fileHeader.bfOffBits,4);

		//cerr << "Wrote file header.\n";

		out.write((char*)&infoHeader,sizeof(BitmapInfoHeader));
		//cerr << "Wrote info header.\n";
		for (int i=mat.getRows()-1;i>=0;i--)
			{
				out.write((char*)(data+i*rowLength),rowLength);
				if (remainder)
					out.write(zeros,4-remainder);
			}
		delete[] data;
	}


	void ColorBmpCodec::decodeMatrix(istream& in, Matrix< RGBColor<> >& mat) throw (MatrixException&, IOException&)
	{
		BitmapFileHeader fileHeader;
		BitmapInfoHeader infoHeader;

		//in.read((char*)fileHeader,14); //sizeof(BitmapFileHeader));
		inRead(in,(char*)&fileHeader.bfType,2);
		inRead(in,(char*)&fileHeader.bfSize,4);
		inRead(in,(char*)&fileHeader.bfReserved1,2);
		inRead(in,(char*)&fileHeader.bfReserved2,2);
		inRead(in,(char*)&fileHeader.bfOffBits,4);

		inRead(in,(char*)&infoHeader,sizeof(BitmapInfoHeader));


		unsigned short planes = letohs(infoHeader.biPlanes);
		unsigned short depth = letohs(infoHeader.biBitCount);

		fileHeader.bfType = letohs(fileHeader.bfType);
		infoHeader.biHeight = letohl(infoHeader.biHeight);
		infoHeader.biWidth = letohl(infoHeader.biWidth);

		if (fileHeader.bfType != 0x4d42)
			throw MatrixException("DecodeMatrix: Not a BMP image.");
		if (planes != 1 ||
				depth != 24 ||
				infoHeader.biCompression != 0)
			{
				//cerr << "Planes: " << planes << ", depth: " << depth << ", compression: " << infoHeader.biCompression << "\n";
				throw MatrixException("DecodeMatrix: Unsupported image type");
			}

		int rowLength = infoHeader.biWidth * 3;
		int remainder = rowLength & 3;
		int size = infoHeader.biHeight * rowLength;

		unsigned char* data = new unsigned char[size];
		try
			{
				for (int i=infoHeader.biHeight;i--;)
					{
						inRead(in,(char*)(data+i*rowLength),rowLength);
						if (remainder)
							{
								in.ignore(4 - remainder);
								if (!in)
									throw IOException("DecodeMatrix: Corrupted image data");
							}
					}

				mat.setSize(infoHeader.biHeight,infoHeader.biWidth);
				RGBColor<> * matData = mat.getData();
				for (int dataIndex=0,index=0;dataIndex<infoHeader.biHeight*infoHeader.biWidth;dataIndex++,index+=3,matData++)
					{
						RGBColor<> clr(data[index+2],data[index+1],data[index]);
						*matData = clr;
					}
			}
		catch (...)
			{
				delete[] data;
				throw;
			}
		delete[] data;
	}
}
