#include <fitz.h>

fz_error *
fz_scalepixmap(fz_pixmap *src, fz_pixmap *dst, int xdenom, int ydenom);

static int getcomp(fz_pixmap *pix, float u, float v, int k)
{
	float fu = floor(u);
	float fv = floor(v);
	float su = u - fu;
	float sv = v - fv;

	int x0 = fu;
	int x1 = x0 + 1;
	int y0 = fv;
	int y1 = y0 + 1;

	x0 = CLAMP(x0, 0, pix->w - 1);
	x1 = CLAMP(x1, 0, pix->w - 1);
	y0 = CLAMP(y0, 0, pix->h - 1);
	y1 = CLAMP(y1, 0, pix->h - 1);

	float a = pix->samples[ y0 * pix->stride + x0 * (pix->n + pix->a) + k ];
	float b = pix->samples[ y0 * pix->stride + x1 * (pix->n + pix->a) + k ];
	float c = pix->samples[ y1 * pix->stride + x0 * (pix->n + pix->a) + k ];
	float d = pix->samples[ y1 * pix->stride + x1 * (pix->n + pix->a) + k ];

	float ab = a * (1.0 - su) + b * su;
	float cd = c * (1.0 - su) + d * su;
	float abcd = ab * (1.0 - sv) + cd * sv;

	return (int)abcd;
}

static inline void
drawscan(fz_matrix *invmat, fz_pixmap *dst, fz_pixmap *src, int y, int x0, int x1)
{
	int x, k;

	float u = invmat->a * x0 + invmat->c * y + invmat->e;
	float v = invmat->b * x0 + invmat->d * y + invmat->f;

	for (x = x0; x < x1; x++)
	{
		for (k = 0; k < src->n + src->a; k++)
			dst->samples[ y * dst->stride + x * (dst->n+dst->a) + k ] = getcomp(src, u, v, k);
		if (!src->a && dst->a)
			dst->samples[ y * dst->stride + x * (dst->n + dst->a) + dst->n ] = 0xFF;

		u += invmat->a;
		v += invmat->c;
	}
}

static inline void
overscanrgb(fz_matrix *invmat, fz_pixmap *dst, fz_pixmap *src, int y, int x0, int x1)
{
	int x;

	float u = invmat->a * x0 + invmat->c * y + invmat->e;
	float v = invmat->b * x0 + invmat->d * y + invmat->f;

	for (x = x0; x < x1; x++)
	{
		float a = 1.0;
		if (u < 0)
			a *= 1.0 - (u - floor(u));
		if (u > src->w - 1)
			a *= u - floor(u);
		if (v < 0)
			a *= 1.0 - (v - floor(v));
		if (v > src->h - 1)
			a *= v - floor(v);

		int sr = getcomp(src, u, v, 0);
		int sg = getcomp(src, u, v, 1);
		int sb = getcomp(src, u, v, 2);

		int dr = dst->samples[ y * dst->stride + x * (dst->n+dst->a) + 0 ];
		int dg = dst->samples[ y * dst->stride + x * (dst->n+dst->a) + 1 ];
		int db = dst->samples[ y * dst->stride + x * (dst->n+dst->a) + 2 ];

		int sa = a * 255;
		int ssa = 255 - sa;

		dr = fz_mul255(sr, sa) + fz_mul255(dr, ssa);
		dg = fz_mul255(sg, sa) + fz_mul255(dg, ssa);
		db = fz_mul255(sb, sa) + fz_mul255(db, ssa);

		dst->samples[ y * dst->stride + x * (dst->n+dst->a) + 0 ] = dr;
		dst->samples[ y * dst->stride + x * (dst->n+dst->a) + 1 ] = dg;
		dst->samples[ y * dst->stride + x * (dst->n+dst->a) + 2 ] = db;
		dst->samples[ y * dst->stride + x * (dst->n+dst->a) + 3 ] = sa;

		u += invmat->a;
		v += invmat->c;
	}
}

static fz_error *
drawtile(fz_renderer *gc, fz_pixmap *out, fz_pixmap *tile, fz_matrix ctm, int over)
{
	static const fz_point rect[4] = { {0, 0}, {0, 1}, {1, 1}, {1, 0} };
	fz_error *error;
	fz_gel *gel = gc->gel;
	fz_ael *ael = gc->ael;
	fz_matrix imgmat;
	fz_matrix invmat;
	fz_point v[4];
	int i, e, y, x0, x1;

	imgmat.a = 1.0 / tile->w;
	imgmat.b = 0.0;
	imgmat.c = 0.0;
	imgmat.d = -1.0 / tile->h;
	imgmat.e = 0.0;
	imgmat.f = 1.0;
	invmat = fz_invertmatrix(fz_concat(imgmat, ctm));

	for (i = 0; i < 4; i++)
		v[i] = fz_transformpoint(ctm, rect[i]);
	fz_resetgel(gel, 1, 1);
	fz_insertgel(gel, v[0].x, v[0].y, v[1].x, v[1].y);
	fz_insertgel(gel, v[1].x, v[1].y, v[2].x, v[2].y);
	fz_insertgel(gel, v[2].x, v[2].y, v[3].x, v[3].y);
	fz_insertgel(gel, v[3].x, v[3].y, v[0].x, v[0].y);
	fz_sortgel(gel);

	e = 0;
	y = gel->edges[0].y;

	while (ael->len > 0 || e < gel->len)
	{
		error = fz_insertael(ael, gel, y, &e);
		if (error)
			return error;

		x0 = ael->edges[0]->x;
		x1 = ael->edges[ael->len - 1]->x;

		if (y >= out->y && y < out->y + out->h)
		{
			x0 = CLAMP(x0, out->x, out->x + out->w - 1);
			x1 = CLAMP(x1, out->x, out->x + out->w - 1);
			if (over && tile->cs && tile->cs->n == 3)
				overscanrgb(&invmat, out, tile, y, x0, x1);
			else
				drawscan(&invmat, out, tile, y, x0, x1);
		}

		fz_advanceael(ael);

		if (ael->len > 0)
			y ++;
		else if (e < gel->len)
			y = gel->edges[e].y;
	}

	return nil;
}

fz_error *
fz_renderimage(fz_renderer *gc, fz_imagenode *node, fz_matrix ctm)
{
	fz_error *error;
	fz_pixmap *tile1;
	fz_pixmap *tile2;
	fz_pixmap *tile3;
	fz_image *image = node->image;
	fz_colorspace *cs = image->cs;
	int w = image->w;
	int h = image->h;
	int n = image->n;
	int a = image->a;
	int sw = w;
	int sh = h;

	float s = sqrt(ctm.a * ctm.a + ctm.b * ctm.b);

	int d = 1;
	while ((w + d - 1) / d > s)
		d++;
	if (d > 1)
		d --;

printf("renderimage s=%g d=%d\n", s, d);

	error = fz_newpixmap(&tile1, cs, 0, 0, w, h, n, a);

printf("  load tile\n");
	error = image->loadtile(image, tile1);

	if (d != 1)
	{
		sw = (w + d - 1) / d;
		sh = (h + d - 1) / d;

printf("  new pixmap\n");
		error = fz_newpixmap(&tile2, cs, 0, 0, sw, sh, n, a);
printf("  scale tile to %d %d\n", sw, sh);
		error = fz_scalepixmap(tile1, tile2, d, d);

printf("  free loaded tile\n");
		fz_freepixmap(tile1);
	}
	else
		tile2 = tile1;

printf("  swtich render mode\n");

	/* render image mask */
	if (n == 0 && a == 1)
	{
printf("draw image mask\n");
		error = fz_newpixmap(&gc->tmp, nil, gc->x, gc->y, gc->w, gc->h, 0, 1);
		fz_clearpixmap(gc->tmp);
		error = drawtile(gc, gc->tmp, tile2, ctm, 0);
fz_debugpixmap(gc->tmp);getchar();
	}

	/* render rgb over */
	else if (n == 3 && a == 0 && gc->acc)
	{
printf("draw image rgb over\n");
		error = drawtile(gc, gc->acc, tile2, ctm, 1);
	}

	/* render generic image */
	else
	{
printf("draw generic image\n");
		error = fz_newpixmap(&tile3, gc->model, 0, 0, sw, sh, gc->model->n, a);
		fz_convertpixmap(tile2, tile3);
		error = fz_newpixmap(&gc->tmp, gc->model, gc->x, gc->y, gc->w, gc->h, gc->model->n, 1);
		fz_clearpixmap(gc->tmp);
		error = drawtile(gc, gc->tmp, tile3, ctm, 0);
		fz_freepixmap(tile3);
	}

	fz_freepixmap(tile2);
	return nil;
}