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

#ifndef _MAGICKCODEC_H
#define _MAGICKCODEC_H

#ifdef USE_IMAGEMAGICK

//We do not need drawing operations - fortunately.
//Including magick/draw.h causes a name clash.
#define _MAGICK_DRAW_H
#define _MAGICK_GEM_H

//How much memory is allocated for image data when reading from a
//stream? When the input buffer is filled, new space will be allocated
//in chunks of this many bytes.
#define DECODE_BLOCK_SIZE 1024*1024

#include <MatrixCodec.h>
#include <stdlib.h>
#include <stdio.h>
#include <magick/api.h>
#include "Color.h"

//PENDING: support for 16-bit color values!
namespace prapi
{
	/**
	 * MagickCodec is a general-purpose image codec that uses the
	 * ImageMagick library to read and write images. It supports
	 * gray-scale (intensity), RGB and RGBA (RGB-Alpha) images, all
	 * common image formats, and many not so common ones. Intensity
	 * images are represented as char, short, int, long, float or double
	 * images. Depending on your ImageMagick installation, the maximum
	 * value for the non-floating point types is either 255 or 65535.
	 * Floating point images are assumed to be in the range [0,1].
	 * Images are always read and written as integers. Color images are
	 * presented as matrices of type Color<T,n>, where T is the type of
	 * the color (char, short, int, long, float, double), and n is the
	 * number of color channels. 1, 3, and 4 channels are supported. If
	 * the colors have only one channel, it is assumed to be intensity,
	 * 3-channel colors are treated as RGB, and 4-channel colors as
	 * RGBA.<p>
	 *
	 * The template parameter determines the type of the matrix you will
	 * get. If the image read is of a different type, automatic
	 * conversion can be made in most cases.<p>
	 *
	 * An example:
	 * <pre>
	 * MagickCodec&lt;int&gt; codec;
	 * //Automatic conversion to gray scale if needed
	 * Matrix&lt;int&gt; image(codec.readFromFile("image.tif"));
	 * codec.writeToFile("image.jpg", image);
	 *
	 * MagicCodec&lt;Color&lt;int,3&gt; &gt; codec2;
	 * //Colors preserved
	 * Matrix&lt;Color&lt;int,3&gt; &gt; colorImage(codec.readFromFile("image.tif"));
	 * </pre>
	 **/
	template <class T> class MagickCodec : public util::MatrixCodec<T>
	{
	public:
		/**
		 * Create a new image codec.
		 *
		 * @param imageFormat the default format when images are written
		 * into a stream. Normally, file name suffix is used to determine
		 * the format. Valid formats are "GIF", "TIFF", "JPG", "XPM",
		 * "BMP" etc. See your local ImageMagick docs for a comprehensive
		 * list of supported image formats.
		 **/
		MagickCodec(const char* imageFormat = "TIFF") : _pMagic(imageFormat) {}
		/**
		 * Set the image format.
		 **/
		void setImageFormat(const char* format) { _pMagic = format; }

		/**
		 * Get the image format.
		 **/
		const char* getImageFormat() { return _pMagic; }

		/**
		 * Read an image from a file.
		 **/
		util::Matrix<T> readFromFile(std::string file) throw (util::MatrixException&, util::io::IOException&);
		/**
		 * Write an image to a file.
		 **/
		void writeToFile(std::string file, const util::Matrix<T>& mat) throw (util::MatrixException&, util::io::IOException&);

		/**
		 * Decode the contents of a stream, and store decoded bytes into
		 * an image. This method reads the stream until it ends, or the
		 * program runs out of memory. When the input ends, the data read
		 * is encoded.
		 **/
		void decodeMatrix(std::istream& in, util::Matrix<T>& mat) throw (util::MatrixException&, util::io::IOException&);
		/**
		 * Encode an image and write it into a stream.
		 **/
		void encodeMatrix(std::ostream& out, const util::Matrix<T>& mat) throw (util::MatrixException&, util::io::IOException&);

		/**
		 * Convert a matrix to an ImageMagick image. A newly allocated
		 * Image is returned, and must be destroyed by the caller with the
		 * DestroyImage(image) ImageMagick function. Example:
		 *
		 * <pre>
		 * Matrix&lt;int&gt; mat(3,3,
     *                       1,2,3,
     *                       4,5,6,
     *                       7,8,9);
		 *
		 * ::Image* imageMagicImage = MagickCodec&lt;int&gt;::constituteImage(mat);
		 *
		 * ... do whatever you need ...
		 * 
		 * DestroyImage(imageMagicImage);
		 * </pre>
		 **/
		static ::Image* constituteImage(const util::Matrix<T>& mat) throw (util::MatrixException&);

		/**
		 * Convert an ImageMagick image to a matrix. For example:
		 *
		 * <pre>
		 * ::Image* image = (get this somehow);
		 * Matrix&lt;Color&lt;int,3&gt; &gt; colorImage(MagicCodec&lt;Color&lt;int,3&gt; &gt::constituteMatrix(image));
		 * </pre>
		 **/
		static util::Matrix<T> constituteMatrix(::Image* image) throw (util::MatrixException&);
	private:
		const char* _pMagic;
	};

	/**
	 * Converts ImageMagic images to a matrices. This needs to be a
	 * separate class due to different template specializations for
	 * color images. This one is for gray-scale images.
	 **/
	template <class T> class MagickConverter
	{
	public:
		/**
		 * Return "I", since one-layer images are always intensity images. 
		 * This value is interpreted by the underlying ImageMagick
		 * library.
		 **/
		static const char* getColorType() { return "I"; }
		/**
		 * Get the pixel data from a matrix.
		 **/
		static void* getPixels(const util::Matrix<T>& mat);
		/**
		 * Destroy the pointer returned by getPixels().
		 **/
		static void destroyPixels(void* pixels) { delete[] (T*)pixels; }
		/**
		 * Get the pixel data from an ImageMagick image. This method is
		 * used to convert an ImageMagick image to a matrix. The pixel
		 * array must be deallocated by the caller with delete[], if the
		 * responsibility is not given to a matrix. An example:
		 *
		 * <pre>
		 * ::Image* image = (get this somehow);
		 * int* data MagickConverter&lt;int&gt;::getPixels(image);
		 * Matrix&lt;int&gt; matrix(image->rows, image->columns, data, true);
		 * </pre>
		 *
		 * @see MagickCodec::constituteMatrix(Image*)
		 **/
		static T* getPixels(::Image* image) throw (util::MatrixException&);
		/**
		 * Get the number of bytes each pixel needs for storage.
		 **/
		static size_t getPixelSize() { return sizeof(T); }
	};

	/**
	 * A specialization of the MagickConverter class for color images.
	 **/
	template <class T, int comps> class MagickConverter<Color<T,comps> >
	{
	public:
		static const char* getColorType() throw (util::MatrixException&);
		static void* getPixels(const util::Matrix<Color<T,comps> >& mat);
		static void destroyPixels(void* pixels) { delete[] (Color<T,comps>*)pixels; }
		static Color<T,comps>* getPixels(::Image* image) throw (util::MatrixException&);
		static size_t getPixelSize() { return comps*sizeof(T); }
	};

	template <class T> class MagickStorageType {};
#define TYPE_CLASS(type, stype)													\
	template <> class MagickStorageType<type>							\
	{																											\
  public:																								\
    static StorageType getType() { return stype; }			\
	}

	TYPE_CLASS(char,CharPixel);
	TYPE_CLASS(short,ShortPixel);
	TYPE_CLASS(int,IntegerPixel);
	TYPE_CLASS(long,LongPixel);
	TYPE_CLASS(float,FloatPixel);
	TYPE_CLASS(double,DoublePixel);
#undef TYPE_CLASS
	
	template <class T, int comps> class MagickStorageType<Color<T,comps> >
	{
	public:
		static StorageType getType() { return MagickStorageType<T>::getType(); }
	};

	template <class T, int comps> const char* MagickConverter<Color<T,comps> >::getColorType()
		throw (util::MatrixException&)
	{
		switch (comps)
			{
			case 1:	return "I";
			case 3: return "RGB";
			case 4: return "RGBA";
			default: throw util::MatrixException("getColorType(): Only 1, 3, and 4 color channels are supported.");
			}
	}

	template <class T> void* MagickConverter<T>::getPixels(const util::Matrix<T>& mat)
	{
		int size = mat.getRows()*mat.getColumns();
		T* result = new T[size], *ptr = result;
		const T* data = mat.getData();
		for (int i=size; i--; data++, ptr++)
			*ptr = T((*data & 0xff) | (*data << 8));
		return result;
	}

	template <class T, int comps> void* MagickConverter<Color<T,comps> >::getPixels(const util::Matrix<Color<T,comps> >& mat)
	{
		int size = mat.getRows()*mat.getColumns();
		T* result = new T[size*comps], *ptr = result;
		const Color<T,comps>* data = mat.getData();
		for (int i=size; i--; data++)
			for (int j=0; j<comps; j++,ptr++)
				*ptr = T(((*data)[j] << 8) | (*data)[j]);
		return result;
	}

	template <class T> T* MagickConverter<T>::getPixels(::Image* image)
		throw (util::MatrixException&)
	{
		ExceptionInfo ex;
		GetExceptionInfo(&ex);

		int size = image->columns * image->rows;
		const PixelPacket* pixels = AcquireImagePixels(image, 0, 0, image->columns, image->rows, &ex),
			*sourcePtr = pixels;
		
		if (ex.severity != UndefinedException)
			{
				util::MatrixException exception(std::string("getPixels(Image*): ") +
																				ex.reason + " " + ex.description);
				DestroyExceptionInfo(&ex);
				throw exception;
			}
		DestroyExceptionInfo(&ex);
		
		T* result = new T[size], *targetPtr = result;

		for (int i=size; i--; targetPtr++, sourcePtr++)
#if QuantumDepth == 16
			*targetPtr = T( sourcePtr->red & 0xff);
#else
		  *targetPtr = T(sourcePtr->red);
#endif
		return result;
	}

	template <class T, int comps> Color<T,comps>* MagickConverter<Color<T,comps> >::getPixels(::Image* image)
		throw (util::MatrixException&)
	{
		ExceptionInfo ex;
		GetExceptionInfo(&ex);

		int size = image->columns * image->rows;
		const PixelPacket* pixels = AcquireImagePixels(image, 0, 0, image->columns, image->rows, &ex),
			*sourcePtr = pixels;

		if (ex.severity != UndefinedException)
			{
				util::MatrixException exception(std::string("getPixels(Image*): ") +
																				ex.reason + " " + ex.description);
				DestroyExceptionInfo(&ex);
				throw exception;
			}
		DestroyExceptionInfo(&ex);
		

		Color<T,comps>* result = new Color<T,comps>[size], *targetPtr = result;

		for (int i=size; i--; targetPtr++, sourcePtr++)
			for (int j=0; j<comps; j++)
#if QuantumDepth == 16
				(*targetPtr)[j] = T(((Quantum*)sourcePtr)[j] & 0xff);
#else
				(*targetPtr)[j] = T(((Quantum*)sourcePtr)[j]);
#endif
		return result;
	}

	template <class T> ::Image* MagickCodec<T>::constituteImage(const util::Matrix<T>& mat) throw (util::MatrixException&)
	{
		ExceptionInfo ex;
		GetExceptionInfo(&ex);
		void* data = MagickConverter<T>::getPixels(mat); //const_cast<util::Matrix<T>&>(mat)
		::Image* image = ConstituteImage(mat.getColumns(), mat.getRows(),
																		 MagickConverter<T>::getColorType(),
																		 MagickStorageType<T>::getType(),
																		 data, &ex);
		DestroyConstitute();
		MagickConverter<T>::destroyPixels(data);
		if (ex.severity != UndefinedException)
			{
				util::MatrixException exception(std::string("MagickCodec::constituteImage(Matrix&): ") +
																				ex.reason + " " + ex.description);

				DestroyExceptionInfo(&ex);
				throw exception;
			}
		DestroyExceptionInfo(&ex);
		return image;
	}

	template <class T> util::Matrix<T> MagickCodec<T>::constituteMatrix(::Image* image) throw (util::MatrixException&)
	{
		return util::Matrix<T>(image->rows, image->columns, MagickConverter<T>::getPixels(image), true);
	}

	template <class T> void MagickCodec<T>::writeToFile(std::string file, const util::Matrix<T>& mat)
		throw (util::MatrixException&, util::io::IOException&)
	{
		::Image* image = constituteImage(mat);

		ImageInfo* info = CloneImageInfo(NULL);

		strncpy(image->filename, file.c_str(), MaxTextExtent-1);
		if (!WriteImage(info, image))
			{
				util::io::IOException exception(std::string("MagickCodec::writeToFile(string,Matrix&): ") +
																				image->exception.reason + " " + image->exception.description);
				
				DestroyImageInfo(info);
				DestroyImage(image);
				throw exception;
			}

		DestroyImageInfo(info);
		DestroyImage(image);
	}
	
	template <class T> util::Matrix<T> MagickCodec<T>::readFromFile(std::string file)
		throw (util::MatrixException&, util::io::IOException&)
	{
		ImageInfo info;
		GetImageInfo(&info);
		ExceptionInfo ex;
		GetExceptionInfo(&ex);
		::Image* image;
		
		strncpy(info.filename, file.c_str(), MaxTextExtent-1);
		image = ReadImage(&info, &ex);
		DestroyImageInfo(&info);
		
		if (ex.severity != UndefinedException)
			{
				util::io::IOException exception(std::string("MagickCodec::readFromFile(string): ") +
																				ex.reason + " " + ex.description);
				DestroyExceptionInfo(&ex);
				throw exception;
			}

		DestroyExceptionInfo(&ex);
		util::Matrix<T> result(image->rows, image->columns, MagickConverter<T>::getPixels(image), true);

		DestroyImage(image);

		return result;
	}

	template <class T> void MagickCodec<T>::decodeMatrix(std::istream& in, util::Matrix<T>& mat)
		throw (util::MatrixException&, util::io::IOException&)
	{
		char* data = (char*)malloc(DECODE_BLOCK_SIZE);
		if (!data)
			throw util::MatrixException("MagickCodec::decodeMatrix(istream&,Matrix&): Cannot allocate memory for input buffer.");
		int length = 0, capacity = DECODE_BLOCK_SIZE;
		do
			{
#if __GNUC__ < 3
				in.read(data+length, capacity-length);
				int bytes = in.gcount();
#else
				int bytes = in.readsome(data+length, capacity-length);
#endif
				if (!in || !bytes)
					break;
				
				length += bytes;
				if (length >= capacity) //need to make new room
					{
						capacity += DECODE_BLOCK_SIZE;
						char* tmp = (char*)realloc(data, capacity);
						if (!tmp)
							{
								free(data);
								throw util::MatrixException("MagickCodec::decodeMatrix(istream&,Matrix&): Cannot reallocate memory for input buffer.");
							}
						data = tmp;
					}
			} while(in);
				
		ExceptionInfo ex;
		GetExceptionInfo(&ex);
		ImageInfo info;
		GetImageInfo(&info);
		::Image* image = BlobToImage(&info, data, length, &ex);
		free(data);
		DestroyImageInfo(&info);
		if (ex.severity != UndefinedException)
			{
				util::MatrixException exception(std::string("MagickCodec::decodeMatrix(istream&,Matrix&): ") +
																				ex.reason + " " + ex.description);
				DestroyExceptionInfo(&ex);
				throw exception;
			}

		DestroyExceptionInfo(&ex);
		mat = util::Matrix<T>(image->rows, image->columns, MagickConverter<T>::getPixels(image), true);

		DestroyImage(image);
	}

	template <class T> void MagickCodec<T>::encodeMatrix(std::ostream& out, const util::Matrix<T>& mat)
		throw (util::MatrixException&, util::io::IOException&)
	{
		::Image* image = constituteImage(mat);
		ExceptionInfo ex;
		GetExceptionInfo(&ex);
		ImageInfo* info = CloneImageInfo(NULL);
		strncpy(image->magick, _pMagic, MaxTextExtent-1);
		size_t size = image->rows * image->columns * MagickConverter<T>::getPixelSize();

		void* data = ImageToBlob(info, image, &size, &ex);
		DestroyImageInfo(info);
		DestroyImage(image);

		if (ex.severity != UndefinedException)
			{
				util::MatrixException exception(std::string("MagickCodec::encodeMatrix(ostream&,const Matrix&): ") +
																				ex.reason + " " + ex.description);
				DestroyExceptionInfo(&ex);
				throw exception;
			}
		DestroyExceptionInfo(&ex);

		if (!out.write((char*)data, size))
			throw util::io::IOException("MagickCodec::encodeMatrix(ostream&,const Matrix&): Cannot write image to stream.");
	}
	
};

#endif

#endif
