#include "fitz.h"
#include "mupdf.h"

/* this mess is jeong's */

#define BIGNUM 32000

#define NSEGS 32
#define MAX_RAD_SEGS 36

fz_error
pdf_loadtype1shade(fz_shade *shade, pdf_xref *xref, fz_obj *dict, fz_obj *ref)
{
	fz_error error;
	fz_obj *obj;
	fz_matrix matrix;
	pdf_function *func;

	int xx, yy;
	float x, y;
	float xn, yn;
	float x0, y0, x1, y1;
	int n;

	pdf_logshade("load type1 shade {\n");

	obj = fz_dictgets(dict, "Domain");
	if (obj) {
		x0 = fz_toreal(fz_arrayget(obj, 0));
		x1 = fz_toreal(fz_arrayget(obj, 1));
		y0 = fz_toreal(fz_arrayget(obj, 2));
		y1 = fz_toreal(fz_arrayget(obj, 3));
	}
	else {
		x0 = 0;
		x1 = 1.0;
		y0 = 0;
		y1 = 1.0;
	}

	pdf_logshade("domain %g %g %g %g\n", x0, x1, y0, y1);

	obj = fz_dictgets(dict, "Matrix");
	if (obj)
	{
		matrix = pdf_tomatrix(obj);
		pdf_logshade("matrix [%g %g %g %g %g %g]\n",
			matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f);
	}
	else
		matrix = fz_identity();

	obj = fz_dictgets(dict, "Function");
	if (!obj)
		return fz_throw("shading function not found");
	error = pdf_loadfunction(&func, xref, obj);
	if (error)
		return fz_rethrow(error, "could not load shading function");

	shade->usefunction = 0;

	shade->meshlen = NSEGS * NSEGS * 2;
	shade->mesh = fz_malloc(sizeof(float) * (2 + shade->cs->n) * 3 * shade->meshlen);
	if (!shade->mesh)
		return fz_rethrow(-1, "out of memory");

	n = 0;
	for (yy = 0; yy < NSEGS; ++yy)
	{
		y = y0 + (y1 - y0) * yy / (float)NSEGS;
		yn = y0 + (y1 - y0) * (yy + 1) / (float)NSEGS;
		for (xx = 0; xx < NSEGS; ++xx)
		{
			x = x0 + (x1 - x0) * (xx / (float)NSEGS);
			xn = x0 + (x1 - x0) * (xx + 1) / (float)NSEGS;

#define ADD_VERTEX(xx, yy) \
			{\
				fz_point p;\
				float cp[2], cv[FZ_MAXCOLORS];\
				int c;\
				p.x = xx;\
				p.y = yy;\
				p = fz_transformpoint(matrix, p);\
				shade->mesh[n++] = p.x;\
				shade->mesh[n++] = p.y;\
				\
				cp[0] = xx;\
				cp[1] = yy;\
				error = pdf_evalfunction(func, cp, 2, cv, shade->cs->n);\
				if (error) \
					return fz_rethrow(error, "unable to evaluate shading function"); \
				\
				for (c = 0; c < shade->cs->n; ++c) {\
					shade->mesh[n++] = cv[c];\
				}\
			}\

			ADD_VERTEX(x, y);
			ADD_VERTEX(xn, y);
			ADD_VERTEX(xn, yn);

			ADD_VERTEX(x, y);
			ADD_VERTEX(xn, yn);
			ADD_VERTEX(x, yn);
		}
	}

	pdf_logshade("}\n");

	return fz_okay;
}

fz_error
pdf_loadtype2shade(fz_shade *shade, pdf_xref *xref, fz_obj *dict, fz_obj *ref)
{
	fz_point p1, p2, p3, p4;
	fz_point ep1, ep2, ep3, ep4;
	float x0, y0, x1, y1;
	float t0, t1;
	int e0, e1;
	fz_obj *obj;
	float theta;
	float dist;
	int n;
	fz_error error;

	pdf_logshade("load type2 shade {\n");

	obj = fz_dictgets(dict, "Coords");
	x0 = fz_toreal(fz_arrayget(obj, 0));
	y0 = fz_toreal(fz_arrayget(obj, 1));
	x1 = fz_toreal(fz_arrayget(obj, 2));
	y1 = fz_toreal(fz_arrayget(obj, 3));

	pdf_logshade("coords %g %g %g %g\n", x0, y0, x1, y1);

	obj = fz_dictgets(dict, "Domain");
	if (obj) {
		t0 = fz_toreal(fz_arrayget(obj, 0));
		t1 = fz_toreal(fz_arrayget(obj, 1));
	} else {
		t0 = 0.0;
		t1 = 1.0;
	}

	obj = fz_dictgets(dict, "Extend");
	if (obj) {
		e0 = fz_tobool(fz_arrayget(obj, 0));
		e1 = fz_tobool(fz_arrayget(obj, 1));
	} else {
		e0 = 0;
		e1 = 0;
	}

	pdf_logshade("domain %g %g\n", t0, t1);
	pdf_logshade("extend %d %d\n", e0, e1);

	error = pdf_loadshadefunction(shade, xref, dict, t0, t1);
	if (error)
		return fz_rethrow(error, "unable to load shading function");

	theta = atan2(y1 - y0, x1 - x0);
	theta += M_PI / 2.0;

	pdf_logshade("theta=%g\n", theta);

	dist = hypot(x1 - x0, y1 - y0);

	/* if the axis has virtually length 0 (a point),
	   do not extend as there is nothing to extend beyond */
	if (dist < FLT_EPSILON)
	{
		e0 = 0;
		e1 = 0;
	}

	shade->meshlen = 2 + e0 * 2 + e1 * 2;
	shade->mesh = fz_malloc(sizeof(float) * 3*3 * shade->meshlen);
	if (!shade->mesh)
		return fz_rethrow(-1, "out of memory");

	p1.x = x0 + BIGNUM * cos(theta);
	p1.y = y0 + BIGNUM * sin(theta);
	p2.x = x1 + BIGNUM * cos(theta);
	p2.y = y1 + BIGNUM * sin(theta);
	p3.x = x0 - BIGNUM * cos(theta);
	p3.y = y0 - BIGNUM * sin(theta);
	p4.x = x1 - BIGNUM * cos(theta);
	p4.y = y1 - BIGNUM * sin(theta);

	pdf_logshade("p1 %g %g\n", p1.x, p1.y);
	pdf_logshade("p2 %g %g\n", p2.x, p2.y);
	pdf_logshade("p3 %g %g\n", p3.x, p3.y);
	pdf_logshade("p4 %g %g\n", p4.x, p4.y);

	n = 0;

	/* if the axis has virtually length 0 (a point), use the same axis
	   position t = 0 for all triangle vertices */
	if (dist < FLT_EPSILON)
	{
		pdf_setmeshvalue(shade->mesh, n++, p1.x, p1.y, 0);
		pdf_setmeshvalue(shade->mesh, n++, p2.x, p2.y, 0);
		pdf_setmeshvalue(shade->mesh, n++, p4.x, p4.y, 0);
		pdf_setmeshvalue(shade->mesh, n++, p1.x, p1.y, 0);
		pdf_setmeshvalue(shade->mesh, n++, p4.x, p4.y, 0);
		pdf_setmeshvalue(shade->mesh, n++, p3.x, p3.y, 0);
	}
	else
	{
		pdf_setmeshvalue(shade->mesh, n++, p1.x, p1.y, 0);
		pdf_setmeshvalue(shade->mesh, n++, p2.x, p2.y, 1);
		pdf_setmeshvalue(shade->mesh, n++, p4.x, p4.y, 1);
		pdf_setmeshvalue(shade->mesh, n++, p1.x, p1.y, 0);
		pdf_setmeshvalue(shade->mesh, n++, p4.x, p4.y, 1);
		pdf_setmeshvalue(shade->mesh, n++, p3.x, p3.y, 0);
	}

	if (e0)
	{
		ep1.x = p1.x - (x1 - x0) / dist * BIGNUM;
		ep1.y = p1.y - (y1 - y0) / dist * BIGNUM;
		ep3.x = p3.x - (x1 - x0) / dist * BIGNUM;
		ep3.y = p3.y - (y1 - y0) / dist * BIGNUM;

		pdf_setmeshvalue(shade->mesh, n++, ep1.x, ep1.y, 0);
		pdf_setmeshvalue(shade->mesh, n++, p1.x, p1.y, 0);
		pdf_setmeshvalue(shade->mesh, n++, p3.x, p3.y, 0);
		pdf_setmeshvalue(shade->mesh, n++, ep1.x, ep1.y, 0);
		pdf_setmeshvalue(shade->mesh, n++, p3.x, p3.y, 0);
		pdf_setmeshvalue(shade->mesh, n++, ep3.x, ep3.y, 0);
	}

	if (e1)
	{
		ep2.x = p2.x + (x1 - x0) / dist * BIGNUM;
		ep2.y = p2.y + (y1 - y0) / dist * BIGNUM;
		ep4.x = p4.x + (x1 - x0) / dist * BIGNUM;
		ep4.y = p4.y + (y1 - y0) / dist * BIGNUM;

		pdf_setmeshvalue(shade->mesh, n++, p2.x, p2.y, 1);
		pdf_setmeshvalue(shade->mesh, n++, ep2.x, ep2.y, 1);
		pdf_setmeshvalue(shade->mesh, n++, ep4.x, ep4.y, 1);
		pdf_setmeshvalue(shade->mesh, n++, p2.x, p2.y, 1);
		pdf_setmeshvalue(shade->mesh, n++, ep4.x, ep4.y, 1);
		pdf_setmeshvalue(shade->mesh, n++, p4.x, p4.y, 1);
	}

	pdf_logshade("}\n");

	return fz_okay;
}

static int
buildannulusmesh(float* mesh, int pos,
					float x0, float y0, float r0, float x1, float y1, float r1,
					float c0, float c1, int nomesh)
{
	int n = pos * 3;
	float dist = hypot(x1 - x0, y1 - y0);
	float step;
	float theta;
	int i;

	if (dist != 0)
		theta = asin((r1 - r0) / dist) + M_PI/2.0 + atan2(y1 - y0, x1 - x0);
	else
		theta = 0;

	if (!(theta >= 0 && theta <= M_PI))
		theta = 0;

	step = M_PI * 2. / (float)MAX_RAD_SEGS;

	for (i = 0; i < MAX_RAD_SEGS; theta -= step, ++i)
	{
		fz_point pt1, pt2, pt3, pt4;

		pt1.x = cos (theta) * r1 + x1;
		pt1.y = sin (theta) * r1 + y1;
		pt2.x = cos (theta) * r0 + x0;
		pt2.y = sin (theta) * r0 + y0;
		pt3.x = cos (theta+step) * r1 + x1;
		pt3.y = sin (theta+step) * r1 + y1;
		pt4.x = cos (theta+step) * r0 + x0;
		pt4.y = sin (theta+step) * r0 + y0;

		if (r0 > 0) {
			if (!nomesh) {
				pdf_setmeshvalue(mesh, n++, pt1.x, pt1.y, c1);
				pdf_setmeshvalue(mesh, n++, pt2.x, pt2.y, c0);
				pdf_setmeshvalue(mesh, n++, pt4.x, pt4.y, c0);
			}
			pos++;
		}

		if (r1 > 0) {
			if (!nomesh) {
				pdf_setmeshvalue(mesh, n++, pt1.x, pt1.y, c1);
				pdf_setmeshvalue(mesh, n++, pt3.x, pt3.y, c1);
				pdf_setmeshvalue(mesh, n++, pt4.x, pt4.y, c0);
			}
			pos++;
		}
	}

	return pos;
}

fz_error
pdf_loadtype3shade(fz_shade *shade, pdf_xref *xref, fz_obj *shading, fz_obj *ref)
{
	fz_obj *obj;
	float x0, y0, r0, x1, y1, r1;
	float t0, t1;
	int e0, e1;
	float ex0, ey0, er0;
	float ex1, ey1, er1;
	float rs;
	int i;
	fz_error error;

	pdf_logshade("load type3 shade {\n");

	obj = fz_dictgets(shading, "Coords");
	x0 = fz_toreal(fz_arrayget(obj, 0));
	y0 = fz_toreal(fz_arrayget(obj, 1));
	r0 = fz_toreal(fz_arrayget(obj, 2));
	x1 = fz_toreal(fz_arrayget(obj, 3));
	y1 = fz_toreal(fz_arrayget(obj, 4));
	r1 = fz_toreal(fz_arrayget(obj, 5));

	pdf_logshade("coords %g %g %g  %g %g %g\n", x0, y0, r0, x1, y1, r1);

	obj = fz_dictgets(shading, "Domain");
	if (obj) {
		t0 = fz_toreal(fz_arrayget(obj, 0));
		t1 = fz_toreal(fz_arrayget(obj, 1));
	} else {
		t0 = 0.;
		t1 = 1.;
	}

	obj = fz_dictgets(shading, "Extend");
	if (obj) {
		e0 = fz_tobool(fz_arrayget(obj, 0));
		e1 = fz_tobool(fz_arrayget(obj, 1));
	} else {
		e0 = 0;
		e1 = 0;
	}

	pdf_logshade("domain %g %g\n", t0, t1);
	pdf_logshade("extend %d %d\n", e0, e1);

	error = pdf_loadshadefunction(shade, xref, shading, t0, t1);
	if (error)
		return fz_rethrow(error, "unable to load shading function");

	if (r0 < r1)
		rs = r0 / (r0 - r1);
	else
		rs = -BIGNUM;

	ex0 = x0 + (x1 - x0) * rs;
	ey0 = y0 + (y1 - y0) * rs;
	er0 = r0 + (r1 - r0) * rs;

	if (r0 > r1)
		rs = r1 / (r1 - r0);
	else
		rs = -BIGNUM;

	ex1 = x1 + (x0 - x1) * rs;
	ey1 = y1 + (y0 - y1) * rs;
	er1 = r1 + (r0 - r1) * rs;

	for (i=0; i<2; ++i)
	{
		int pos = 0;
		if (e0)
			pos = buildannulusmesh(shade->mesh, pos, ex0, ey0, er0, x0, y0, r0, 0, 0, 1-i);
		pos = buildannulusmesh(shade->mesh, pos, x0, y0, r0, x1, y1, r1, 0, 1., 1-i);
		if (e1)
			pos = buildannulusmesh(shade->mesh, pos, x1, y1, r1, ex1, ey1, er1, 1., 1., 1-i);

		if (i == 0)
		{
			shade->meshlen = pos;
			shade->mesh = fz_malloc(sizeof(float) * 9 * shade->meshlen);
			if (!shade->mesh)
				return fz_rethrow(-1, "out of memory");
		}
	}

	pdf_logshade("}\n");

	return fz_okay;
}

