/*
* This file is part of project OpenEmbroidery. It's copyrighted by
* the contributors recorded in the version control history of the file.
* Original project location https://code.electrolab.fr/openEmbroidery/openEmbroidery_software
*
* SPDX-License-Identifier: CECILL-2.1
* License-Filename: Licence_CeCILL_V2.1-en.txt
*/

#include "OE_io.h"
#include "OE_utils.h"
#include "OE_document.h"

#include "xml/tinyxml.h"

#include "QString" // For locale free str to float conversion
#include <iostream>
#include <math.h>

#ifndef M_PIf32
	#define M_PIf32 3.141592653589793f
#endif


static bool loadFromSVG(std::string path, OE_document* document);

extern const OE_io ioSvg;
const OE_io ioSvg = {"svg",
                     "Scalable Vector Graphics",
                     loadFromSVG,
                     nullptr};

struct SVGContext
{
	SVGContext(OE_document* document) : document(document) {}
	OE_document* document;
	OE_Matrix matrix;
};

static bool parseSvg(SVGContext& context, TiXmlElement *input);
static bool parsePath(SVGContext& context, TiXmlElement *input);
static bool pushCurve(SVGContext& context, OE_pointcurve * curve);

static bool loadFromSVG(std::string path, OE_document* document)
{
	// Xml load and parse
	TiXmlDocument doc(path.c_str());
	if (!doc.LoadFile())
	{
		std::cerr << "error during loading of the following file :" << std::endl;
		std::cerr << "error #" << doc.ErrorId() << " : " << doc.ErrorDesc() << std::endl;
		return false;
	}
	TiXmlHandle hdl(&doc);
	TiXmlElement *object = hdl.FirstChildElement("svg").Element();

	// Svg parse and extract
	SVGContext context(document);
	OE_document::ScopeLock lock(*document);
	parseSvg(context, object);

	// Center document around (0,0)
	BoundingBox bb = document->getBound();
	for (auto curve : document->curves)
	{
		curve->move(vector_2d(0,0)-bb.getCenter());
	}

	// Rescale document if larger than hoop
	bb = document->getBound();
	float docW = bb.getMax().x-bb.getMin().x;
	float docH = bb.getMax().y-bb.getMin().y;
	float hoopW = document->getHoopSize().x;
	float hoopH = document->getHoopSize().y;
	if (docW > hoopW || docH > hoopH)
	{
		float ratio = nanf("");
		if (docW >= docH && docW != 0.0f)
		{
			ratio = hoopW / (docW);
		}
		else if (docW < docH && docH != 0.0f)
		{
			ratio = hoopH / (docH);
		}
		if (!std::isnan(ratio))
		{
			for (auto curve : document->curves)
			{
				curve->scale(vector_2d(ratio, ratio), bb.getCenter());
			}
		}
	}

	return true;
}

static float sqr(float x)
{
	return x*x;
}

static float vmag(float x, float y)
{
	return sqrtf(x*x + y*y);
}

static float vecrat(float ux, float uy, float vx, float vy)
{
	return (ux*vx + uy*vy) / (vmag(ux,uy) * vmag(vx,vy));
}

static float vecang(float ux, float uy, float vx, float vy)
{
	float r = vecrat(ux,uy, vx,vy);
	if (r < -1.0f) r = -1.0f;
	if (r > 1.0f) r = 1.0f;
	return ((ux*vy < uy*vx) ? -1.0f : 1.0f) * acosf(r);
}

static void xformPoint(float* dx, float* dy, float x, float y, float* t)
{
	*dx = x*t[0] + y*t[2] + t[4];
	*dy = x*t[1] + y*t[3] + t[5];
}

static void xformVec(float* dx, float* dy, float x, float y, float* t)
{
	*dx = x*t[0] + y*t[2];
	*dy = x*t[1] + y*t[3];
}

static int isspace(char c)
{
	return strchr(" \t\n\v\f\r", c) != nullptr;
}

static int isdigit(char c)
{
	return strchr("0123456789", c) != nullptr;
}

static int isnum(char c)
{
	return strchr("0123456789+-.eE", c) != nullptr;
}

static const char* parseNumber(const char* s, char* it, const int size)
{
	const int last = size-1;
	int i = 0;

	// sign
	if (*s == '-' || *s == '+')
	{
		if (i < last)
		{
			it[i++] = *s;
		}
		s++;
	}
	// integer part
	while (*s && isdigit(*s))
	{
		if (i < last)
		{
			it[i++] = *s;
		}
		s++;
	}
	if (*s == '.')
	{
		// decimal point
		if (i < last)
		{
			it[i++] = *s;
		}
		s++;
		// fraction part
		while (*s && isdigit(*s))
		{
			if (i < last)
			{
				it[i++] = *s;
			}
			s++;
		}
	}
	// exponent
	if (*s == 'e' || *s == 'E')
	{
		if (i < last)
		{
			it[i++] = *s;
		}
		s++;
		if (*s == '-' || *s == '+')
		{
			if (i < last)
			{
				it[i++] = *s;
			}
			s++;
		}
		while (*s && isdigit(*s))
		{
			if (i < last)
			{
				it[i++] = *s;
			}
			s++;
		}
	}
	it[i] = '\0';
	return s;
}

static void pathMoveTo(OE_pointcurve* curve, float* cpx, float* cpy, float* args, int rel)
{
	if (rel)
	{
		*cpx += args[0];
		*cpy += args[1];
	}
	else
	{
		*cpx = args[0];
		*cpy = args[1];
	}
	curve->addPoint(*cpx, *cpy);
}

static void pathLineTo(OE_pointcurve* curve, float* cpx, float* cpy, float* args, int rel)
{
	if (rel)
	{
		*cpx += args[0];
		*cpy += args[1];
	}
	else
	{
		*cpx = args[0];
		*cpy = args[1];
	}
	curve->lineTo(*cpx, *cpy);
}

static void pathHLineTo(OE_pointcurve* curve, float* cpx, float* cpy, float* args, int rel)
{
	if (rel)
	{
		*cpx += args[0];
	}
	else
	{
		*cpx = args[0];
	}
	curve->lineTo(*cpx, *cpy);
}

static void pathVLineTo(OE_pointcurve* curve, float* cpx, float* cpy, float* args, int rel)
{
	if (rel)
	{
		*cpy += args[0];
	}
	else
	{
		*cpy = args[0];
	}
	curve->lineTo(*cpx, *cpy);
}

static void pathCubicBezTo(OE_pointcurve* curve, float* cpx, float* cpy, float* cpx2, float* cpy2, float* args, int rel)
{
	float  x2, y2, cx1, cy1, cx2, cy2;

	//x1 = *cpx;
	//y1 = *cpy;
	if (rel)
	{
		cx1 = *cpx + args[0];
		cy1 = *cpy + args[1];
		cx2 = *cpx + args[2];
		cy2 = *cpy + args[3];
		x2 = *cpx + args[4];
		y2 = *cpy + args[5];
	}
	else
	{
		cx1 = args[0];
		cy1 = args[1];
		cx2 = args[2];
		cy2 = args[3];
		x2 = args[4];
		y2 = args[5];
	}

	curve->cubicBezTo(cx1,cy1, cx2,cy2, x2,y2);

	*cpx2 = cx2;
	*cpy2 = cy2;
	*cpx = x2;
	*cpy = y2;
}

static void pathCubicBezShortTo(OE_pointcurve* curve, float* cpx, float* cpy, float* cpx2, float* cpy2, float* args, int rel)
{
	float x1, y1, x2, y2, cx1, cy1, cx2, cy2;

	x1 = *cpx;
	y1 = *cpy;
	if (rel)
	{
		cx2 = *cpx + args[0];
		cy2 = *cpy + args[1];
		x2 = *cpx + args[2];
		y2 = *cpy + args[3];
	}
	else
	{
		cx2 = args[0];
		cy2 = args[1];
		x2 = args[2];
		y2 = args[3];
	}

	cx1 = 2*x1 - *cpx2;
	cy1 = 2*y1 - *cpy2;

	curve->cubicBezTo(cx1,cy1, cx2,cy2, x2,y2);

	*cpx2 = cx2;
	*cpy2 = cy2;
	*cpx = x2;
	*cpy = y2;
}

static void pathQuadBezTo(OE_pointcurve* curve, float* cpx, float* cpy, float* cpx2, float* cpy2, float* args, int rel)
{
	float x1, y1, x2, y2, cx, cy;
	float cx1, cy1, cx2, cy2;

	x1 = *cpx;
	y1 = *cpy;
	if (rel)
	{
		cx = *cpx + args[0];
		cy = *cpy + args[1];
		x2 = *cpx + args[2];
		y2 = *cpy + args[3];
	}
	else
	{
		cx = args[0];
		cy = args[1];
		x2 = args[2];
		y2 = args[3];
	}

	// Convert to cubic bezier
	cx1 = x1 + 2.0f/3.0f*(cx - x1);
	cy1 = y1 + 2.0f/3.0f*(cy - y1);
	cx2 = x2 + 2.0f/3.0f*(cx - x2);
	cy2 = y2 + 2.0f/3.0f*(cy - y2);

	curve->cubicBezTo(cx1,cy1, cx2,cy2, x2,y2);

	*cpx2 = cx;
	*cpy2 = cy;
	*cpx = x2;
	*cpy = y2;
}

static void pathQuadBezShortTo(OE_pointcurve* curve, float* cpx, float* cpy, float* cpx2, float* cpy2, float* args, int rel)
{
	float x1, y1, x2, y2, cx, cy;
	float cx1, cy1, cx2, cy2;

	x1 = *cpx;
	y1 = *cpy;
	if (rel)
	{
		x2 = *cpx + args[0];
		y2 = *cpy + args[1];
	}
	else
	{
		x2 = args[0];
		y2 = args[1];
	}

	cx = 2*x1 - *cpx2;
	cy = 2*y1 - *cpy2;

	// Convert to cubix bezier
	cx1 = x1 + 2.0f/3.0f*(cx - x1);
	cy1 = y1 + 2.0f/3.0f*(cy - y1);
	cx2 = x2 + 2.0f/3.0f*(cx - x2);
	cy2 = y2 + 2.0f/3.0f*(cy - y2);

	curve->cubicBezTo(cx1,cy1, cx2,cy2, x2,y2);

	*cpx2 = cx;
	*cpy2 = cy;
	*cpx = x2;
	*cpy = y2;
}

static void pathArcTo(OE_pointcurve* curve, float* cpx, float* cpy, float* args, int rel)
{
	// Ported from canvg (https://code.google.com/p/canvg/)
	float rx, ry, rotx;
	float x1, y1, x2, y2, cx, cy, dx, dy, d;
	float x1p, y1p, cxp, cyp, s, sa, sb;
	float ux, uy, vx, vy, a1, da;
	float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6];
	float sinrx, cosrx;
	int fa, fs;
	int i, ndivs;
	float hda, kappa;

	rx = fabsf(args[0]);                // y radius
	ry = fabsf(args[1]);                // x radius
	rotx = args[2] / 180.0f * M_PIf32;  // x rotation engle
	fa = fabsf(args[3]) > 1e-6f ? 1 : 0;// Large arc
	fs = fabsf(args[4]) > 1e-6f ? 1 : 0;// Sweep direction
	x1 = *cpx;                          // start point
	y1 = *cpy;
	if (rel)                            // end point
	{
		x2 = *cpx + args[5];
		y2 = *cpy + args[6];
	}
	else
	{
		x2 = args[5];
		y2 = args[6];
	}

	dx = x1 - x2;
	dy = y1 - y2;
	d = sqrtf(dx*dx + dy*dy);
	if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f)
	{
		// The arc degenerates to a line
		curve->lineTo(x2, y2);
		*cpx = x2;
		*cpy = y2;
		return;
	}

	sinrx = sinf(rotx);
	cosrx = cosf(rotx);

	// Convert to center point parameterization.
	// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
	// 1) Compute x1', y1'
	x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f;
	y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f;
	d = sqr(x1p)/sqr(rx) + sqr(y1p)/sqr(ry);
	if (d > 1)
	{
		d = sqrtf(d);
		rx *= d;
		ry *= d;
	}
	// 2) Compute cx', cy'
	s = 0.0f;
	sa = sqr(rx)*sqr(ry) - sqr(rx)*sqr(y1p) - sqr(ry)*sqr(x1p);
	sb = sqr(rx)*sqr(y1p) + sqr(ry)*sqr(x1p);
	if (sa < 0.0f)
	{
		sa = 0.0f;
	}
	if (sb > 0.0f)
	{
		s = sqrtf(sa / sb);
	}
	if (fa == fs)
	{
		s = -s;
	}
	cxp = s * rx * y1p / ry;
	cyp = s * -ry * x1p / rx;

	// 3) Compute cx,cy from cx',cy'
	cx = (x1 + x2)/2.0f + cosrx*cxp - sinrx*cyp;
	cy = (y1 + y2)/2.0f + sinrx*cxp + cosrx*cyp;

	// 4) Calculate theta1, and delta theta.
	ux = (x1p - cxp) / rx;
	uy = (y1p - cyp) / ry;
	vx = (-x1p - cxp) / rx;
	vy = (-y1p - cyp) / ry;
	a1 = vecang(1.0f,0.0f, ux,uy); // Initial angle
	da = vecang(ux,uy, vx,vy);     // Delta angle

//	if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI;
//	if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0;

	if (fa)
	{
		// Choose large arc
		if (da > 0.0f)
		{
			da = da - 2*M_PIf32;
		}
		else
		{
			da = 2*M_PIf32 + da;
		}
	}

	// Approximate the arc using cubic spline segments.
	t[0] = cosrx; t[1] = sinrx;
	t[2] = -sinrx; t[3] = cosrx;
	t[4] = cx; t[5] = cy;

	// Split arc into max 90 degree segments.
	// The loop assumes an iteration per end point (including start and end), this +1.
	ndivs = (int)(fabsf(da) / (M_PIf32*0.5f) + 1.0f);
	hda = (da / (float)ndivs) / 2.0f;
	kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda));
	if (da < 0.0f)
	{
		kappa = -kappa;
	}

	for (i = 0; i <= ndivs; i++)
	{
		a = a1 + da * (i/(float)ndivs);
		dx = cosf(a);
		dy = sinf(a);
		xformPoint(&x, &y, dx*rx, dy*ry, t); // position
		xformVec(&tanx, &tany, -dy*rx * kappa, dx*ry * kappa, t); // tangent
		if (i > 0)
		{
			curve->cubicBezTo(px+ptanx,py+ptany, x-tanx, y-tany, x, y);
		}
		px = x;
		py = y;
		ptanx = tanx;
		ptany = tany;
	}

	*cpx = x2;
	*cpy = y2;
}

static const char* getNextPathItem(const char* s, char* it)
{
	it[0] = '\0';
	// Skip white spaces and commas
	while (*s && (isspace(*s) || *s == ','))
	{
		s++;
	}
	if (!*s)
	{
		return s;
	}
	if (*s == '-' || *s == '+' || *s == '.' || isdigit(*s))
	{
		s = parseNumber(s, it, 64);
	}
	else
	{
		// Parse command
		it[0] = *s++;
		it[1] = '\0';
		return s;
	}

	return s;
}

static int getArgsPerElement(char cmd)
{
	switch (cmd)
	{
		case 'v':
		case 'V':
		case 'h':
		case 'H':
			return 1;
		case 'm':
		case 'M':
		case 'l':
		case 'L':
		case 't':
		case 'T':
			return 2;
		case 'q':
		case 'Q':
		case 's':
		case 'S':
			return 4;
		case 'c':
		case 'C':
			return 6;
		case 'a':
		case 'A':
			return 7;
	}
	return 0;
}

static bool parseSvg(SVGContext& context, TiXmlElement *input)
{
	TiXmlElement* element = nullptr;
	if (input)
	{
		for( element = input->FirstChildElement(); element; element = element->NextSiblingElement() )
		{
			const char* val = element->Value();

			if((strlen(val)==1)&&(strncmp(val,"g",1) == 0))
			{
				const char* transform = element->Attribute("transform");
				OE_Matrix newTransform;
				OE_Matrix oldTransform(context.matrix);
				if (transform)
				{
					char snb[64];
					float f[6] = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0};
					if (!strncmp(transform, "matrix", 6))
					{
						const char* matrix = transform+strlen("matrix(");
						for (int i=0; i<6; i++)
						{
							snb[0] = 0;
							matrix = parseNumber(matrix, snb, sizeof(snb))+1;
							f[i] = QString(snb).toFloat();
						}
						newTransform = OE_Matrix(f);
					}
					else if (!strncmp(transform, "translate", 9))
					{
						const char* translate = transform+strlen("translate(");
						for (int i=4; i<6; i++)
						{
							snb[0] = 0;
							translate = parseNumber(translate, snb, sizeof(snb))+1;
							f[i] = QString(snb).toFloat();
						}
					}
					else if (!strncmp(transform, "scale", 5))
					{
						const char* scale = transform+strlen("scale(");
						for (int i=0; i<6; i+=3)
						{
							snb[0] = 0;
							scale = parseNumber(scale, snb, sizeof(snb))+1;
							f[i] = QString(snb).toFloat();
						}
					}
					else if (!strncmp(transform, "rotate", 6))
					{
						// https://developer.mozilla.org/en/docs/Web/SVG/Attribute/transform
						printf("Unmanaged transform \"%s\"\n", transform);
					}
					else if (!strncmp(transform, "skewX", 5))
					{
						printf("Unmanaged transform \"%s\"\n", transform);
					}
					else if (!strncmp(transform, "skewX", 5))
					{
						printf("Unmanaged transform \"%s\"\n", transform);
					}
					else
					{
						printf("Unknown transform \"%s\"\n", transform);
					}
				}
				context.matrix = context.matrix * newTransform;
				parseSvg(context, element);
				context.matrix = oldTransform;
			}

			if((strlen(val)==4)&&(strncmp(val,"path",4) == 0))
			{
					parsePath(context, element);
			}
		}
	}
	return true;
}

static bool parsePath(SVGContext& context, TiXmlElement *input)
{
	if (input)
	{
		OE_pointcurve* curve;

		const char * s = input->Attribute("d");
		char cmd = '\0';
		float args[10];
		int nargs;
		int rargs = 0; /*nb arg after a command*/
		float cpx, cpy, cpx2, cpy2;
		char item[64];
    /*
        //const char** attr;

        //const char* s = NULL;
       // const char* tmp[4];
        for (i = 0; attr[i]; i += 2) {
            if (strcmp(attr[i], "d") == 0) {
                s = attr[i + 1];
            } else {
                tmp[0] = attr[i];
                tmp[1] = attr[i + 1];
                tmp[2] = 0;
                tmp[3] = 0;
                nsvg__parseAttribs(p, tmp);
            }
        }*/

		if (s)
		{
			curve = new OE_pointcurve();
			if (curve)
			{
				cpx = 0; cpy = 0;
				nargs = 0;

				while (*s)
				{
					s = getNextPathItem(s, item);
					if (!*item) break;
					if (isnum(item[0]))
					{
						if (nargs < 10)
						{
							args[nargs++] = QString(item).toFloat();
						}
						if (nargs >= rargs)
						{
							switch (cmd)
							{
								case 'm':
								case 'M':
									pathMoveTo(curve, &cpx, &cpy, args, cmd == 'm' ? 1 : 0);
									// Moveto can be followed by multiple coordinate pairs,
									// which should be treated as linetos.
									cmd = (cmd == 'm') ? 'l' : 'L';
									rargs = getArgsPerElement(cmd);
									cpx2 = cpx; cpy2 = cpy;
									break;
								case 'l':
								case 'L':
									pathLineTo(curve, &cpx, &cpy, args, cmd == 'l' ? 1 : 0);
									cpx2 = cpx; cpy2 = cpy;
									break;
								case 'H':
								case 'h':
									pathHLineTo(curve, &cpx, &cpy, args, cmd == 'h' ? 1 : 0);
									cpx2 = cpx; cpy2 = cpy;
									break;
								case 'V':
								case 'v':
									pathVLineTo(curve, &cpx, &cpy, args, cmd == 'v' ? 1 : 0);
									cpx2 = cpx; cpy2 = cpy;
									break;
								case 'C':
								case 'c':
									pathCubicBezTo(curve, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0);
									break;
								case 'S':
								case 's':
									pathCubicBezShortTo(curve, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0);
									break;
								case 'Q':
								case 'q':
									pathQuadBezTo(curve, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0);
									break;
								case 'T':
								case 't':
									pathQuadBezShortTo(curve, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0);
									break;
								case 'A':
								case 'a':
									pathArcTo(curve, &cpx, &cpy, args, cmd == 'a' ? 1 : 0);
									cpx2 = cpx; cpy2 = cpy;
									break;
								default:
									if (nargs >= 2)
									{
										cpx = args[nargs-2];
										cpy = args[nargs-1];
										cpx2 = cpx; cpy2 = cpy;
									}
									break;
							}
							nargs = 0;
						}
					}
					else
					{
						cmd = item[0];
						rargs = getArgsPerElement(cmd); //give us the number of arguments

						if (cmd == 'M' || cmd == 'm')
						{
							// Add the curve to document.
							pushCurve(context, curve);
							// Start new curve.
							curve = new OE_pointcurve();

							nargs = 0;
						}
						else if (cmd == 'Z' || cmd == 'z')
						{
							//z mean the curve is closed so...
							curve->setClosed(true);
							if (curve->getNpts() > 3)
							{
								// Move current point to first point
								curve->getPoint(1,&cpx,&cpy);
								cpx2 = cpx; cpy2 = cpy;

								// Add the curve to document.
								pushCurve(context, curve);
							// Start new curve.
							curve = new OE_pointcurve();
							}
							nargs = 0;
						}
					}
				}
				pushCurve(context, curve);
				return true;
			}
		}
	}
	return false;
}

static bool pushCurve(SVGContext& context, OE_pointcurve * curve)
{
	if (curve->check())
	{
		curve->transform(context.matrix);
		return context.document->addCurve(curve);

	}
	else
	{
		delete curve;
		curve = nullptr;
		return false;
	}
}
