/*********************************************************************
 * 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.9 $
 *********************************************************************/

#ifndef _PNMCODEC_H
#define _PNMCODEC_H

#include <iostream>
#include <ctype.h>
#include <strings.h>
#include <stdlib.h>
#include <io/IO.h>
#include <List.h>
#include <String.h>
#include <Matrix.h>
#include <MatrixCodec.h>

#include "Color.h"


namespace prapi
{
	/**
	 * Possible pnm file types.
	 **/
	enum PnmType { PNM_PLAIN_BITMAP, PNM_PLAIN_GRAYMAP, PNM_PLAIN_PIXMAP,
								 PNM_RAWBITS_BITMAP, PNM_RAWBITS_GRAYMAP, PNM_RAWBITS_PIXMAP };

	/**
	 * A codec for PNM images. This Codec is able to read and write
	 * integer-valued binary, gray-scale and color images in plain
	 * (ascii) and raw formats. The codec only works with RGBColor and
	 * int images. Int images are written as binary when possible.
	 **/
	template <class T> class PnmCodec : public util::MatrixCodec<T>
	{
	public:
		PnmCodec();

		/**
		 * Set the raw flag. This flag controls whether the images are
		 * written in raw format. Default is true.
		 **/
		void setRaw(bool raw) { _bRaw = raw; }
		/**
		 * Check whether the output will be in raw format.
		 **/
		bool isRaw() { return _bRaw; }

		/**
		 * Check if binary output format is allowed (if possible).
		 **/
		bool isBinaryAllowed() { return _bBinaryAllowed; }
		/**
		 * Enable or disable binary output. If allowed, binary file will
		 * be produced when the data to be encoded contains only ones and
		 * zeros. Binary format is disabled by default.
		 **/
		void setBinaryAllowed(bool allow) { _bAllowBinary = allow; }

		void decodeMatrix(std::istream& in, util::Matrix<T>& mat) throw (util::MatrixException&, util::io::IOException&);
		void encodeMatrix(std::ostream& out, const util::Matrix<T>& mat) throw (util::MatrixException&, util::io::IOException&);

	private:
		bool _bRaw;
		bool _bColor;
		bool _bAllowBinary;
		
		void writeMatrix(std::ostream& out, unsigned char* data,
										 int width, int height, int maxval,
										 PnmType type) throw (util::MatrixException&, util::io::IOException&);
		unsigned char* readMatrix(std::istream& in, int& width, int& height) throw (util::MatrixException&, util::io::IOException&);
	};

	/**
	 * A specialization for RGB color images.
	 **/
	template <> PnmCodec<RGBColor<> >::PnmCodec();
	/**
	 * A specialization for integer-valued gray-scale images.
	 **/
	template <> PnmCodec<int>::PnmCodec();
	/**
	 * A specialization for RGB color images.
	 **/
	template <>	void PnmCodec<RGBColor<> >::encodeMatrix(std::ostream& out,
																											 const util::Matrix<RGBColor<> >& mat) throw (util::MatrixException&, util::io::IOException&);
	/**
	 * A specialization for integer-valued gray-scale images.
	 **/
	template <>	void PnmCodec<int>::encodeMatrix(std::ostream& out,
																							 const util::Matrix<int>& mat) throw (util::MatrixException&, util::io::IOException&);
	/**
	 * A specialization for RGB color images.
	 **/
	template <>	void PnmCodec<RGBColor<> >::decodeMatrix(std::istream& in,
																											 util::Matrix<RGBColor<> >& mat) throw (util::MatrixException&, util::io::IOException&);
	/**
	 * A specialization for integer-valued gray-scale images.
	 **/
	template <>	void PnmCodec<int>::decodeMatrix(std::istream& in,
																							 util::Matrix<int>& mat) throw (util::MatrixException&, util::io::IOException&);
	
	
	template <class T> void PnmCodec<T>::writeMatrix(std::ostream& out, unsigned char* data,
																									 int width, int height, int maxval,
																									 PnmType type)
		throw (util::MatrixException&, util::io::IOException&)
	{
		char * typeTags[6] = {"P1","P2","P3","P4","P5","P6"};
		out << typeTags[type] << "\n#Created by prapi::PnmCodec\n"
				<< width << " " << height << "\n";
		if (type != PNM_PLAIN_BITMAP && type != PNM_RAWBITS_BITMAP)
			out << maxval << "\n";
		int i=0;
		int size = width*height;
		if (type == PNM_PLAIN_PIXMAP || type == PNM_RAWBITS_PIXMAP)
			size *= 3;
		switch (type)
			{
			case PNM_PLAIN_PIXMAP:
			case PNM_PLAIN_GRAYMAP:
			case PNM_PLAIN_BITMAP:
				for (i=0;i<size;i++)
					{
						if (i & 0x0f)
							out << " ";
						out << (int)data[i];
						if ((i & 0x0f) == 0x0f)
							out << "\n";
					}
				break;
			case PNM_RAWBITS_PIXMAP:
			case PNM_RAWBITS_GRAYMAP:
				outWrite(out,(char*)data,size);
				break;
			case PNM_RAWBITS_BITMAP:
				while(i<size)
					{
						int base = 1;
						unsigned char byte=0;
						for (int j=0;j<8 && i<size;j++)
							{
								if (data[i++])
									byte |= base;
								base <<= 1;
							}
						outWrite(out,(char*)&byte,1);
					}
				break;
			}
	}

	template <class T> unsigned char* PnmCodec<T>::readMatrix(std::istream& in, int& width, int& height)
		throw (util::MatrixException&, util::io::IOException&)
	{
		char line[128];
		int type;
		
		in.getline(line,3);
		if (!in)
			throw util::MatrixException("PnmCodec<T>::readMatrix(istream&,int&,int&): Cannot read matrix type.");
		line[2] = 0;
		if (!strcmp(line,"P1"))
			type = PNM_PLAIN_BITMAP;
		else if (!strcmp(line,"P2"))
			type = PNM_PLAIN_GRAYMAP;
		else if (!strcmp(line,"P3"))
			type = PNM_PLAIN_PIXMAP;
		else if (!strcmp(line,"P4"))
			type = PNM_RAWBITS_BITMAP;
		else if (!strcmp(line,"P5"))
			type = PNM_RAWBITS_GRAYMAP;
		else if(!strcmp(line,"P6"))
			type = PNM_RAWBITS_PIXMAP;
		else
			throw util::MatrixException("PnmCodec<T>::readMatrix(istream&,int&,int&): Not a recognized matrix type.");

		if ((_bColor &&
				 !(type == PNM_RAWBITS_PIXMAP || type == PNM_PLAIN_PIXMAP)) ||

				(!_bColor &&
				 !(type == PNM_RAWBITS_GRAYMAP || type == PNM_PLAIN_GRAYMAP ||
					 type == PNM_RAWBITS_BITMAP || type == PNM_PLAIN_BITMAP)))
			throw util::MatrixException("PnmCodec<T>::readMatrix(istream&,int&,int&): Reading a wrong type of matrix.");
				
				
		int maxval = 255;
		do
			{
				in.getline(line,128);
			} while(line[0] == '#');

		util::List<std::string> parts;
		util::String::tokenize(std::string(line)," \t",parts,2);
		if (parts.getLength() != 2)
			throw util::MatrixException("PnmCodec<T>::readMatrix(stream&,int&,int&): Expecting width and height.");

		width = util::String::parseInt(parts[0]);
		height = util::String::parseInt(parts[1]);

		//cerr << width << "x" << height << endl;
		
		if (type == PNM_PLAIN_BITMAP || type == PNM_RAWBITS_BITMAP)
			maxval = 1;
		else
			{
				do
					{
						in.getline(line,128);
					} while(line[0] == '#');
				maxval = util::String::parseInt(std::string(line));
				//cerr << "Max: " << maxval << endl;
			}

		if (maxval > 255)
			throw util::MatrixException("PnmCodec<T>::readMatrix(stream&,int&,int&): Cannot handle values larger than 255.");

		int size = 1;
		int index = 0, byteIndex = 0;
		unsigned char* data = NULL;
		switch (type)
			{
			case PNM_PLAIN_PIXMAP:
				size = 3;
			case PNM_PLAIN_BITMAP:
			case PNM_PLAIN_GRAYMAP:
				size *= width * height;
				data = new unsigned char[size];
				while (index < size)
					{
						in >> ws; //.ws();
						in.getline(line,128);
						if (!in)
							throw util::MatrixException("PnmCodec<T>::readMatrix(stream&,int&,int&): Unexcepted end of input while reading plain pnm.");
						if (line[0] == '#')
							continue;

						std::string strLine(line);
						parts.clear();
						util::String::tokenize(strLine," \t",parts);
						for (int i=0; i<parts.getLength() && index < size; i++)
							{
								if (parts[i][0] == '#')
									break;
								data[index++] = (unsigned char)util::String::parseInt(parts[i]);
							}
					}
				break;
			case PNM_RAWBITS_PIXMAP:
				size = 3;
			case PNM_RAWBITS_GRAYMAP:
				size *= width * height;
				data = new unsigned char[size];
				in.read((char*)data,size);
				if (!in)
					throw util::MatrixException("PnmCodec<T>::readMatrix(stream&,int&,int&): Unexpected end of input while reading raw p{g,p}m.");
				break;
			case PNM_RAWBITS_BITMAP:
				size = width * height;
				int byteSize = (size+7)>>3;
				data = new unsigned char[size];
				unsigned char byte;
				while (byteIndex < byteSize)
					{
						in.read((char*)&byte,1);
						if (!in)
							throw util::MatrixException("PnmCodec<T>::readMatrix(stream&,int&,int&): Unexpected end of input while reading raw pbm.");
						int base = 1;
						for (int i=0;i<8 && index<size;i++)
							{
								data[index++] = (unsigned char)((byte & base) >> i);
								base <<= 1;
							}
						byteIndex++;
					}
			}
		return data;
	}	
}

#endif
