/*********************************************************************
 * 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 _GRAPHICS_H
#define _GRAPHICS_H

#include <Matrix.h>
#include <math.h>
#include "Shape.h"

namespace prapi { namespace graphics {

	/**
	 * Different drawing modes. Note that all graphics contexts may not
	 * support drawing modes other than the default (DRAWMODE_NORMAL).
	 *
	 * <ul>
	 * <li>DRAWMODE_NORMAL - normal mode. New pixel replaces the old
	 * one totally or partially, depending on the current opacity.</li>
	 * <li>DRAWMODE_PLUS - the current color, weighted by opacity, is
	 * added to the old pixel value</li>
	 * <li>DRAWMODE_MINUS - the current color, weighted by opacity, is
	 * subtracted from the old pixel value</li>
	 * <li>DRAWMODE_MULTIPLY - the old color is multiplied by the
	 * current color, weighted by opacity</li>
	 * <li>DRAWMODE_DIVIDE - the old color is divided by the current
	 * color, weighted by opacity. Make sure that the current color
	 * has no zero channels, and that opacity is non-zero. Otherwise,
	 * division by zero is performed.</li>
	 * </ul>
	 **/
	enum DrawMode { DRAWMODE_NORMAL, DRAWMODE_PLUS, DRAWMODE_MINUS, DRAWMODE_MULTIPLY, DRAWMODE_DIVIDE };

	/**
	 * Drawing methods. This is a superclass for classes that are able
	 * to draw on various devices. A graphics device must contain a
	 * rectangular area of pixels. The upper left corner of the area is
	 * used as an origin by default, with the positive y axis running
	 * down and the positive x axis to the right. By default, the
	 * Graphics class takes care of high-level drawing primitives, and
	 * delegates only the setting of pixels to the actual graphics
	 * device. It is however possible the override the drawing
	 * functions. This way it is possible to create, for example,
	 * PostScript graphics etc.<p>
	 *
	 * The template parameter T defines the type of a pixel in the
	 * graphics context. In a memory buffer, pixels may be represented
	 * as chars or ints. For color graphics, the type may be Color.
	 **/
	template <class T> class Graphics : virtual public util::Object
	{
	public:
		/**
		 * Set the drawing mode. All subsequent drawing operations will be
		 * performed in the indicated mode.
		 **/
		void setDrawMode(DrawMode mode) { _drawMode = mode; }

		/**
		 * Get the current drawing mode.
		 **/
		DrawMode getDrawMode() const { return _drawMode; }
		
		/**
		 * Set the current drawing "color" to a value. All subsequent
		 * drawing operations will use this color. For gray-scale
		 * graphics, for example integer valued memory buffers, the
		 * "color" is actually a scalar.
		 **/
		void setColor(T color) { _color = color; }

		/**
		 * Get the current drawing color.
		 **/
		T getColor() const { return _color; }

		/**
		 * Set the opacity of the drawing operations. Opacity determines
		 * how much newly drawn pixels affect those under it. If opacity
		 * is set to one, the old pixel value is totally overwritten. If
		 * opacity is set to zero, drawing operations have no visible
		 * effect. The default value is one.
		 **/
		void setOpacity(double opacity) { _dOpacity = opacity; }

		/**
		 * Get the current opacity.
		 **/
		double getOpacity() { return _dOpacity; }
		
		/**
		 * Shift the origin of this graphics context to the given
		 * coordinates. The coordinates of all subsequent drawing
		 * operations will be interpreted relative to this position. The
		 * origin can lie outside of the boundaried of the graphics
		 * context. An example:
		 *
		 * <pre>
		 * //To move the origin to (30,20):
		 * graphics.translate(30,20);
		 * //To move the origin back:
		 * graphics.translate(-30,-20);
		 * </pre>
		 *
		 * In addition to the origin, this method also moves the current
		 * clipping region.
		 **/
		void translate(int x, int y)
		{
			_iOriginX += x;
			_iOriginY += y;
		}

		/**
		 * Get the current origin of the coordinate system.
		 **/
		Point<int> getOrigin() const { return Point<int>(_iOriginX, _iOriginY); }

		/**
		 * Set the clipping region. The clipping region determines the
		 * drawable area in a graphics context. All graphics primitives
		 * that fall outside of the clipping region, as determined by the
		 * Shape::contains(x,y) method, are not drawn. The clipping region
		 * is always interpreted relative to the current origin of the
		 * coordinate system. That is, moving the origin also moves the
		 * clipping region.
		 **/
		void setClip(const Shape* shape) { _pClipShape = shape; }
		
		/**
		 * Set a pixel in a graphics context with no interpolation
		 * (anti-aliasing). It is not possible to override this method. 
		 * Instead, subclasses should override the protected
		 * get/setPixel() methods.
		 *
		 * @param x the x coordinate
		 * @param y the y coordinate
		 **/
		void drawPixel(int x, int y) { drawPixel(x, y, _dOpacity); }

		/**
		 * Draw a pixel in a graphics context. This method uses bilinear
		 * interpolation to draw anti-aliased pixels. Up to four
		 * neighboring pixels closest to the given floating-point image
		 * coordinates may be affected.
		 * 
		 * @param x the x coordinate
		 * @param y the y coordinate
		 **/
		virtual void drawPixel(double x, double y);

		/**
		 * Modify a pixel. This method works in a similar way as
		 * drawPixel(), but instead of overwriting a pixel it modifies the
		 * old value. The current opacity is used to control the
		 * "strength" of the modification. 0 means no change, and 1 means
		 * the old value will be modified with exactly the given value. 
		 * The current drawing mode does not affect pixel modifications. 
		 * Therefore, this method allows one to alter the pixel data in
		 * many customized ways. The <i>modifier</i> template parameter
		 * can be any class with the T&nbsp;operator()&nbsp;(T,T) defined. 
		 * An example:
		 *
		 * <pre>
		 * class MyModifier
		 * {
		 * public:
		 *   int operator() (int a, int b) { return b-a; }
		 * };
		 * ...
		 * //Subtract the value of the pixel at (2.3, 3.5) from three (with anti-aliasing)
		 * graphics.setColor(3);
		 * graphics.modifyPixel&lt;MyModifier&gt;(2.3, 3.5);
		 *
		 * //Subtract the value of the pixel at (3, 4) from two (without anti-aliasing)
		 * graphics.setColor(4);
		 * graphics.setOpacity(0.5);
		 * graphics.modifyPixel&lt;MyModifier&gt;(3,4);
		 * </pre>
		 **/
		template <class modifier> inline void modifyPixel(double x, double y);

		/**
		 * Modify a pixel in an image without interpolation (anti-aliasing).
		 *
		 * @see #modifyPixel(double,double)
		 * @see #drawPixel(int,int)
		 **/
		template <class modifier> void modifyPixel(int x, int y) { modifyPixel<modifier>(x, y, _dOpacity); }

		/**
		 * Draw an anti-alised circle.
		 *
		 * @param centerX the x coordinate of the center of the circle
		 * @param centerY the y coordinate of the center of the circle
		 * @param radius the radius of the circle
		 **/
		virtual void drawCircle(double centerX, double centerY, double radius);

		/**
		 * Draw an anti-alised arc. Angles are represented as radians in
		 * counter-clockwise direction. Zero angle is at the direction of
		 * the horizontal x-axis.
		 *
		 * @param centerX the x coordinate of the center of the circle
		 * @param centerY the y coordinate of the center of the circle
		 * @param radius the radius of the circle
		 **/
		virtual void drawArc(double centerX, double centerY, double radius,
												 double startAngle, double endAngle);

		/**
		 * Draw an interpolated (anti-aliased) line.
		 *
		 * @param startX the x coordinate of the starting position
		 * @param startY the y coordinate of the starting position
		 * @param endX the x coordinate of the end position
		 * @param endY the y coordinate of the end position
		 **/
		virtual void drawLine(double startX, double startY,
													double endX, double endY);

		/**
		 * Draw a non-interpolated line.
		 *
		 * @param startX the x coordinate of the starting position
		 * @param startY the y coordinate of the starting position
		 * @param endX the x coordinate of the end position
		 * @param endY the y coordinate of the end position
		 **/
		virtual void drawLine(int startX, int startY,
													int endX, int endY);

		/**
		 * Draw a rectangle with non-interpolated borders.
		 *
		 * @param x the x coordinate of the upper left corner
		 * @param y the y coordinate of the upper left corner
		 * @param width the width of the rectangle
		 * @param height the height of the rectangle
		 **/
		virtual void drawRect(int x, int y, int width, int height) { rect(x, y, width, height); }

		/**
		 * Draw a rectangle with interpolated (anti-alised) borders.
		 *
		 * @param x the x coordinate of the upper left corner
		 * @param y the y coordinate of the upper left corner
		 * @param width the width of the rectangle
		 * @param height the height of the rectangle
		 **/
		virtual void drawRect(double x, double y, double width, double height) { rect(x, y, width, height); }

		virtual ~Graphics() {}
		
	protected:
		/**
		 * Create a new graphics context. Subclasses should provide the
		 * bounds of their device/buffer at initialization.
		 *
		 * @param width the width of the graphics buffer
		 * @param height the height of the graphics buffer
		 **/
		Graphics(int width=0, int height=0) :
			_dimBounds(width, height), _iOriginX(0), _iOriginY(0),
			_pClipShape(NULL), _color(0), _dOpacity(1), _drawMode(DRAWMODE_NORMAL) {}

		/**
		 * The dimensions of the graphics context. Subclasses must fill
		 * this class with the dimensions of the graphics device/buffer.
		 **/
		Dimension<int> _dimBounds;
		/**
		 * The coordinates of the current origin.
		 **/
		int _iOriginX, _iOriginY;
		/**
		 * The current clipping region.
		 **/
		const Shape* _pClipShape;
		/**
		 * The current drawing color. Set initially to zero.
		 **/
		T _color;
		/**
		 * The current opacity. Initially set to one.
		 **/
		double _dOpacity;
		/**
		 * The current drawing mode. Set initially to DRAWMODE_NORMAL.
		 **/
		DrawMode _drawMode;
		
		/**
		 * Get a pixel in a graphics context. Subclasses must implement
		 * this method to get the contents of the graphics device/buffer. 
		 * Boundary checking has been done before calling this method. 
		 * Thus, assuming _dimBounds has been set correctly, subclasses
		 * never need to check whether the given coordinates are within
		 * the allowed range.
		 **/
		virtual T getPixel(int x, int y) const = 0;
		/**
		 * Get a pixel in a graphics context. Subclasses must implement
		 * this method to get the contents of the graphics device/buffer.
		 * Boundary checking has been done before calling this method. 
		 * Thus, assuming _dimBounds has been set correctly, subclasses
		 * never need to check whether the given coordinates are within
		 * the allowed range.
		 **/
		virtual T& pixel(int x, int y) = 0;
		/**
		 * Set a pixel in a graphics context. Subclasses must implement
		 * this method to alter the graphics device/buffer. Boundary
		 * checking has been done before calling this method. Thus,
		 * assuming _dimBounds has been set correctly, subclasses never
		 * need to check whether the given coordinates are within the
		 * allowed range.
		 **/
		virtual void setPixel(int x, int y, T value) = 0;

	private:
		template <class modifier> void modifyPixel(int x, int y, double opacity);
		void drawPixel(int x, int y, double opacity);
		template <class U> void rect(U x, U y, U width, U height);
	};
	

	/**
	 * An implementation of the Graphics interface that draws on a
	 * matrix buffer.
	 **/
	template <class T> class MemoryGraphics : public Graphics<T>
	{
	public:
		/**
		 * Create a new MemoryGraphics object that uses the given matrix
		 * as an output buffer. All drawing methods will modify the pixels
		 * in this buffer. Note that the buffer is passed by a reference. 
		 * You must allocate and keep the storage yourself. A bad example:
		 *
		 * <pre>
		 * Matrix&lt;int&gt; *mat = new Matrix&lt;int&gt;(100,100);
		 * MemoryGraphics&lt;int&gt; graphics(*mat);
		 * delete mat; //OOPS!
		 * graphics.drawPixel(1,1); //segmentation fault
		 * </pre>
		 *
		 * A better one:
		 *
		 * <pre>
		 * Matrix&lt;int&gt; mat(100,100);
		 * MemoryGraphics&lt;int&gt; graphics(mat);
		 * graphics.drawPixel(1,1);
		 * </pre>
		 **/
		MemoryGraphics(util::Matrix<T>& buffer) : Graphics<T>(buffer.getColumns(), buffer.getRows()), _img(buffer) {}

	protected:
		T getPixel(int x, int y) const { return _img(y,x); }
		T& pixel(int x, int y) { return _img(y,x); }
		void setPixel(int x, int y, T value) { _img(y,x) = value; }

	private:
		util::Matrix<T>& _img;
	};


	template <class T> void Graphics<T>::drawPixel(double x, double y)
	{
		int iy = (int)floor(y), ix = (int)floor(x);
		double fy(y-iy), fx(x-ix);
		drawPixel(ix, iy, _dOpacity * (1-fx)*(1-fy));
		drawPixel(ix+1, iy, _dOpacity * fx*(1-fy));
		drawPixel(ix, iy+1, _dOpacity * (1-fx)*fy);
		drawPixel(ix+1, iy+1, _dOpacity * fx*fy);
	}

	template <class T> void Graphics<T>::drawPixel(int x, int y, double opacity)
	{
		int tx = x + _iOriginX;
		int ty = y + _iOriginY;
		if (tx >= 0 && tx < _dimBounds.width &&
				ty >= 0 && ty < _dimBounds.height &&
				(!_pClipShape || _pClipShape->contains(x,y)))
			{
				T& px(pixel(tx,ty));
				switch (_drawMode)
					{
					case DRAWMODE_NORMAL:
						px = T(px * (1.0-opacity) + _color * opacity);
						break;
					case DRAWMODE_PLUS:
						px += T(_color * opacity);
						break;
					case DRAWMODE_MINUS:
						px -= T(_color * opacity);
						break;
					case DRAWMODE_MULTIPLY:
						px *= T(_color * opacity);
						break;
					case DRAWMODE_DIVIDE:
						px /= T(_color * opacity);
						break;
					}
			}
	}

	template <class T>
	template <class modifier> void Graphics<T>::modifyPixel(double x, double y)
	{
		int iy = (int)floor(y), ix = (int)floor(x);
		double fy(y-iy), fx(x-ix);
		modifyPixel<modifier>(ix, iy, _dOpacity * (1-fx)*(1-fy));
		modifyPixel<modifier>(ix+1, iy, _dOpacity * fx*(1-fy));
		modifyPixel<modifier>(ix, iy+1, _dOpacity * (1-fx)*fy);
		modifyPixel<modifier>(ix+1, iy+1, _dOpacity * fx*fy);
	}

	template <class T>
	template <class modifier> void Graphics<T>::modifyPixel(int x, int y, double opacity)
	{
		static modifier mod;
		int tx = x + _iOriginX;
		int ty = y + _iOriginY;
		if (tx >= 0 && tx < _dimBounds.width &&
				ty >= 0 && ty < _dimBounds.height &&
				(!_pClipShape || _pClipShape->contains(x,y)))
			{
				T& px = pixel(tx,ty);
				px = mod(px, T(opacity * _color));
			}
	}

	template <class T> void Graphics<T>::drawCircle(double centerX, double centerY, double radius)
	{
		double step = M_PI/(4*radius);
		for (double angle=0; angle<=M_PI/4; angle+=step)
			{
				double s = radius*sin(angle), c = radius*cos(angle);
				drawPixel(centerX-s, centerY+c);
				drawPixel(centerX+s, centerY+c);
				drawPixel(centerX-s, centerY-c);
				drawPixel(centerX+s, centerY-c);
				drawPixel(centerX-c, centerY+s);
				drawPixel(centerX-c, centerY-s);
				drawPixel(centerX+c, centerY-s);
				drawPixel(centerX+c, centerY+s);
			}		
	}
	
	template <class T> void Graphics<T>::drawArc(double centerX, double centerY, double radius,
																							 double startAngle, double endAngle)
	{
		double step = M_PI/(4*radius);
		for (double angle=startAngle; angle<endAngle; angle+=step)
			drawPixel(centerX+radius*cos(angle), centerY-radius*sin(angle));
	}

	template <class T> void Graphics<T>::drawLine(double startX, double startY,
																								double endX, double endY)
	{
		double dX = endX-startX, dXAbs = fabs(dX);
		double dY = endY-startY, dYAbs = fabs(dY);
		if (dX == 0 && dY == 0)
			{
				drawPixel(startX, startY);
				return;
			}

		double steps = sqrt(dX*dX + dY*dY);

		if (dXAbs >= dYAbs)
			{
				double k = dY/dX;
				double diff = dX/steps;
				if (dX > 0)
					{
						for (double x=startX; x<=endX; x+=diff)
							drawPixel(x, (x-startX)*k + startY);
					}
				else
					{
						for (double x=startX; x>=endX; x+=diff)
							drawPixel(x, (x-endX)*k + endY);
					}
			}
		else
			{
				double k = dX/dY;
				double diff = dY/steps;
				if (dY > 0)
					{
						for (double y=startY; y<=endY; y+=diff)
							drawPixel((y-startY)*k + startX, y);
					}
				else
					{
						for (double y=startY; y>=endY; y+=diff)
							drawPixel((y-startY)*k + startX, y);
					}
			}
	}

	template <class T> void Graphics<T>::drawLine(int startX, int startY,
																								int endX, int endY)
	{
		int dX = endX-startX, dXAbs = absolute(dX);
		int dY = endY-startY, dYAbs = absolute(dY);
		if (dX == 0 && dY == 0)
			{
				drawPixel(startX, startY);
				return;
			}

		if (dXAbs >= dYAbs)
			{
				double k = double(dY)/double(dX);
				if (dX > 0)
					{
						for (int x=startX; x<=endX; x++)
							drawPixel(x, int(k*(x-startX)+0.5) + startY);
					}
				else
					{
						for (int x=startX; x>=endX; x--)
							drawPixel(x, int(k*(x-startX)+0.5) + startY);
					}
			}		
		else
			{
				if (dY > 0)
					{
						double k = double(dX)/double(dY);
						for (int y=startY; y<=endY; y++)
							drawPixel(int(k*(y-startY)) + startX, y);
					}
				else
					{
						double k = double(dX)/double(dY);
						for (int y=startY; y>=endY; y--)
							drawPixel(int(k*(y-startY)) + startX, y);
					}
			}
	}


	template <class T>
	template <class U> void Graphics<T>::rect(U x, U y, U width, U height)
	{
		U w = width >= 0 ? width-1 : width+1;
		U h = height >= 0 ? height-1 : height+1;

		drawLine(x, y, x+w, y);
		drawLine(x+w, y, x+w, y+h);
		drawLine(x+w, y+h, x, y+h);
		drawLine(x, y+h, x, y);
	}
}}

#endif
