/*********************************************************************
 * This file is part of the PRAPI library.
 *
 * Copyright (C) 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.4 $
 *********************************************************************/

#ifndef _XWINDOWS_H
#define _XWINDOWS_H

#include <Matrix.h>
#include <X11/Xlib.h>
#include <Runnable.h>
#include "Color.h"

namespace prapi
{
	/**
	 * An Exception for X windows related errors.
	 **/
	class XException : public util::Exception
	{
	public:
		XException(std::string message) : util::Exception(message) {}
	};
		
	/**
	 * A C++ wrapper for the xlib's Display structure.
	 **/
	class XDisplay : public util::Object
	{
	public:
		/**
		 * Open the default X display. If the display cannot be opened, an
		 * exception is thrown.
		 **/
		XDisplay() throw (XException&);
		/**
		 * Open an X display with the given name. The format of the name
		 * is hostname:number.screen_number. If the display cannot be
		 * opened, an exception is thrown.
		 **/
		XDisplay(std::string name) throw (XException&);
		/**
		 * The copy constructor.
		 **/
		XDisplay(const XDisplay& other) : _pDisplay(other._pDisplay) {}

		/**
		 * Close the connection to an X server.
		 **/
		void close() { XCloseDisplay(_pDisplay); }

		/**
		 * Assignment operator.
		 **/
		XDisplay& operator= (const XDisplay& other) { _pDisplay = other._pDisplay; }

		/**
		 * Typecast this class to a Display pointer. This allows the use
		 * of the class in the place of the Display structure.
		 **/
		operator Display*() { return _pDisplay; }
		/**
		 * Typecast this class to a Display pointer (const version).
		 **/
		operator const Display*() const { return _pDisplay; }

	private:
		Display* _pDisplay;
	};
	
	/**
	 * A class for displaying images on an X screen.
	 **/
	class XWindows
	{
	public:
		/**
		 * Display an image on an X window. A window is created on the
		 * default display, with its default visual. If the waitUserInput
		 * flag is set to true, the call blocks until a key or a mouse
		 * button is pressed, and NULL is returned. If it is false, a
		 * separate thread is created, which waits until the window
		 * closes. A pointer to this thread is returned. The caller is
		 * responsible for joining the thread (Thread::join()) and for
		 * deleting the pointer. An exception is thrown if an error
		 * occurs. Make sure that the depth of the given color (or
		 * gray-scale) values is eight bits. An example:
		 *
		 * <pre>
		 * Matrix&lt;Color&lt;int,3&gt; &gt; clrImg(...);
		 * Thread* thread = XWindows::display(clrImg);
		 * thread.join();
		 * delete thread;
		 * </pre>
		 *
		 * @param img the image to be displayed
		 * @param waitUserInput if true, the call blocks and returns NULL. 
		 * @param title the title of the display window
		 * Otherwise, it does not block and returns a pointer to a thread.
		 **/
		template <class T> static util::Thread* display(const util::Matrix<T>& img,
																										bool waitUserInput=false,
																										std::string title="PRAPI Image Display")
			throw (XException&);

	private:
		class Displayer;
	};

	class XWindows::Displayer : public util::Thread
	{
	public:
		Displayer(XImage* image, int width, int height, std::string title, XDisplay& display) :
			_pImage(image), _iWidth(width), _iHeight(height), _strTitle(title), _display(display) {}

		void run();

	private:
		XImage* _pImage;
		int _iWidth, _iHeight;
		std::string _strTitle;
		XDisplay _display;
	};

	
	/**
	 * A class for converting gray-scale images to X images.
	 **/
	template <class T> class XConverter
	{
	public:
		/**
		 * Convert a gray-scale image to an X image. If you want to use
		 * the images for viewing in the X windows environment, this
		 * method might come in handy. The returned pointer must be
		 * released with XDestroyImage(ptr).
		 **/
		static XImage* createXImage(const util::Matrix<T>& mat, XDisplay& display);

	private:
		static char* getData(const util::Matrix<T>& mat);
	};

	/**
	 * A class for converting color images to X images.
	 **/
	template <class T> class XConverter<Color<T,3> >
	{
	public:
		/**
		 * Convert a color image to an X image. If you want to use the
		 * images for viewing in the X windows environment, this method
		 * might come in handy. The returned pointer must be released with
		 * XDestroyImage(ptr).
		 **/
		static XImage* createXImage(const util::Matrix<Color<T,3> >& mat, XDisplay& display);

	private:
		static char* getData(const util::Matrix<Color<T,3> >& mat);
	};

	template <class T> util::Thread* XWindows::display(const util::Matrix<T>& img, bool wait, std::string title) throw (XException&)
	{
		XDisplay display;
		Displayer *displayer = new Displayer(XConverter<T>::createXImage(img, display),
																				 img.getColumns(), img.getRows(),
																				 title, display);
		if (wait)
			{
				displayer->run();
				delete displayer;
				return NULL;
			}
		else
			{
				displayer->start();
				return displayer;
			}
	}


	template <class T> char* XConverter<T>::getData(const util::Matrix<T>& mat)
	{
		int size = mat.getRows()*mat.getColumns();
		int *result = (int*)malloc(size*sizeof(int));
		const T* data = mat.getData();
		for (int i=0; i<size; i++, data++)
			result[i] = ((int)(*data) & 0xff) | (((int)(*data) << 8) & 0xff00) | (((int)(*data) << 16) & 0xff0000);
		return (char*)result;
	}

	template <class T> XImage* XConverter<T>::createXImage(const util::Matrix<T>& mat, XDisplay& display)
	{
		char* data = getData(mat);
		XImage *image = XCreateImage(display, XDefaultVisual(display,0),
																 24, ZPixmap,
																 0, data,
																 mat.getColumns(), mat.getRows(),
																 32, 0);
		return image;
	}

	template <class T> char* XConverter<Color<T,3> >::getData(const util::Matrix<Color<T,3> >& mat)
	{
		int size = mat.getRows()*mat.getColumns();
		int *result = (int*)malloc(size*sizeof(int));
		const Color<T,3>* data = mat.getData();
		for (int i=0; i<size; i++, data++)
			result[i] = ((int)(*data)[0] & 0xff) | (((int)(*data)[1] << 8) & 0xff00) | (((int)(*data)[2] << 16) & 0xff0000);
		return (char*)result;
	}

	template <class T> XImage* XConverter<Color<T,3> >::createXImage(const util::Matrix<Color<T,3> >& mat, XDisplay& display)
	{
		char* data = getData(mat);
		XImage *image = XCreateImage(display, XDefaultVisual(display,0),
																 24, ZPixmap,
																 0, data,
																 mat.getColumns(), mat.getRows(),
																 32, 0);
		return image;
	}
}

#endif
