/*************************************************************************
** GraphicPath.h                                                        **
**                                                                      **
** This file is part of dvisvgm -- the DVI to SVG converter             **
** Copyright (C) 2005-2010 Martin Gieseking <martin.gieseking@uos.de>   **
**                                                                      **
** This program is free software; you can redistribute it and/or        ** 
** modify it under the terms of the GNU General Public License as       **
** published by the Free Software Foundation; either version 3 of       **
** the License, or (at your option) any later version.                  **
**                                                                      **
** This program is distributed in the hope that it will be useful, but  **
** WITHOUT ANY WARRANTY; without even the implied warranty of           **
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the         **
** GNU General Public License for more details.                         **
**                                                                      **
** You should have received a copy of the GNU General Public License    **
** along with this program; if not, see <http://www.gnu.org/licenses/>. ** 
*************************************************************************/

#ifndef GRAPHICPATH_H
#define GRAPHICPATH_H

#include <list>
#include <ostream>
#include <vector>
#include "BoundingBox.h"
#include "Matrix.h"
#include "Pair.h"


template <typename T>
class GraphicPath
{
	public:
		typedef Pair<T> Point;

		struct Command {
			enum Type {MOVETO, LINETO, CONICTO, CUBICTO, CLOSEPATH};

			Command (Type t) : type(t) {}

			Command (Type t, const Point &p) : type(t) {
				params[0] = p;
			}

			Command (Type t, const Point &p1, const Point &p2) : type(t) {
				params[0] = p1;
				params[1] = p2;
			}

			Command (Type t, const Point &p1, const Point &p2, const Point &p3) : type(t) {
				params[0] = p1;
				params[1] = p2;
				params[2] = p3;
			}

			int numParams () const {
				switch (type) {
					case CLOSEPATH : return 0;
					case MOVETO    :
					case LINETO    : return 1;
					case CONICTO   : return 2;
					case CUBICTO   : return 3;
				}
				return 0;
			}

			void transform (const Matrix &matrix) {
				for (int i=0; i < numParams(); i++)
					params[i] = matrix * params[i];
			}

			Type type;
			Point params[3];
		};

		struct Actions
		{
			virtual ~Actions () {}
			virtual void moveto (const Point &p) {}
			virtual void lineto (const Point &p) {}
			virtual void hlineto (const T &y) {}
			virtual void vlineto (const T &x) {}
			virtual void sconicto (const Point &p) {}
			virtual void conicto (const Point &p1, const Point &p2) {}
			virtual void scubicto (const Point &p1, const Point &p2) {}
			virtual void cubicto (const Point &p1, const Point &p2, const Point &p3) {}
			virtual void closepath () {}
			virtual void draw (char cmd, const Point *points, int n) {}
			virtual bool quit () {return false;}
		};

		typedef typename std::vector<Command>::iterator Iterator;
		typedef typename std::vector<Command>::const_iterator ConstIterator;
		typedef typename std::vector<Command>::const_reverse_iterator ConstRevIterator;

   public:
		void newpath () {
			_commands.clear();
		}

		/// Returns true if path is empty (there is nothing to draw)
		bool empty () const {
			return _commands.empty();
		}

		size_t size () const {
			return _commands.size();
		}

		void moveto (const T &x, const T &y) {
			moveto(Point(x, y));
		}

		void moveto (const Point &p) {
			// avoid sequences of several MOVETOs; always use latest
			if (_commands.empty() || _commands.back().type != Command::MOVETO)
				_commands.push_back(Command(Command::MOVETO, p));
			else
				_commands.back().params[0] = p;
		}

		void lineto (const T &x, const T &y) {
			lineto(Point(x, y));
		}

		void lineto (const Point &p) {
			_commands.push_back(Command(Command::LINETO, p));
		}

		void conicto (const T &x1, const T &y1, const T &x2, const T &y2) {
			conicto(Point(x1, y1), Point(x2, y2));
		}

		void conicto (const Point &p1, const Point &p2) {
			_commands.push_back(Command(Command::CONICTO, p1, p2));
		}

		void cubicto (const T &x1, const T &y1, const T &x2, const T &y2, const T &x3, const T &y3) {
			cubicto(Point(x1, y1), Point(x2, y2), Point(x3, y3));
		}

		void cubicto (const Point &p1, const Point &p2, const Point &p3) {
			_commands.push_back(Command(Command::CUBICTO, p1, p2, p3));
		}

		void closepath () {
			_commands.push_back(Command(Command::CLOSEPATH));
		}

		
		const std::vector<Command>& commands () const {
			return _commands;
		}


		/** Detects all open subpaths and closes them by adding a closePath command.
		 *	 Most font formats only support closed outline paths so there are no explicit closePath statements
		 *	 in the glyph's outline description. All open paths are automatically closed by the renderer.
		 *	 This method detects all open paths and adds the missing closePath statement. */
		void closeOpenSubPaths () {
			Command *prevCommand = 0;
			FORALL(_commands, Iterator, it) {
				if (it->type == Command::MOVETO && prevCommand && prevCommand->type != Command::CLOSEPATH) {
					prevCommand = &(*it);
					it = _commands.insert(it, Command(Command::CLOSEPATH))+1;
//					++it; // skip inserted closePath command in next iteration step
				}
				else
					prevCommand = &(*it);
			}
			if (!_commands.empty() && _commands.back().type != Command::CLOSEPATH)
				closepath();
		}


		void writeSVG (std::ostream &os, double sx=1.0, double sy=1.0) const {
			struct WriteActions : Actions {
				WriteActions (std::ostream &os, double sx, double sy) : _os(os), _sx(sx), _sy(sy) {}
				void draw (char cmd, const Point *points, int n) {
					_os << cmd;
					switch (cmd) {
						case 'H': _os << _sx*points->x(); break;
						case 'V': _os << _sy*points->y(); break;
						default :
							for (int i=0; i < n; i++) {
								if (i > 0)
									_os << ' ';
								_os << _sx*points[i].x() << ' ' << _sy*points[i].y();
							}
					}
				}
				std::ostream &_os;
				double _sx, _sy;
			} actions(os, sx, sy);
			iterate(actions, true);
		}


		void computeBBox (BoundingBox &bbox) const {
			struct BBoxActions : Actions {
				BBoxActions (BoundingBox &bb) : bbox(bb) {}
				void moveto (const Point &p) {bbox.embed(p);}
				void lineto (const Point &p) {bbox.embed(p);}
				void conicto (const Point &p1, const Point &p2) {bbox.embed(p1); bbox.embed(p2);}
				void cubicto (const Point &p1, const Point &p2, const Point &p3) {bbox.embed(p1); bbox.embed(p2); bbox.embed(p3);}
				BoundingBox &bbox;
			} actions(bbox);
			iterate(actions, false);
		}

		bool isDot (Point &p) const {
			struct DotActions : Actions {
				DotActions () : differs(false) {}
				void moveto (const Point &p) {point = p;}
				void lineto (const Point &p) {differs = (p != point);}
				void conicto (const Point &p1, const Point &p2) {differs = (point != p1 || point != p2);}
				void cubicto (const Point &p1, const Point &p2, const Point &p3) {differs = (point != p1 || point != p2 || point != p3);}
				bool quit () {return differs;}
				Point point;
				bool differs;
			} actions;
			iterate(actions, false);
			p = actions.point;
			return !actions.differs;
		}

		void transform (const Matrix &matrix) {
			FORALL(_commands, Iterator, it)
				it->transform(matrix);
		}

		void iterate (Actions &actions, bool optimize) const;

   private:
		std::vector<Command> _commands;
};


/** Iterates over all commands defining this path and calls the corresponding template methods.  
 *  In the case of successive bezier curve sequences, control points or tangent slopes are often
 *  identical so that the path description contains redundant information. SVG provides shorthand
 *  curve commands that require less parameters. If 'optimize' is true, this method detects such 
 *  command sequences.
 *  @param[in] actions template methods called by each iteration step
 *  @param[in] optimize if true, shorthand drawing commands (sconicto, scubicto,...) are considered */
template <typename T>
void GraphicPath<T>::iterate (Actions &actions, bool optimize) const {
	ConstIterator prev = _commands.end();  // pointer to preceding command
	Point fp; // first point of current path
	Point cp; // current point
	Point pstore[2];
	for (ConstIterator it=_commands.begin(); it != _commands.end() && !actions.quit(); ++it) {
		const Point *params = it->params;
		switch (it->type) {
			case Command::MOVETO:
				actions.moveto(params[0]);
				actions.draw('M', params, 1);
				fp = params[0];
				break;
			case Command::LINETO:
				if (optimize) {
					if (cp.x() == params[0].x()) {
						actions.vlineto(params[0].y());
						actions.draw('V', params, 1);
					}
					else if (cp.y() == params[0].y()) {
						actions.hlineto(params[0].x());
						actions.draw('H', params, 1);
					}
					else {
						actions.lineto(params[0]);
						actions.draw('L', params, 1);
					}
				}
				else {
					actions.lineto(params[0]);
					actions.draw('L', params, 1);
				}
				break;
			case Command::CONICTO:
				if (optimize && prev != _commands.end() && prev->type == Command::CONICTO && params[0] == pstore[1]*T(2)-pstore[0]) {
					actions.sconicto(params[1]);
					actions.draw('T', params+1, 1);
				}
				else {
					actions.conicto(params[0], params[1]);
					actions.draw('Q', params, 2);
				}
				pstore[0] = params[0]; // store control point and
				pstore[1] = params[1]; // curve endpoint
				break;
			case Command::CUBICTO:
				// is first control point reflection of preceding second control point?
				if (optimize && prev != _commands.end() && prev->type == Command::CUBICTO && params[0] == pstore[1]*T(2)-pstore[0]) {
					actions.scubicto(params[1], params[2]);
					actions.draw('S', params+1, 2);
				}
				else {
					actions.cubicto(params[0], params[1], params[2]);
					actions.draw('C', params, 3);
				}
				pstore[0] = params[1]; // store second control point and
				pstore[1] = params[2]; // curve endpoint
				break;
			case Command::CLOSEPATH:
				actions.closepath();
				actions.draw('Z', params, 0);
				cp = fp;
		}
		// update current point
		const int np = it->numParams();
		if (np > 0)
			cp = it->params[np-1];
		prev = it;
	}
}

#endif
