// 19 november 2015
#include <cmath>
#include "uipriv_haiku.hpp"
using namespace std;

struct uiDrawPath {
	BShape *shape;
	uiDrawFillMode fillMode;
	bool ended;
};

uiDrawPath *uiDrawNewPath(uiDrawFillMode fillMode)
{
	uiDrawPath *p;

	p = uiNew(uiDrawPath);
	p->shape = new BShape();
	p->fillMode = fillMode;
	return p;
}

void uiDrawFreePath(uiDrawPath *p)
{
	delete p->shape;
	uiFree(p);
}

// TODO add ended checks
// TODO add error checks

void uiDrawPathNewFigure(uiDrawPath *p, double x, double y)
{
	p->shape->MoveTo(BPoint(x, y));
}

// Even though BView::FillArc()/StrokeArc() existed and used the sane arc drawing model, Haiku decided to use the Direct2D arc drawing model when it added BShape::ArcTo().
// So refer to windows/draw.c for details.
// This is slightly better as it uses center points and sweep amounts instead of endpoints, but *still*.
// TODO split into common/d2darc.c

struct arc {
	double xCenter;
	double yCenter;
	double radius;
	double startAngle;
	double sweep;
	int negative;
};

// this is used for the comparison below
// if it falls apart it can be changed later
#define aerMax 6 * DBL_EPSILON

static void drawArc(uiDrawPath *p, struct arc *a, void (*startFunction)(uiDrawPath *, double, double))
{
	double sinx, cosx;
	double startX, startY;
	double endX, endY;
	bool largeArc;
	bool counterclockwise;
	bool fullCircle;
	double absSweep;

	// TODO is this relevaith Haiku?
	// as above, we can't do a full circle with one arc
	// simulate it with two half-circles
	// of course, we have a dragon: equality on floating-point values!
	// I've chosen to do the AlmostEqualRelative() technique in https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
	fullCircle = false;
	// use the absolute value to tackle both ≥2π and ≤-2π at the same time
	absSweep = fabs(a->sweep);
	if (absSweep > (2 * M_PI))		// this part is easy
		fullCircle = true;
	else {
		double aerDiff;

		aerDiff = fabs(absSweep - (2 * M_PI));
		// if we got here then we know a->sweep is larger (or the same!)
		fullCircle = aerDiff <= absSweep * aerMax;
	}
	// TODO make sure this works right for the negative direction
	if (fullCircle) {
		a->sweep = M_PI;
		drawArc(p, a, startFunction);
		a->startAngle += M_PI;
		drawArc(p, a, NULL);
		return;
	}

	// first, figure out the arc's endpoints
	sinx = sin(a->startAngle);
	cosx = cos(a->startAngle);
	startX = a->xCenter + a->radius * cosx;
	startY = a->yCenter + a->radius * sinx;
	sinx = sin(a->startAngle + a->sweep);
	cosx = cos(a->startAngle + a->sweep);
	endX = a->xCenter + a->radius * cosx;
	endY = a->yCenter + a->radius * sinx;

	// now do the initial step to get the current point to be the start point
	// this is either creating a new figure, drawing a line, or (in the case of our full circle code above) doing nothing
	if (startFunction != NULL)
		(*startFunction)(p, startX, startY);

	// now we can draw the arc
	if (a->negative)
		counterclockwise = true;
	else
		counterclockwise = false;
	// TODO explain the outer if
	if (!a->negative)
		if (a->sweep > M_PI)
			largeArc = true;
		else
			largeArc = false;
	else
		// TODO especially this part
		if (a->sweep > M_PI)
			largeArc = false;
		else
			largeArc = true;
	p->shape->ArcTo(a->radius, a->radius,
		// TODO should this be sweep amount?
		a->startAngle,		// TODO convert to degrees
		largeArc, counterclockwise,
		BPoint(a->xCenter, a->yCenter));
}

void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
{
	struct arc a;

	a.xCenter = xCenter;
	a.yCenter = yCenter;
	a.radius = radius;
	a.startAngle = startAngle;
	a.sweep = sweep;
	a.negative = negative;
	drawArc(p, &a, uiDrawPathNewFigure);
}

void uiDrawPathLineTo(uiDrawPath *p, double x, double y)
{
	p->shape->LineTo(BPoint(x, y));
}

void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
{
	struct arc a;

	a.xCenter = xCenter;
	a.yCenter = yCenter;
	a.radius = radius;
	a.startAngle = startAngle;
	a.sweep = sweep;
	a.negative = negative;
	drawArc(p, &a, uiDrawPathLineTo);
}

void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY)
{
	p->shape->BezierTo(BPoint(c1x, c1y),
		BPoint(c2x, c2y),
		BPoint(endX, endY));
}

void uiDrawPathCloseFigure(uiDrawPath *p)
{
	p->shape->Close();
}

void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height)
{
	// this is the same algorithm used by cairo and Core Graphics, according to their documentations
	uiDrawPathNewFigure(p, x, y);
	uiDrawPathLineTo(p, x + width, y);
	uiDrawPathLineTo(p, x + width, y + height);
	uiDrawPathLineTo(p, x, y + height);
	uiDrawPathCloseFigure(p);
}

void uiDrawPathEnd(uiDrawPath *p)
{
	p->ended = true;
}

struct uiDrawContext {
	BView *view;
};

uiDrawContext *newContext(BView *view)
{
	uiDrawContext *c;

	c = uiNew(uiDrawContext);
	c->view = view;
	return c;
}

void freeContext(uiDrawContext *c)
{
	uiFree(c);
}

// TODO verify this
static void setHighColor(BView *view, uiDrawBrush *b)
{
	view->SetHighColor(b->R * 255,
		b->G * 255,
		b->B * 255,
		b->A * 255);
}

// TODO path ended checks; error checks

void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p)
{
	cap_mode cap;
	join_mode join;

	switch (p->Cap) {
	case uiDrawLineCapFlat:
		cap = B_BUTT_CAP;
		break;
	case uiDrawLineCapRound:
		cap = B_ROUND_CAP;
		break;
	case uiDrawLineCapSquare:
		cap = B_SQUARE_CAP;
		break;
	}
	switch (p->Join) {
	case uiDrawLineJoinMiter:
		join = B_MITER_JOIN;
		break;
	case uiDrawLineJoinRound:
		join = B_ROUND_JOIN;
		break;
	case uiDrawLineJoinBevel:
		join = B_BEVEL_JOIN;
		break;
	}
	c->view->SetLineMode(cap, join, p->MiterLimit);
	c->view->SetPenSize(p->Thickness);
	// TODO line dashes
	switch (b->Type) {
	case uiDrawBrushTypeSolid:
		setHighColor(c->view, b);
		c->view->StrokeShape(path->shape);
		break;
	case uiDrawBrushTypeLinearGradient:
		// TODO
	case uiDrawBrushTypeRadialGradient:
		// TODO
		break;
//	case uiDrawBrushTypeImage:
	}
}

void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b)
{
	// TODO not documented on api.haiku-os.org
	switch (path->fillMode) {
	case uiDrawFillModeWinding:
		c->view->SetFillRule(B_NONZERO);
		break;
	case uiDrawFillModeAlternate:
		c->view->SetFillRule(B_EVEN_ODD);
		break;
	}
	switch (b->Type) {
	case uiDrawBrushTypeSolid:
		setHighColor(c->view, b);
		c->view->FillShape(path->shape);
		break;
	case uiDrawBrushTypeLinearGradient:
		// TODO
	case uiDrawBrushTypeRadialGradient:
		// TODO
		break;
//	case uiDrawBrushTypeImage:
	}
}

// TODO none of this is documented on api.haiku-os.org

static void m2a(uiDrawMatrix *m, BAffineTransform *a)
{
	a->sx = m->M11;
	a->shy = m->M12;
	a->shx = m->M21;
	a->sy = m->M22;
	a->tx = m->M31;
	a->ty = m->M32;
}

static void a2m(BAffineTransform *a, uiDrawMatrix *m)
{
	m->M11 = a->sx;
	m->M12 = a->shy;
	m->M21 = a->shx;
	m->M22 = a->sy;
	m->M31 = a->tx;
	m->M32 = a->ty;
}

void uiDrawMatrixSetIdentity(uiDrawMatrix *m)
{
	setIdentity(m);
}

void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y)
{
	BAffineTransform a;

	m2a(m, &a);
	a.TranslateBy(x, y);
	a2m(&a, m);
}

void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y)
{
	BAffineTransform a;

	m2a(m, &a);
	a.ScaleBy(BPoint(xCenter, yCenter), BPoint(x, y));
	a2m(&a, m);
}

void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount)
{
	BAffineTransform a;

	m2a(m, &a);
	// TODO degrees or radians?
	a.RotateBy(BPoint(x, y), amount);
	a2m(&a, m);
}

void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)
{
	BAffineTransform a;

	m2a(m, &a);
	// TODO degrees or radians?
	a.ShearBy(BPoint(x, y), BPoint(xamount, yamount));
	a2m(&a, m);
}

void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src)
{
	BAffineTransform c;
	BAffineTransform d;

	m2a(dest, &c);
	m2a(src, &d);
	c.Multiply(d);
	a2m(&c, dest);
}

int uiDrawMatrixInvertible(uiDrawMatrix *m)
{
	// TODO
	return 0;
}

int uiDrawMatrixInvert(uiDrawMatrix *m)
{
	// TODO
	return 0;
}

void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y)
{
	// TODO
}

void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y)
{
	// TODO
}

void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m)
{
	BAffineTransform a;

	m2a(m, &a);
	// see windows/draw.c
	a.Multiply(c->view->Transform());
	c->view->SetTransform(a);
}

// TODO not documented on api.haiku-os.org
void uiDrawClip(uiDrawContext *c, uiDrawPath *path)
{
	c->view->ClipToShape(path->shape);
}

void uiDrawSave(uiDrawContext *c)
{
	c->view->PushState();
}

void uiDrawRestore(uiDrawContext *c)
{
	c->view->PopState();
}
