// 7 september 2015
#include "uipriv_windows.h"

static ID2D1Factory *d2dfactory = NULL;

HRESULT initDraw(void)
{
	D2D1_FACTORY_OPTIONS opts;

	ZeroMemory(&opts, sizeof (D2D1_FACTORY_OPTIONS));
	// TODO make this an option
	opts.debugLevel = D2D1_DEBUG_LEVEL_NONE;
	return D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
		&IID_ID2D1Factory,
		&opts,
		(void **) (&d2dfactory));
}

void uninitDraw(void)
{
	ID2D1Factory_Release(d2dfactory);
}

ID2D1HwndRenderTarget *makeHWNDRenderTarget(HWND hwnd)
{
	D2D1_RENDER_TARGET_PROPERTIES props;
	D2D1_HWND_RENDER_TARGET_PROPERTIES hprops;
	HDC dc;
	RECT r;
	ID2D1HwndRenderTarget *rt;
	HRESULT hr;

	// we need a DC for the DPI
	// we *could* just use the screen DPI but why when we have a window handle and its DC has a DPI
	dc = GetDC(hwnd);
	if (dc == NULL)
		logLastError("error getting DC to find DPI in makeHWNDRenderTarget()");

	ZeroMemory(&props, sizeof (D2D1_RENDER_TARGET_PROPERTIES));
	props.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
	props.pixelFormat.format = DXGI_FORMAT_UNKNOWN;
	props.pixelFormat.alphaMode = D2D1_ALPHA_MODE_UNKNOWN;
	props.dpiX = GetDeviceCaps(dc, LOGPIXELSX);
	props.dpiY = GetDeviceCaps(dc, LOGPIXELSY);
	props.usage = D2D1_RENDER_TARGET_USAGE_NONE;
	props.minLevel = D2D1_FEATURE_LEVEL_DEFAULT;

	if (ReleaseDC(hwnd, dc) == 0)
		logLastError("error releasing DC for finding DPI in makeHWNDRenderTarget()");

	if (GetClientRect(hwnd, &r) == 0)
		logLastError("error getting current size of window in makeHWNDRenderTarget()");

	ZeroMemory(&hprops, sizeof (D2D1_HWND_RENDER_TARGET_PROPERTIES));
	hprops.hwnd = hwnd;
	hprops.pixelSize.width = r.right - r.left;
	hprops.pixelSize.height = r.bottom - r.top;
	hprops.presentOptions = D2D1_PRESENT_OPTIONS_NONE;

	hr = ID2D1Factory_CreateHwndRenderTarget(d2dfactory,
		&props,
		&hprops,
		&rt);
	if (hr != S_OK)
		logHRESULT("error creating area HWND render target in makeHWNDRenderTarget()", hr);
	return rt;
}

struct uiDrawPath {
	ID2D1PathGeometry *path;
	ID2D1GeometrySink *sink;
	BOOL inFigure;
};

uiDrawPath *uiDrawNewPath(uiDrawFillMode fillmode)
{
	uiDrawPath *p;
	HRESULT hr;

	p = uiNew(uiDrawPath);
	hr = ID2D1Factory_CreatePathGeometry(d2dfactory,
		&(p->path));
	if (hr != S_OK)
		logHRESULT("error creating path in uiDrawNewPath()", hr);
	hr = ID2D1PathGeometry_Open(p->path,
		&(p->sink));
	if (hr != S_OK)
		logHRESULT("error opening path in uiDrawNewPath()", hr);
	switch (fillmode) {
	case uiDrawFillModeWinding:
		ID2D1GeometrySink_SetFillMode(p->sink,
			D2D1_FILL_MODE_WINDING);
		break;
	case uiDrawFillModeAlternate:
		ID2D1GeometrySink_SetFillMode(p->sink,
			D2D1_FILL_MODE_ALTERNATE);
		break;
	}
	return p;
}

void uiDrawFreePath(uiDrawPath *p)
{
	if (p->inFigure)
		ID2D1GeometrySink_EndFigure(p->sink,
			D2D1_FIGURE_END_OPEN);
	if (p->sink != NULL)
		// TODO close sink first?
		ID2D1GeometrySink_Release(p->sink);
	ID2D1PathGeometry_Release(p->path);
	uiFree(p);
}

void uiDrawPathNewFigure(uiDrawPath *p, double x, double y)
{
	D2D1_POINT_2F pt;

	if (p->inFigure)
		ID2D1GeometrySink_EndFigure(p->sink,
			D2D1_FIGURE_END_OPEN);
	pt.x = x;
	pt.y = y;
	ID2D1GeometrySink_BeginFigure(p->sink,
		pt,
		D2D1_FIGURE_BEGIN_FILLED);
	p->inFigure = TRUE;
}

// Direct2D arcs require a little explanation.
// An arc in Direct2D is defined by the chord between the endpoints.
// There are four possible arcs with the same two endpoints that you can draw this way.
// See https://www.youtube.com/watch?v=ATS0ANW1UxQ for a demonstration.
// There is a property rotationAngle which deals with the rotation /of the entire ellipse that forms an ellpitical arc/ - it's effectively a transformation on the arc.
// That is to say, it's NOT THE SWEEP.
// The sweep is defined by the start and end points and whether the arc is "large".
// As a result, this design does not allow for full circles or ellipses with a single arc; they have to be simulated with two.

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;
	D2D1_ARC_SEGMENT as;
	BOOL fullCircle;
	double absSweep;

	// 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
	// unfortunately D2D1SinCos() is only defined on Windows 8 and newer
	// the MSDN page doesn't say this, but says it requires d2d1_1.h, which is listed as only supported on Windows 8 and newer elsewhere on MSDN
	// so we must use sin() and cos() and hope it's right...
	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
	as.point.x = endX;
	as.point.y = endY;
	as.size.width = a->radius;
	as.size.height = a->radius;
	as.rotationAngle = 0;		// as above, not relevant for circles
	if (a->negative)
		as.sweepDirection = D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE;
	else
		as.sweepDirection = D2D1_SWEEP_DIRECTION_CLOCKWISE;
	// TODO explain the outer if
	if (!a->negative)
		if (a->sweep > M_PI)
			as.arcSize = D2D1_ARC_SIZE_LARGE;
		else
			as.arcSize = D2D1_ARC_SIZE_SMALL;
	else
		// TODO especially this part
		if (a->sweep > M_PI)
			as.arcSize = D2D1_ARC_SIZE_SMALL;
		else
			as.arcSize = D2D1_ARC_SIZE_LARGE;
	ID2D1GeometrySink_AddArc(p->sink, &as);
}

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)
{
	D2D1_POINT_2F pt;

	pt.x = x;
	pt.y = y;
	ID2D1GeometrySink_AddLine(p->sink, pt);
}

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)
{
	D2D1_BEZIER_SEGMENT s;

	s.point1.x = c1x;
	s.point1.y = c1y;
	s.point2.x = c2x;
	s.point2.y = c2y;
	s.point3.x = endX;
	s.point3.y = endY;
	ID2D1GeometrySink_AddBezier(p->sink, &s);
}

void uiDrawPathCloseFigure(uiDrawPath *p)
{
	ID2D1GeometrySink_EndFigure(p->sink,
		D2D1_FIGURE_END_CLOSED);
	p->inFigure = FALSE;
}

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)
{
	HRESULT hr;

	if (p->inFigure) {
		ID2D1GeometrySink_EndFigure(p->sink,
			D2D1_FIGURE_END_OPEN);
		// needed for uiDrawFreePath()
		p->inFigure = FALSE;
	}
	hr = ID2D1GeometrySink_Close(p->sink);
	if (hr != S_OK)
		logHRESULT("error closing path in uiDrawPathEnd()", hr);
	ID2D1GeometrySink_Release(p->sink);
	p->sink = NULL;
}

struct uiDrawContext {
	ID2D1RenderTarget *rt;
	UT_array *states;
	ID2D1PathGeometry *currentClip;
};

// declared below
static void initStates(uiDrawContext *);
static void uninitStates(uiDrawContext *);

static void resetTarget(ID2D1RenderTarget *rt)
{
	D2D1_MATRIX_3X2_F dm;

	// transformations persist
	// reset to the identity matrix
	ZeroMemory(&dm, sizeof (D2D1_MATRIX_3X2_F));
	dm._11 = 1;
	dm._22 = 1;
	ID2D1RenderTarget_SetTransform(rt, &dm);
}

uiDrawContext *newContext(ID2D1RenderTarget *rt)
{
	uiDrawContext *c;

	c = uiNew(uiDrawContext);
	c->rt = rt;
	initStates(c);
	resetTarget(c->rt);
	return c;
}

void freeContext(uiDrawContext *c)
{
	if (c->currentClip != NULL)
		ID2D1PathGeometry_Release(c->currentClip);
	uninitStates(c);
	uiFree(c);
}

static ID2D1Brush *makeSolidBrush(uiDrawBrush *b, ID2D1RenderTarget *rt, D2D1_BRUSH_PROPERTIES *props)
{
	D2D1_COLOR_F color;
	ID2D1SolidColorBrush *brush;
	HRESULT hr;

	color.r = b->R;
	color.g = b->G;
	color.b = b->B;
	color.a = b->A;

	hr = ID2D1RenderTarget_CreateSolidColorBrush(rt,
		&color,
		props,
		&brush);
	if (hr != S_OK)
		logHRESULT("error creating solid brush in makeSolidBrush()", hr);
	return (ID2D1Brush *) brush;
}

static ID2D1GradientStopCollection *mkstops(uiDrawBrush *b, ID2D1RenderTarget *rt)
{
	ID2D1GradientStopCollection *s;
	D2D1_GRADIENT_STOP *stops;
	size_t i;
	HRESULT hr;

	stops = uiAlloc(b->NumStops * sizeof (D2D1_GRADIENT_STOP), "D2D1_GRADIENT_STOP[]");
	for (i = 0; i < b->NumStops; i++) {
		stops[i].position = b->Stops[i].Pos;
		stops[i].color.r = b->Stops[i].R;
		stops[i].color.g = b->Stops[i].G;
		stops[i].color.b = b->Stops[i].B;
		stops[i].color.a = b->Stops[i].A;
	}

	// TODO BUG IN MINGW
	// the Microsoft headers give this all 6 parameters
	// the MinGW headers use the 4-parameter version
	hr = (*(rt->lpVtbl->CreateGradientStopCollection))(rt,
		stops,
		b->NumStops,
		D2D1_GAMMA_2_2,			// this is the default for the C++-only overload of ID2D1RenderTarget::GradientStopCollection()
		D2D1_EXTEND_MODE_CLAMP,
		&s);
	if (hr != S_OK)
		logHRESULT("error creating stop collection in mkstops()", hr);

	uiFree(stops);
	return s;
}

static ID2D1Brush *makeLinearBrush(uiDrawBrush *b, ID2D1RenderTarget *rt, D2D1_BRUSH_PROPERTIES *props)
{
	ID2D1LinearGradientBrush *brush;
	D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES gprops;
	ID2D1GradientStopCollection *stops;
	HRESULT hr;

	ZeroMemory(&gprops, sizeof (D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES));
	gprops.startPoint.x = b->X0;
	gprops.startPoint.y = b->Y0;
	gprops.endPoint.x = b->X1;
	gprops.endPoint.y = b->Y1;

	stops = mkstops(b, rt);

	hr = ID2D1RenderTarget_CreateLinearGradientBrush(rt,
		&gprops,
		props,
		stops,
		&brush);
	if (hr != S_OK)
		logHRESULT("error creating gradient brush in makeLinearBrush()", hr);

	// the example at https://msdn.microsoft.com/en-us/library/windows/desktop/dd756682%28v=vs.85%29.aspx says this is safe to do now
	ID2D1GradientStopCollection_Release(stops);
	return (ID2D1Brush *) brush;
}

static ID2D1Brush *makeRadialBrush(uiDrawBrush *b, ID2D1RenderTarget *rt, D2D1_BRUSH_PROPERTIES *props)
{
	ID2D1RadialGradientBrush *brush;
	D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES gprops;
	ID2D1GradientStopCollection *stops;
	HRESULT hr;

	ZeroMemory(&gprops, sizeof (D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES));
	gprops.gradientOriginOffset.x = b->X0 - b->X1;
	gprops.gradientOriginOffset.y = b->Y0 - b->Y1;
	gprops.center.x = b->X1;
	gprops.center.y = b->Y1;
	gprops.radiusX = b->OuterRadius;
	gprops.radiusY = b->OuterRadius;

	stops = mkstops(b, rt);

	hr = ID2D1RenderTarget_CreateRadialGradientBrush(rt,
		&gprops,
		props,
		stops,
		&brush);
	if (hr != S_OK)
		logHRESULT("error creating gradient brush in makeRadialBrush()", hr);

	ID2D1GradientStopCollection_Release(stops);
	return (ID2D1Brush *) brush;
}

static ID2D1Brush *makeBrush(uiDrawBrush *b, ID2D1RenderTarget *rt)
{
	D2D1_BRUSH_PROPERTIES props;

	ZeroMemory(&props, sizeof (D2D1_BRUSH_PROPERTIES));
	props.opacity = 1.0;
	// identity matrix
	props.transform._11 = 1;
	props.transform._22 = 1;

	switch (b->Type) {
	case uiDrawBrushTypeSolid:
		return makeSolidBrush(b, rt, &props);
	case uiDrawBrushTypeLinearGradient:
		return makeLinearBrush(b, rt, &props);
	case uiDrawBrushTypeRadialGradient:
		return makeRadialBrush(b, rt, &props);
//	case uiDrawBrushTypeImage:
//		TODO
	}

	complain("invalid brush type %d in makeBrush()", b->Type);
	return NULL;		// make compiler happy
}

// how clipping works:
// every fill and stroke is done on a temporary layer with the clip geometry applied to it
// this is really the only way to clip in Direct2D that doesn't involve opacity images
// reference counting:
// - initially the clip is NULL, which means do not use a layer
// - the first time uiDrawClip() is called, we take a reference on the path passed in (this is also why uiPathEnd() is needed)
// - every successive time, we create a new PathGeometry and merge the current clip with the new path, releasing the reference we took earlier and taking a reference to the new one
// - in Save, we take another reference; in Restore we drop the refernece to the existing path geometry and transfer that saved ref to the new path geometry over to the context
// uiDrawFreePath() doesn't destroy the path geometry, it just drops the reference count, so a clip can exist independent of its path

static ID2D1Layer *applyClip(uiDrawContext *c)
{
	ID2D1Layer *layer;
	D2D1_LAYER_PARAMETERS params;
	HRESULT hr;

	// if no clip, don't do anything
	if (c->currentClip == NULL)
		return NULL;

	// create a layer for clipping
	// we have to explicitly make the layer because we're still targeting Windows 7
	// TODO MINGW BUG
	// this macro is supposed to take three parameters
	hr = (*(c->rt->lpVtbl->CreateLayer))(c->rt,
		NULL,
		&layer);
	if (hr != S_OK)
		logHRESULT("error creating clip layer in applyClip()", hr);

	// apply it as the clip
	ZeroMemory(&params, sizeof (D2D1_LAYER_PARAMETERS));
	// this is the equivalent of InfiniteRect() in d2d1helper.h
	params.contentBounds.left = -FLT_MAX;
	params.contentBounds.top = -FLT_MAX;
	params.contentBounds.right = FLT_MAX;
	params.contentBounds.bottom = FLT_MAX;
	params.geometricMask = (ID2D1Geometry *) (c->currentClip);
	// TODO is this correct?
	params.maskAntialiasMode = ID2D1RenderTarget_GetAntialiasMode(c->rt);
	// identity matrix
	params.maskTransform._11 = 1;
	params.maskTransform._22 = 1;
	params.opacity = 1.0;
	params.opacityBrush = NULL;
	params.layerOptions = D2D1_LAYER_OPTIONS_NONE;
	// TODO is this correct?
	if (ID2D1RenderTarget_GetTextAntialiasMode(c->rt) == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE)
		params.layerOptions = D2D1_LAYER_OPTIONS_INITIALIZE_FOR_CLEARTYPE;
	ID2D1RenderTarget_PushLayer(c->rt,
		&params,
		layer);

	// return the layer so it can be freed later
	return layer;
}

static void unapplyClip(uiDrawContext *c, ID2D1Layer *layer)
{
	if (layer == NULL)
		return;
	ID2D1RenderTarget_PopLayer(c->rt);
	ID2D1Layer_Release(layer);
}

void uiDrawStroke(uiDrawContext *c, uiDrawPath *p, uiDrawBrush *b, uiDrawStrokeParams *sp)
{
	ID2D1Brush *brush;
	ID2D1StrokeStyle *style;
	D2D1_STROKE_STYLE_PROPERTIES dsp;
	FLOAT *dashes;
	size_t i;
	ID2D1Layer *cliplayer;
	HRESULT hr;

	brush = makeBrush(b, c->rt);

	ZeroMemory(&dsp, sizeof (D2D1_STROKE_STYLE_PROPERTIES));
	switch (sp->Cap) {
	case uiDrawLineCapFlat:
		dsp.startCap = D2D1_CAP_STYLE_FLAT;
		dsp.endCap = D2D1_CAP_STYLE_FLAT;
		dsp.dashCap = D2D1_CAP_STYLE_FLAT;
		break;
	case uiDrawLineCapRound:
		dsp.startCap = D2D1_CAP_STYLE_ROUND;
		dsp.endCap = D2D1_CAP_STYLE_ROUND;
		dsp.dashCap = D2D1_CAP_STYLE_ROUND;
		break;
	case uiDrawLineCapSquare:
		dsp.startCap = D2D1_CAP_STYLE_SQUARE;
		dsp.endCap = D2D1_CAP_STYLE_SQUARE;
		dsp.dashCap = D2D1_CAP_STYLE_SQUARE;
		break;
	}
	switch (sp->Join) {
	case uiDrawLineJoinMiter:
		dsp.lineJoin = D2D1_LINE_JOIN_MITER_OR_BEVEL;
		dsp.miterLimit = sp->MiterLimit;
		break;
	case uiDrawLineJoinRound:
		dsp.lineJoin = D2D1_LINE_JOIN_ROUND;
		break;
	case uiDrawLineJoinBevel:
		dsp.lineJoin = D2D1_LINE_JOIN_BEVEL;
		break;
	}
	dsp.dashStyle = D2D1_DASH_STYLE_SOLID;
	dashes = NULL;
	// note that dash widths and the dash phase are scaled up by the thickness by Direct2D
	// TODO be sure to formally document this
	if (sp->NumDashes != 0) {
		dsp.dashStyle = D2D1_DASH_STYLE_CUSTOM;
		dashes = (FLOAT *) uiAlloc(sp->NumDashes * sizeof (FLOAT), "FLOAT[]");
		for (i = 0; i < sp->NumDashes; i++)
			dashes[i] = sp->Dashes[i] / sp->Thickness;
	}
	dsp.dashOffset = sp->DashPhase / sp->Thickness;
	hr = ID2D1Factory_CreateStrokeStyle(d2dfactory,
		&dsp,
		dashes,
		sp->NumDashes,
		&style);
	if (hr != S_OK)
		logHRESULT("error creating stroke style in uiDrawStroke()", hr);
	if (sp->NumDashes != 0)
		uiFree(dashes);

	cliplayer = applyClip(c);
	ID2D1RenderTarget_DrawGeometry(c->rt,
		(ID2D1Geometry *) (p->path),
		brush,
		sp->Thickness,
		style);
	unapplyClip(c, cliplayer);

	ID2D1StrokeStyle_Release(style);
	ID2D1Brush_Release(brush);
}

void uiDrawFill(uiDrawContext *c, uiDrawPath *p, uiDrawBrush *b)
{
	ID2D1Brush *brush;
	ID2D1Layer *cliplayer;

	brush = makeBrush(b, c->rt);
	cliplayer = applyClip(c);
	ID2D1RenderTarget_FillGeometry(c->rt,
		(ID2D1Geometry *) (p->path),
		brush,
		NULL);
	unapplyClip(c, cliplayer);
	ID2D1Brush_Release(brush);
}

static void m2d(uiDrawMatrix *m, D2D1_MATRIX_3X2_F *d)
{
	d->_11 = m->M11;
	d->_12 = m->M12;
	d->_21 = m->M21;
	d->_22 = m->M22;
	d->_31 = m->M31;
	d->_32 = m->M32;
}

static void d2m(D2D1_MATRIX_3X2_F *d, uiDrawMatrix *m)
{
	m->M11 = d->_11;
	m->M12 = d->_12;
	m->M21 = d->_21;
	m->M22 = d->_22;
	m->M31 = d->_31;
	m->M32 = d->_32;
}

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

// frustratingly all of the operations on a matrix except rotation and skeweing are provided by the C++-only d2d1helper.h
// we'll have to recreate their functionalities here
// the implementations are all in the main matrix.c file

void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y)
{
	fallbackTranslate(m, x, y);
}

void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y)
{
	fallbackScale(m, xCenter, yCenter, x, y);
}

void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount)
{
	D2D1_POINT_2F center;
	D2D1_MATRIX_3X2_F dm;
	uiDrawMatrix rm;

	amount *= 180 / M_PI;		// must be in degrees
	center.x = x;
	center.y = y;
	D2D1MakeRotateMatrix(amount, center, &dm);
	d2m(&dm, &rm);
	uiDrawMatrixMultiply(m, &rm);
}

void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)
{
	D2D1_POINT_2F center;
	D2D1_MATRIX_3X2_F dm;
	uiDrawMatrix sm;

	xamount *= 180 / M_PI;		// must be in degrees
	yamount *= 180 / M_PI;		// must be in degrees
	center.x = x;
	center.y = y;
	D2D1MakeSkewMatrix(xamount, yamount, center, &dm);
	d2m(&dm, &sm);
	uiDrawMatrixMultiply(m, &sm);
}

void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src)
{
	fallbackMultiply(dest, src);
}

int uiDrawMatrixInvertible(uiDrawMatrix *m)
{
	D2D1_MATRIX_3X2_F d;

	m2d(m, &d);
	return D2D1IsMatrixInvertible(&d) != FALSE;
}

int uiDrawMatrixInvert(uiDrawMatrix *m)
{
	D2D1_MATRIX_3X2_F d;

	m2d(m, &d);
	if (D2D1InvertMatrix(&d) == FALSE)
		return 0;
	d2m(&d, m);
	return 1;
}

void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y)
{
	fallbackTransformPoint(m, x, y);
}

void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y)
{
	fallbackTransformSize(m, x, y);
}

void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m)
{
	D2D1_MATRIX_3X2_F dm;
	uiDrawMatrix already;
	uiDrawMatrix temp;

	ID2D1RenderTarget_GetTransform(c->rt, &dm);
	d2m(&dm, &already);
	temp = *m;		// don't modify m
	// you would think we have to do already * m, right?
	// WRONG! we have to do m * already
	// why? a few reasons
	// a) this lovely comment in cairo's source - http://cgit.freedesktop.org/cairo/tree/src/cairo-matrix.c?id=0537479bd1d4c5a3bc0f6f41dec4deb98481f34a#n330
	// 	Direct2D uses column vectors and I don't know if this is even documented
	// b) that's what Core Graphics does
	// TODO see if Microsoft says to do this
	uiDrawMatrixMultiply(&temp, &already);
	m2d(&temp, &dm);
	ID2D1RenderTarget_SetTransform(c->rt, &dm);
}

// TODO for this, stroke, and fill, make sure that an open path causes error state
void uiDrawClip(uiDrawContext *c, uiDrawPath *path)
{
	ID2D1PathGeometry *newPath;
	ID2D1GeometrySink *newSink;
	HRESULT hr;

	// if there's no current clip, borrow the path
	if (c->currentClip == NULL) {
		c->currentClip = path->path;
		// we have to take our own reference to that clip
		ID2D1PathGeometry_AddRef(c->currentClip);
		return;
	}

	// otherwise we have to intersect the current path with the new one
	// we do that into a new path, and then replace c->currentClip with that new path
	hr = ID2D1Factory_CreatePathGeometry(d2dfactory,
		&newPath);
	if (hr != S_OK)
		logHRESULT("error creating new path in uiDrawClip()", hr);
	hr = ID2D1PathGeometry_Open(newPath,
		&newSink);
	if (hr != S_OK)
		logHRESULT("error opening new path in uiDrawClip()", hr);
	// TODO BUG IN MINGW
	// the macro is supposed to take 6 parameters
	hr = (*(c->currentClip->lpVtbl->Base.CombineWithGeometry))(
		(ID2D1Geometry *) (c->currentClip),
		(ID2D1Geometry *) (path->path),
		D2D1_COMBINE_MODE_INTERSECT,
		NULL,
		// TODO is this correct or can this be set per target?
		D2D1_DEFAULT_FLATTENING_TOLERANCE,
		(ID2D1SimplifiedGeometrySink *) newSink);
	if (hr != S_OK)
		logHRESULT("error intersecting old path with new path in uiDrawClip()", hr);
	hr = ID2D1GeometrySink_Close(newSink);
	if (hr != S_OK)
		logHRESULT("error closing new path in uiDrawClip()", hr);
	ID2D1GeometrySink_Release(newSink);

	// okay we have the new clip; we just need to replace the old one with it
	ID2D1PathGeometry_Release(c->currentClip);
	c->currentClip = newPath;
	// we have a reference already; no need for another
}

struct state {
	ID2D1DrawingStateBlock *dsb;
	ID2D1PathGeometry *clip;
};

static const UT_icd stateicd = {
	.sz = sizeof (struct state),
	.init = NULL,
	.copy = NULL,
	.dtor = NULL,
};

static void initStates(uiDrawContext *c)
{
	utarray_new(c->states, &stateicd);
}

static void uninitStates(uiDrawContext *c)
{
	if (utarray_len(c->states) > 0)
		complain("imbalanced save/restore count in uiDrawContext in uninitStates(); did you save without restoring?");
	utarray_free(c->states);
}

void uiDrawSave(uiDrawContext *c)
{
	struct state state;
	ID2D1DrawingStateBlock *dsb;
	HRESULT hr;

	hr = ID2D1Factory_CreateDrawingStateBlock(d2dfactory,
		// TODO verify that these are correct
		NULL,
		NULL,
		&dsb);
	if (hr != S_OK)
		logHRESULT("error creating drawing state block in uiDrawSave()", hr);
	ID2D1RenderTarget_SaveDrawingState(c->rt, dsb);

	// if we have a clip, we need to hold another reference to it
	if (c->currentClip != NULL)
		ID2D1PathGeometry_AddRef(c->currentClip);

	state.dsb = dsb;
	state.clip = c->currentClip;
	utarray_push_back(c->states, &state);
}

void uiDrawRestore(uiDrawContext *c)
{
	struct state *state;

	state = (struct state *) utarray_back(c->states);

	ID2D1RenderTarget_RestoreDrawingState(c->rt, state->dsb);
	ID2D1DrawingStateBlock_Release(state->dsb);

	// if we have a current clip, we need to drop it
	if (c->currentClip != NULL)
		ID2D1PathGeometry_Release(c->currentClip);
	// no need to explicitly addref or release; just transfer the ref
	c->currentClip = state->clip;

	utarray_pop_back(c->states);
}
