#include "Painter.h"

namespace Upp {

#include "SvgInternal.h"

void BoundsPainter::New()
{
	boundingbox = Null;
	current = ccontrol = qcontrol = Pointf(0, 0);
}

// Note: There are no 'rel' operations with SVG; 'path' rels are handled by Path

void BoundsPainter::Bounds(Pointf p)
{
	boundingbox.Union(p);
}

void BoundsPainter::MoveOp(const Pointf& p, bool)
{
	sw.Move(p);
	qcontrol = ccontrol = current = p;
}

void BoundsPainter::LineOp(const Pointf& p, bool)
{
	sw.Line(p);
	Bounds(current);
	Bounds(p);
	ccontrol = qcontrol = p;
}

void QuadraticMinMax(double p1, double p2, double p3, double& l, double& h) {
    double a = p1 - 2 * p2 + p3;
    double b = p2 - p1;
    if(a) {
	    double tv = b/a;
	    if (tv > 0 && tv < 1) {
			tv = ((p1 - 2 * p2 + p3) * tv + 2 * (p2 - p1)) * tv + p1;
			if(tv < l)
				l = tv;
			if(tv > h)
				h = tv;
	    }
    }
}
    
void BoundsPainter::Quadratic(const Pointf& p1, const Pointf& p)
{
	Bounds(current);
	Bounds(p);
	if(!boundingbox.Contains(p1)) {
		QuadraticMinMax(current.x, p1.x, p.x, boundingbox.left, boundingbox.right);
		QuadraticMinMax(current.y, p1.y, p.y, boundingbox.top, boundingbox.bottom);
	}
	qcontrol = p1;
	current = p;
}

void BoundsPainter::QuadraticOp(const Pointf& p1, const Pointf& p, bool)
{
	sw.Quadratic(p1, p);
	Quadratic(p1, p);
}

void BoundsPainter::QuadraticOp(const Pointf& p, bool)
{
	QuadraticOp(2.0 * current - qcontrol, p, false);
}

int SolveQuadraticEquation(double a, double b, double c, double *r)
{
	if(a == 0) {
		if(b == 0)
			return 0;
  		r[0] = r[1] = -c / b;
  		return 1;
    }
	double d = b * b - 4 * a * c;
	if (d < 0)
		return 0;
	a *= 2;
	if (d == 0) {
		r[0] = r[1] = -b / a;
		return 1;
	}
	d = sqrt(d);
	if(b == 0) {
		r[0] = d / a;
		r[1] = -r[0];
    }
	else {
		r[0] = (-b + d) / a;
		r[1] = (-b - d) / a;
    }
	return 2;
}

void CubicMinMax(double p1, double p2, double p3, double p4, double& l, double& h) {
	double c0 = 3 * (p2 - p1);
	double c1 = 6 * (p3 - p2);
	double c2 = 3 * (p4 - p3);
	double r[2];
	int n = SolveQuadraticEquation(c2 - c1 + c0, c1 - 2 * c0, c0, r);
	for(int i = 0; i < n; i++) {
		double tv = r[i];
		if(tv > 0 && tv < 1) {
			tv =     ((1 - tv) * (1 - tv) * (1 - tv) * p1 +
			     3 * tv * (1 - tv) * (1 - tv) * p2 +
			     3 * tv * tv * (1 - tv) * p3 +
			         tv * tv * tv * p4);
			if(tv < l)
				l = tv;
			if(tv > h)
				h = tv;
		}
	}
}

void BoundsPainter::Cubic(const Pointf& p1, const Pointf& p2, const Pointf& p)
{
	Bounds(current);
	Bounds(p);
	if(!boundingbox.Contains(p1) || !boundingbox.Contains(p2)) {
		CubicMinMax(current.x, p1.x, p2.x, p.x, boundingbox.left, boundingbox.right);
		CubicMinMax(current.y, p1.y, p2.y, p.y, boundingbox.top, boundingbox.bottom);
	}
	ccontrol = p2;
	current = p;
}

void BoundsPainter::CubicOp(const Pointf& p1, const Pointf& p2, const Pointf& p, bool)
{
	sw.Cubic(p1, p2, p);
	Cubic(p1, p2, p);
}

void BoundsPainter::CubicOp(const Pointf& p2, const Pointf& p, bool)
{
	CubicOp(2.0 * current - ccontrol, p2, p, false);
}

void BoundsPainter::ArcOp(const Pointf& c, const Pointf& r, double angle, double sweep, bool)
{
	DoArc(c, r, angle, sweep, 0);
}

void BoundsPainter::SvgArcOp(const Pointf& r, double xangle, bool large, bool sweep, const Pointf& p, bool)
{
	DoSvgArc(r, xangle, large, sweep, p, current);
}

void BoundsPainter::CloseOp()
{
	sw.Close();
}

void BoundsPainter::DivOp()
{
	sw.Div();
}

void BoundsPainter::TextOp(const Pointf& p, const wchar *text, Font fnt, int n, const double *dx)
{
	Painter::TextOp(p, text, fnt, n, dx);
}

void BoundsPainter::CharacterOp(const Pointf& p, int ch, Font fnt)
{
	PaintCharacter(*this, p, ch, fnt);
}

void BoundsPainter::Finish(double width)
{
	if(compute_svg_boundingbox && !IsNull(boundingbox)) {
		Rectf h = boundingbox.Inflated(width / 2);
		Pointf a = mtx.Top().Transform(h.TopLeft());
		Pointf b = mtx.Top().Transform(h.BottomRight());		
		svg_boundingbox.Union(a);
		svg_boundingbox.Union(b);
	}
}

void BoundsPainter::TransformOp(const Xform2D& m)
{
	mtx.Top() = m * mtx.Top();
	sw.Transform(m);
}

void BoundsPainter::BeginOp()
{
	mtx.Add();
	mtx.Top() = mtx[mtx.GetCount() - 2];
	boundingbox = Null;
	sw.Begin();
}

void BoundsPainter::EndOp()
{
	mtx.Drop();
	sw.End();
}

}
