/* This file is part of the KDE project
   Copyright (C) 2002, 2003 The Karbon Developers

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include "svgpathparser.h"
#include <tqstring.h>
#include <math.h>

// parses the number into parameter number
const char *
KSVG::getNumber( const char *ptr, double &number )
{
	int integer, exponent;
	double decimal, frac;
	int sign, expsign;

	exponent = 0;
	integer = 0;
	frac = 1.0;
	decimal = 0;
	sign = 1;
	expsign = 1;

	// read the sign
	if(*ptr == '+')
		ptr++;
	else if(*ptr == '-')
	{
		ptr++;
		sign = -1;
	}

	// read the integer part
	while(*ptr != '\0' && *ptr >= '0' && *ptr <= '9')
		integer = (integer * 10) + *(ptr++) - '0';
	if(*ptr == '.') // read the decimals
	{
		ptr++;
		while(*ptr != '\0' && *ptr >= '0' && *ptr <= '9')
			decimal += (*(ptr++) - '0') * (frac *= 0.1);
	}

	if(*ptr == 'e' || *ptr == 'E') // read the exponent part
	{
		ptr++;

		// read the sign of the exponent
		if(*ptr == '+')
			ptr++;
		else if(*ptr == '-')
		{
			ptr++;
			expsign = -1;
		}

		exponent = 0;
		while(*ptr != '\0' && *ptr >= '0' && *ptr <= '9')
		{
			exponent *= 10;
			exponent += *ptr - '0';
			ptr++;
		}
	}
	number = integer + decimal;
	number *= sign * pow( (double)10, double( expsign * exponent ) );

	return ptr;
}

// parses the coord into parameter number and forwards to the next coord in the path data
const char *
SVGPathParser::getCoord( const char *ptr, double &number )
{
	ptr = KSVG::getNumber( ptr, number );
	// skip the following space
	if(*ptr == ' ')
		ptr++;

	return ptr;
}

void
SVGPathParser::parseSVG( const TQString &s, bool process )
{
	if(!s.isEmpty())
	{
		TQString d = s;
		d = d.replace(',', ' ');
		d = d.simplifyWhiteSpace();
		const char *ptr = d.latin1();
		const char *end = d.latin1() + d.length() + 1;

		double contrlx, contrly, curx, cury, subpathx, subpathy, tox, toy, x1, y1, x2, y2, xc, yc;
		double px1, py1, px2, py2, px3, py3;
		bool relative, closed = true;
		char command = *(ptr++), lastCommand = ' ';

		subpathx = subpathy = curx = cury = contrlx = contrly = 0.0;
		while( ptr < end )
		{
			if( *ptr == ' ' )
				ptr++;

			relative = false;

			//std::cout << "Command : " << command << std::endl;
			switch( command )
			{
				case 'm':
					relative = true;
				case 'M':
				{
					ptr = getCoord( ptr, tox );
					ptr = getCoord( ptr, toy );

					if( process )
					{
						subpathx = curx = relative ? curx + tox : tox;
						subpathy = cury = relative ? cury + toy : toy;

						svgMoveTo( curx, cury, closed );
					}
					else
						svgMoveTo( tox, toy, closed, !relative );
					closed = false;
					break;
				}
				case 'l':
					relative = true;
				case 'L':
				{
					ptr = getCoord( ptr, tox );
					ptr = getCoord( ptr, toy );

					if( process )
					{
						curx = relative ? curx + tox : tox;
						cury = relative ? cury + toy : toy;

						svgLineTo( curx, cury );
					}
					else
						svgLineTo( tox, toy, !relative );
					break;
				}
				case 'h':
				{
					ptr = getCoord( ptr, tox );
					if( process )
					{
						curx = curx + tox;
						svgLineTo( curx, cury );
					}
					else
						svgLineToHorizontal( tox, false );
					break;
				}
				case 'H':
				{
					ptr = getCoord( ptr, tox );
					if( process )
					{
						curx = tox;
						svgLineTo( curx, cury );
					}
					else
						svgLineToHorizontal( tox );
					break;
				}
				case 'v':
				{
					ptr = getCoord( ptr, toy );
					if( process )
					{
						cury = cury + toy;
						svgLineTo( curx, cury );
					}
					else
						svgLineToVertical( toy, false );
					break;
				}
				case 'V':
				{
					ptr = getCoord( ptr, toy );
					if( process )
					{
						cury = toy;
						svgLineTo( curx, cury );
					}
					else
						svgLineToVertical( toy );
					break;
				}
				case 'z':
				case 'Z':
				{
					// reset curx, cury for next path
					if( process )
					{
						curx = subpathx;
						cury = subpathy;
					}
					closed = true;
					svgClosePath();
					break;
				}
				case 'c':
					relative = true;
				case 'C':
				{
					ptr = getCoord( ptr, x1 );
					ptr = getCoord( ptr, y1 );
					ptr = getCoord( ptr, x2 );
					ptr = getCoord( ptr, y2 );
					ptr = getCoord( ptr, tox );
					ptr = getCoord( ptr, toy );

					if( process )
					{
						px1 = relative ? curx + x1 : x1;
						py1 = relative ? cury + y1 : y1;
						px2 = relative ? curx + x2 : x2;
						py2 = relative ? cury + y2 : y2;
						px3 = relative ? curx + tox : tox;
						py3 = relative ? cury + toy : toy;

						svgCurveToCubic( px1, py1, px2, py2, px3, py3 );

						contrlx = relative ? curx + x2 : x2;
						contrly = relative ? cury + y2 : y2;
						curx = relative ? curx + tox : tox;
						cury = relative ? cury + toy : toy;
					}
					else
						svgCurveToCubic( x1, y1, x2, y2, tox, toy, !relative );

					break;
				}
				case 's':
					relative = true;
				case 'S':
				{
					ptr = getCoord( ptr, x2 );
					ptr = getCoord( ptr, y2 );
					ptr = getCoord( ptr, tox );
					ptr = getCoord( ptr, toy );

					if( process )
					{
						px1 = 2 * curx - contrlx;
						py1 = 2 * cury - contrly;
						px2 = relative ? curx + x2 : x2;
						py2 = relative ? cury + y2 : y2;
						px3 = relative ? curx + tox : tox;
						py3 = relative ? cury + toy : toy;

						svgCurveToCubic( px1, py1, px2, py2, px3, py3 );

						contrlx = relative ? curx + x2 : x2;
						contrly = relative ? cury + y2 : y2;
						curx = relative ? curx + tox : tox;
						cury = relative ? cury + toy : toy;
					}
					else
						svgCurveToCubicSmooth( x2, y2, tox, toy, !relative );
					break;
				}
				case 'q':
					relative = true;
				case 'Q':
				{
					ptr = getCoord( ptr, x1 );
					ptr = getCoord( ptr, y1 );
					ptr = getCoord( ptr, tox );
					ptr = getCoord( ptr, toy );

					if( process )
					{
						px1 = relative ? (curx + 2 * (x1 + curx)) * (1.0 / 3.0) : (curx + 2 * x1) * (1.0 / 3.0);
						py1 = relative ? (cury + 2 * (y1 + cury)) * (1.0 / 3.0) : (cury + 2 * y1) * (1.0 / 3.0);
						px2 = relative ? ((curx + tox) + 2 * (x1 + curx)) * (1.0 / 3.0) : (tox + 2 * x1) * (1.0 / 3.0);
						py2 = relative ? ((cury + toy) + 2 * (y1 + cury)) * (1.0 / 3.0) : (toy + 2 * y1) * (1.0 / 3.0);
						px3 = relative ? curx + tox : tox;
						py3 = relative ? cury + toy : toy;

						svgCurveToCubic( px1, py1, px2, py2, px3, py3 );

						contrlx = relative ? curx + x1 : (tox + 2 * x1) * (1.0 / 3.0);
						contrly = relative ? cury + y1 : (toy + 2 * y1) * (1.0 / 3.0);
						curx = relative ? curx + tox : tox;
						cury = relative ? cury + toy : toy;
					}
					else
						svgCurveToQuadratic( x1, y1, tox, toy, !relative );
					break;
				}
				case 't':
					relative = true;
				case 'T':
				{
					ptr = getCoord(ptr, tox);
					ptr = getCoord(ptr, toy);

					if( process )
					{
						xc = 2 * curx - contrlx;
						yc = 2 * cury - contrly;

						px1 = (curx + 2 * xc) * (1.0 / 3.0);
						py1 = (cury + 2 * yc) * (1.0 / 3.0);
						px2 = relative ? ((curx + tox) + 2 * xc) * (1.0 / 3.0) : (tox + 2 * xc) * (1.0 / 3.0);
						py2 = relative ? ((cury + toy) + 2 * yc) * (1.0 / 3.0) : (toy + 2 * yc) * (1.0 / 3.0);
						px3 = relative ? curx + tox : tox;
						py3 = relative ? cury + toy : toy;

						svgCurveToCubic( px1, py1, px2, py2, px3, py3 );

						contrlx = xc;
						contrly = yc;
						curx = relative ? curx + tox : tox;
						cury = relative ? cury + toy : toy;
					}
					else
						svgCurveToQuadraticSmooth( tox, toy, !relative );
					break;
				}
				case 'a':
					relative = true;
				case 'A':
				{
					bool largeArc, sweep;
					double angle, rx, ry;
					ptr = getCoord( ptr, rx );
					ptr = getCoord( ptr, ry );
					ptr = getCoord( ptr, angle );
					ptr = getCoord( ptr, tox );
					largeArc = tox == 1;
					ptr = getCoord( ptr, tox );
					sweep = tox == 1;
					ptr = getCoord( ptr, tox );
					ptr = getCoord( ptr, toy );
					
					// Spec: radii are nonnegative numbers
					rx = fabs(rx);
					ry = fabs(ry);

					if( process )
						calculateArc( relative, curx, cury, angle, tox, toy, rx, ry, largeArc, sweep );
					else
						svgArcTo( tox, toy, rx, ry, angle, largeArc, sweep, !relative );
				}
			}

			lastCommand = command;

			if(*ptr == '+' || *ptr == '-' || (*ptr >= '0' && *ptr <= '9'))
			{
				// there are still coords in this command
				if(command == 'M')
					command = 'L';
				else if(command == 'm')
					command = 'l';
			}
			else
				command = *(ptr++);

			if( lastCommand != 'C' && lastCommand != 'c' &&
				lastCommand != 'S' && lastCommand != 's' &&
				lastCommand != 'Q' && lastCommand != 'q' &&
				lastCommand != 'T' && lastCommand != 't')
			{
				contrlx = curx;
				contrly = cury;
			}
		}
	}
}

// This works by converting the SVG arc to "simple" beziers.
// For each bezier found a svgToCurve call is done.
// Adapted from Niko's code in tdelibs/tdecore/svgicons.
// Maybe this can serve in some shared lib? (Rob)
void
SVGPathParser::calculateArc(bool relative, double &curx, double &cury, double angle, double x, double y, double r1, double r2, bool largeArcFlag, bool sweepFlag)
{
	double sin_th, cos_th;
	double a00, a01, a10, a11;
	double x0, y0, x1, y1, xc, yc;
	double d, sfactor, sfactor_sq;
	double th0, th1, th_arc;
	int i, n_segs;

	sin_th = sin(angle * (M_PI / 180.0));
	cos_th = cos(angle * (M_PI / 180.0));

	double dx;

	if(!relative)
		dx = (curx - x) / 2.0;
	else
		dx = -x / 2.0;

	double dy;
		
	if(!relative)
		dy = (cury - y) / 2.0;
	else
		dy = -y / 2.0;
		
	double _x1 =  cos_th * dx + sin_th * dy;
	double _y1 = -sin_th * dx + cos_th * dy;
	double Pr1 = r1 * r1;
	double Pr2 = r2 * r2;
	double Px = _x1 * _x1;
	double Py = _y1 * _y1;

	// Spec : check if radii are large enough
	double check = Px / Pr1 + Py / Pr2;
	if(check > 1)
	{
		r1 = r1 * sqrt(check);
		r2 = r2 * sqrt(check);
	}

	a00 = cos_th / r1;
	a01 = sin_th / r1;
	a10 = -sin_th / r2;
	a11 = cos_th / r2;

	x0 = a00 * curx + a01 * cury;
	y0 = a10 * curx + a11 * cury;

	if(!relative)
		x1 = a00 * x + a01 * y;
	else
		x1 = a00 * (curx + x) + a01 * (cury + y);
		
	if(!relative)
		y1 = a10 * x + a11 * y;
	else
		y1 = a10 * (curx + x) + a11 * (cury + y);

	/* (x0, y0) is current point in transformed coordinate space.
	   (x1, y1) is new point in transformed coordinate space.

	   The arc fits a unit-radius circle in this space.
	*/

	d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);

	sfactor_sq = 1.0 / d - 0.25;

	if(sfactor_sq < 0)
		sfactor_sq = 0;

	sfactor = sqrt(sfactor_sq);

	if(sweepFlag == largeArcFlag)
		sfactor = -sfactor;

	xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
	yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);

	/* (xc, yc) is center of the circle. */
	th0 = atan2(y0 - yc, x0 - xc);
	th1 = atan2(y1 - yc, x1 - xc);

	th_arc = th1 - th0;
	if(th_arc < 0 && sweepFlag)
		th_arc += 2 * M_PI;
	else if(th_arc > 0 && !sweepFlag)
		th_arc -= 2 * M_PI;

	n_segs = (int) (int) ceil(fabs(th_arc / (M_PI * 0.5 + 0.001)));

	for(i = 0; i < n_segs; i++)
	{
		{
			double sin_th, cos_th;
			double a00, a01, a10, a11;
			double x1, y1, x2, y2, x3, y3;
			double t;
			double th_half;

			double _th0 = th0 + i * th_arc / n_segs;
			double _th1 = th0 + (i + 1) * th_arc / n_segs;

			sin_th = sin(angle * (M_PI / 180.0));
			cos_th = cos(angle * (M_PI / 180.0));

			/* inverse transform compared with rsvg_path_arc */
			a00 = cos_th * r1;
			a01 = -sin_th * r2;
			a10 = sin_th * r1;
			a11 = cos_th * r2;

			th_half = 0.5 * (_th1 - _th0);
			t = (8.0 / 3.0) * sin(th_half * 0.5) * sin(th_half * 0.5) / sin(th_half);
			x1 = xc + cos(_th0) - t * sin(_th0);
			y1 = yc + sin(_th0) + t * cos(_th0);
			x3 = xc + cos(_th1);
			y3 = yc + sin(_th1);
			x2 = x3 + t * sin(_th1);
			y2 = y3 - t * cos(_th1);

			svgCurveToCubic( a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, a00 * x3 + a01 * y3, a10 * x3 + a11 * y3 );
		}
	}

	if(!relative)
		curx = x;
	else
		curx += x;

	if(!relative)
		cury = y;
	else
		cury += y;	
}

void
SVGPathParser::svgLineToHorizontal( double, bool )
{
}

void
SVGPathParser::svgLineToVertical( double, bool )
{
}

void
SVGPathParser::svgCurveToCubicSmooth( double, double, double, double, bool )
{
}

void
SVGPathParser::svgCurveToQuadratic( double, double, double, double, bool )
{
}

void
SVGPathParser::svgCurveToQuadraticSmooth( double, double, bool )
{
}

void
SVGPathParser::svgArcTo( double, double, double, double, double, bool, bool, bool )
{
}