#include <string.h>

#include "../util/memory.h"
#include "VRoman.h"

#define Vlib_IMPORT
#include "Vlib.h"

#define COLLAPSEUNUSEDPOINTS


static void VComputeClipNormals(Viewport * v)
{

	VPoint   *p, *q;
	int       i, max;
	double    mag;

	p = v->clipPoly->vertex;
	q = v->clipNormals;

/*
 *  We only reserved space for four clipping plane normals in clipNormals
 */

	max = (v->clipPoly->numVtces > 4) ? 4 : v->clipPoly->numVtces;

/*
 *  Compute the unit-normal vectors corresponding to each clipping plane
 */

	for (i = 0; i < max; ++i) {
		mag = sqrt(p->x * p->x + p->y * p->y + p->z * p->z);
		q->x = p->x / mag;
		q->y = p->y / mag;
		q->z = p->z / mag;
		q++;
		p++;
	}

}


static void set_size_and_scale(
	Viewport * v,
	Alib_Rect * view_rect,
	Alib_Point *focus,
	double dist)
{
	VPoint a, b, clip[4];

	Alib_Rect r;
	Alib_setRect(&r, 0, 0, gui_getWidth(v->gui), gui_getHeight(v->gui));
	Alib_intersectRect(view_rect, &r, &v->rect);
	v->focus = *focus;
	v->dist = dist;

/*
 *  Use that info to set scaling factors.
 */

	v->Scale.x = v->xres * dist * 4;
	v->Scale.y = v->yres * dist * 4;
	v->Scale.z = 1.0;

/*
 *  Middl should have been a "double" for accuracy, but was made an "int" for
 *  speed.  Sooo, to eliminate some stick rounding problems, we'll consider it
 *  a fixed point number with the right two bits after the decimal point.
 */

	v->Middl.x = 4 * focus->x;
	v->Middl.y = 4 * focus->y;

/*
	Build the clipping planes for our view into the eye space. The eye is
	located in (0,0,0) and looking toward the z+ axis, x=right, y=down.

	The projection rectangle v->rect is located at z=dist and parallel
	to xy. The z axis intersects the projection screen in its "focus"
	point.

	Begin calculating the points "a" (top-left corner of the screen) and
	"b" (bottom-right corner) in meters:
*/

	VSetPoint(&a,
		(v->rect.a.x - focus->x) / v->xres,
		(v->rect.a.y - focus->y) / v->yres,
		dist);

	VSetPoint(&b,
		(v->rect.b.x - focus->x) / v->xres,
		(v->rect.b.y - focus->y) / v->yres,
		dist);
	
	clip[0] = a;
	VSetPoint(&clip[1], a.x, b.y, dist);
	clip[2] = b;
	VSetPoint(&clip[3], b.x, a.y, dist);

	v->clipPoly = VCreatePolygon(4, clip, (VColor_Type *) 0);
	VGetPlanes(v->clipPoly);
	VComputeClipNormals(v);

	VIdentMatrix(&v->eyeSpace);
}


static void Vlib_destruct(void *p)
{
	Viewport *v = p;
	VDestroyPolygon(v->clipPoly);
}


Viewport * Vlib_new (
	gui_Type * gui,
	Alib_Window *w,
	Alib_Rect *view_rect,
	Alib_Point *focus,
	double dist)
{
	Viewport *v = memory_allocate(sizeof(Viewport), Vlib_destruct);
	memset(v, 0, sizeof(sizeof(Viewport)));

	v->gui = gui;
	v->w = w;

	v->flags = VPPerspective | VPClip;
	/* FIXME: enable also VPDoubleBuffer */

	gui_DisplayDimensions dd;
	gui_getDisplayDimensions(gui, &dd);
	v->xres = 1000.0 * dd.widthPixels / dd.widthMillimiters;
	v->yres = 1000.0 * dd.heightPixels / dd.heightMillimiters;
	set_size_and_scale(v, view_rect, focus, dist);

	return v;
}


void VResizeViewport (
	Viewport *v,
	Alib_Rect *view_rect,
	Alib_Point *focus,
	double dist)
{
	VDestroyPolygon(v->clipPoly);

	set_size_and_scale(v, view_rect, focus, dist);
}


void
VDrawSegments(Viewport * v, Alib_Segment * seg, int nseg, Alib_Pixel color)
{
	Alib_drawSegments(v->w, seg, nseg, color);
}


int
VFontWidthPixels(Viewport * v, int scale)
{
	return VRomanGlyph['A'].glyph_width * scale / 25600;
}


void
VDrawStrokeString(Viewport * v, int x, int y,
				  char *str, int len, int scale, Alib_Pixel color)
{

	int c, i, k, m;
	VGlyph_Vertex *p;
	int x1, y1, x2, y2;

	for (; len > 0; --len) {

		if ((c = *str++) < 128) {
			k = VRomanGlyph[c].path_start;
			for (i = 0; i < VRomanGlyph[c].path_count; ++i, ++k) {
				p = &VRomanVertex[VRomanPath[k].vertex_start];
				x1 = p->x * scale / 25600 + x;
				y1 = y - p->y * scale / 25600;
				++p;
				for (m = 1; m < VRomanPath[k].vertex_count; ++m, ++p) {
					x2 = p->x * scale / 25600 + x;
					y2 = y - p->y * scale / 25600;
					Alib_drawLine(v->w, x1, y1, x2, y2, color);
					x1 = x2;
					y1 = y2;
				}
			}

			x += VRomanGlyph[c].glyph_width * scale / 25600;

		}
	}
}


void
VGetStrokeString(Viewport * v, int x, int y, Alib_Segment * seg, int *nseg,
				 char *str, int len, int scale)
{

	int c, i, k, m, count;
	VGlyph_Vertex *p;
	int x1, y1, x2, y2;
	Alib_Segment  *pseg;

	count = *nseg;

	for (; len > 0; --len) {

		if ((c = *str++) < 128) {
			k = VRomanGlyph[c].path_start;
			for (i = 0; i < VRomanGlyph[c].path_count; ++i, ++k) {
				p = &VRomanVertex[VRomanPath[k].vertex_start];
				x1 = p->x * scale / 25600 + x;
				y1 = y - p->y * scale / 25600;
				++p;
				for (m = 1; m < VRomanPath[k].vertex_count; ++m, ++p) {
					x2 = p->x * scale / 25600 + x;
					y2 = y - p->y * scale / 25600;
					pseg = &seg[count++];
					pseg->x1 = x1;
					pseg->x2 = x2;
					pseg->y1 = y1;
					pseg->y2 = y2;
					x1 = x2;
					y1 = y2;
				}
			}

			x += VRomanGlyph[c].glyph_width * scale / 25600;

		}
	}

	*nseg = count;
}


void
VFillPolygon(Viewport * v, VPolygon * poly)
{
	VPoint  *p;
	Alib_Point    xpt[VmaxVP];
#ifdef COLLAPSEUNUSEDPOINTS
	Alib_Point   *lastpt;
#endif
	int i, k;
	Alib_Window *w;

	w = v->w;
	k = 0;
#ifdef COLLAPSEUNUSEDPOINTS
	lastpt = &xpt[0];
#endif

	for ((i = 0, p = poly->vertex); i < poly->numVtces; (++i, ++p)) {

		if (v->flags & VPPerspective && p->z != 0.0 /* FIXME */) {
			xpt[k].x = (v->Middl.x + (int) (v->Scale.x * p->x / p->z)) >> 2;
			xpt[k].y = (v->Middl.y + (int) (v->Scale.y * p->y / p->z)) >> 2;
		}
		else {
			xpt[k].x = (v->Middl.x + (int) (v->Scale.x * p->x)) >> 2;
			xpt[k].y = (v->Middl.y + (int) (v->Scale.y * p->y)) >> 2;
		}

#ifdef COLLAPSEUNUSEDPOINTS
		if (k == 0 || !(xpt[k].x == lastpt->x && xpt[k].y == lastpt->y))
			lastpt = &xpt[k++];
#else
		++k;
#endif

	}

	if (k > 0) {
#ifdef COLLAPSEUNUSEDPOINTS
		if ( k > 2 ){
/*
		if (k == 1){
			Alib_drawPoint(w, xpt[0].x, xpt[0].y, z);
		} else if (k == 2){
			DrawLine(w, xpt[0].x, xpt[0].y, xpt[1].x, xpt[1].y, z);
		} else {
*/
#endif
			Alib_Pixel color = Alib_computePolygonColor(w, poly);
			Alib_fillPolygon(w, xpt, k, color);
#ifdef COLLAPSEUNUSEDPOINTS
		}
#endif
	}
}


void
VFillRectangle (Viewport *v, Alib_Rect *r, Alib_Pixel c)
{
	Alib_fillRect( v->w, r, c );
}


VPolygon * VGetPlanes(VPolygon * poly)
{

	VPoint    tmp[VmaxVP], *p;
	int       i, lasti;

	lasti = poly->numVtces - 1;
	p = poly->vertex;

	for (i = 0; i < poly->numVtces; ++i) {
		tmp[i].x = p->y * poly->vertex[lasti].z - p->z *
			poly->vertex[lasti].y;
		tmp[i].y = p->z * poly->vertex[lasti].x - p->x *
			poly->vertex[lasti].z;
		tmp[i].z = p->x * poly->vertex[lasti].y - p->y *
			poly->vertex[lasti].x;
		lasti = i;
		p++;
	}

	for (i = 0; i < poly->numVtces; ++i)
		poly->vertex[i] = tmp[i];

	return poly;
}


void
VSetClipRect (Viewport *v, Alib_Rect *r)
{
	Alib_Rect clip;

	Alib_intersectRect(&v->rect, r, &clip);
	Alib_setClipRect(v->w, &clip);
}


void
VExposeBuffer(Viewport * v)
{
	Alib_drawDifferences(v->w);
}


void
VForceWindowRedraw(Viewport * v)
{
	Alib_invalidate(v->w);
}


int VEyeToScreen(Viewport * v, VPoint * p, int *x, int *y)
{
	if (p->z <= 0.0)
		return 0;

	*x = (v->Middl.x + (int) (v->Scale.x * p->x / p->z)) >> 2;
	*y = (v->Middl.y + (int) (v->Scale.y * p->y / p->z)) >> 2;

	return 1;
}


void VGetEyeSpace(Viewport * v, VPoint EyePt, VPoint CntrInt, VPoint up)
{

	VMatrix   Mtx, es;
	VPoint    C1, C2;
	double    Hypotenuse, h1, CosA, SinA;

/*
 *  Calculate the eye space transformation matrix
 *
 *  First, orient the Z axis towards the center of interest.
 */

	VIdentMatrix(&(v->eyeSpace));
	v->eyeSpace.m[0][3] = -EyePt.x;
	v->eyeSpace.m[1][3] = -EyePt.y;
	v->eyeSpace.m[2][3] = -EyePt.z;
	VTransform(&CntrInt, &(v->eyeSpace), &C1);

	VIdentMatrix(&Mtx);
	Hypotenuse = sqrt(C1.x * C1.x + C1.y * C1.y);
	if (Hypotenuse > 0.0) {
		CosA = C1.y / Hypotenuse;
		SinA = C1.x / Hypotenuse;
		Mtx.m[0][0] = Mtx.m[1][1] = CosA;
		Mtx.m[1][0] = SinA;
		Mtx.m[0][1] = -SinA;
		es = v->eyeSpace;
		VMatrixMult(&es, &Mtx, &(v->eyeSpace));
	}

	VTransform(&CntrInt, &(v->eyeSpace), &C2);
	VIdentMatrix(&Mtx);
	Hypotenuse = sqrt(C2.y * C2.y + C2.z * C2.z);
	if (Hypotenuse > 0.0) {
		CosA = C2.y / Hypotenuse;
		SinA = -C2.z / Hypotenuse;
		Mtx.m[1][1] = Mtx.m[2][2] = CosA;
		Mtx.m[2][1] = SinA;
		Mtx.m[1][2] = -SinA;
		es = v->eyeSpace;
		VMatrixMult(&es, &Mtx, &(v->eyeSpace));
	}

/*
 *  Orient the y axis towards "up".
 */

	VTransform(&up, &(v->eyeSpace), &C2);
	VIdentMatrix(&Mtx);
	h1 = sqrt(C2.y * C2.y + C2.z * C2.z);
	Hypotenuse = sqrt(C2.x * C2.x + h1 * h1);
	if (Hypotenuse > 0.0) {
		CosA = h1 / Hypotenuse;
		SinA = C2.x / Hypotenuse;
		if (C2.z < 0.0) {
			CosA = -CosA;
		}
		Mtx.m[0][0] = Mtx.m[2][2] = CosA;
		Mtx.m[2][0] = SinA;
		Mtx.m[0][2] = -SinA;
		es = v->eyeSpace;
		VMatrixMult(&es, &Mtx, &(v->eyeSpace));
	}

/*
 *  Swap y and z axes.
 */

	VIdentMatrix(&Mtx);
	Mtx.m[1][1] = Mtx.m[2][2] = 0.0;
	Mtx.m[2][1] = Mtx.m[1][2] = 1.0;
	Mtx.m[1][2] = -1.0; /* FIXME -- remove, test code */
	es = v->eyeSpace;
	VMatrixMult(&es, &Mtx, &(v->eyeSpace));
}


int VWorldToScreen(Viewport * v, VPoint * p, int *x, int *y)
{

	VPoint    eyept;

	VTransform(p, &(v->eyeSpace), &eyept);
	return VEyeToScreen(v, &eyept, x, y);
}
