#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

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

#define VPoly_IMPORT
#include "VPoly.h"

static char *aspect_name[] =
{
	"left",
	"right",
	"front right bottom",
	"front right top",
	"front left bottom",
	"front left top",
	"aft right bottom",
	"aft right top",
	"aft left bottom",
	"aft left top"
};


typedef struct VPolySet_Elem {
	VPolygon *poly;
	struct VPolySet_Elem *next;
} VPolySet_Elem;


struct VPolySet {
	int count;
	VPolySet_Elem *first;
	VPolySet_Elem *last;
	VPolySet_Elem *curr;
};


static VPolySet_Elem *free_elem = NULL;
static VPolySet      *free_set  = NULL;


static void VPoly_cleanup(void)
{
	VPolySet *s;
	VPolySet_Elem *e;

	while( free_set != NULL ){
		s = free_set;
		free_set = (VPolySet *) free_set->first;
		memory_dispose(s);
	}

	while( free_elem != NULL ){
		e = free_elem;
		free_elem = free_elem->next;
		memory_dispose(e);
	}
}


VPolySet * VPolySet_New()
{
	VPolySet *s;

	if( free_set == NULL ){
		s = memory_allocate( sizeof( VPolySet ), NULL );
		memory_registerCleanup(VPoly_cleanup);
	} else {
		s = free_set;
		free_set = (VPolySet *) free_set->first;
	}

	s->count = 0;
	s->first = NULL;
	s->last = NULL;
	s->curr = NULL;
	return s;
}


void VPolySet_Free(VPolySet *s, int release_polygons_too)
{
	VPolySet_Elem *e, *p;

	if( s == NULL )
		return;
	
	if( s->count < 0 )
		error_internal("using already released VPolySet", 0);
	
	e = s->first;
	while( e != NULL ){

		p = e->next;

		if( release_polygons_too )
			VDestroyPolygon(e->poly);

		/* Release "e": */
		e->next = free_elem;
		free_elem = e;

		e = p;
	}

	s->first = (VPolySet_Elem *) free_set;
	free_set = s;
	free_set->count = -1;
}


void VPolySet_Add(VPolySet *s, VPolygon *p)
{
	VPolySet_Elem *e;

	if( s->count < 0 )
		error_internal("using already released VPolySet", 0);
	
	if( free_elem == NULL ){
		e = memory_allocate( sizeof( VPolySet_Elem ), NULL );
	} else {
		e = free_elem;
		free_elem = free_elem->next;
	}

	e->poly = p;
	e->next = NULL;

	if( s->count == 0 ){
		s->first = e;
	} else {
		s->last->next = e;
	}
	s->last = e;

	s->count++;
}


VPolygon * VPolySet_First(VPolySet *s)
{
	if( s == NULL )
		return NULL;

	if( s->count < 0 )
		error_internal("using already released VPolySet", 0);
	
	if( s->count == 0 ){
		s->curr = NULL;
		return NULL;
	}
	
	s->curr = s->first;
	return s->curr->poly;
}


VPolygon * VPolySet_Next(VPolySet *s)
{
	if( s == NULL )
		return NULL;

	if( s->count < 0 )
		error_internal("using already released VPolySet", 0);
	
	if( s->curr == NULL )
		return NULL;
	
	s->curr = s->curr->next;

	if( s->curr == NULL )
		return NULL;
	else
		return s->curr->poly;
}


void VPolySet_Set(VPolySet *s, VPolygon *poly)
{
	if( s->count < 0 )
		error_internal("using already released VPolySet", 0);
	
	s->curr->poly = poly;
}


int VPolySet_Count(VPolySet *s)
{
	if( s == NULL )
		return 0;

	if( s->count < 0 )
		error_internal("using already released VPolySet", 0);
	
	if( s == NULL )
		return 0;
	else
		return s->count;
}


char *
VGetAspectName(int aspect)
{
	return aspect_name[aspect];
}


void
VPrintPolygon(FILE * file, VPolygon * p)
{

	int       i;
	char     *nullPoly = "*** Null Polygon ***\n";

	if (p == (VPolygon *) NULL)
		fprintf(file, nullPoly);
	else {
		if (p->numVtces == 0) {
			fprintf(file, nullPoly);
			return;
		}

		fprintf(file, "%d vertices:\n", p->numVtces);

		for (i = 0; i < p->numVtces; ++i)
			fprintf(file, "%9.6g %9.6g %9.6g\n", p->vertex[i].x,
					p->vertex[i].y, p->vertex[i].z);
	}

	return;
}


VPolygon *
VCreatePolygon(int numVtces, VPoint * vert, VColor_Type * color)
{
	VPolygon  template;

	template.color = color;
	template.backColor = (VColor_Type *) NULL;
	template.flags = 0;
	template.cullDistance = 0.0;

	return VCreatePolygonFromTemplate(numVtces, vert, &template);
}


VPolygon *
VCreatePolygonFromTemplate(int numVtces, VPoint * vert, VPolygon * template)
{
	VPolygon *p;
	VPoint    a, b;

	p = memory_allocate(sizeof(VPolygon), NULL);

	*p = *template;
	p->numVtces = numVtces;
	p->vertex = memory_allocate(sizeof(VPoint) * numVtces, NULL);
	memcpy((char *) p->vertex,
		   (char *) vert, sizeof(VPoint) * p->numVtces);

	if ((p->flags & PolyNormalValid) == 0) {
		if ((p->flags & PolyClipBackface) != 0 ||
			p->backColor != (VColor_Type *) NULL) {
			a.x = vert[0].x - vert[1].x;
			a.y = vert[0].y - vert[1].y;
			a.z = vert[0].z - vert[1].z;
			b.x = vert[2].x - vert[1].x;
			b.y = vert[2].y - vert[1].y;
			b.z = vert[2].z - vert[1].z;
			VCrossProd(&a, &b, &p->normal);
			p->flags |= PolyNormalValid;
		}
	}

	return p;
}


VPolygon *
VCopyPolygon(VPolygon * poly)
{
	return VCreatePolygonFromTemplate(poly->numVtces, poly->vertex, poly);
}


VPolygon *
VTransformPolygon(VPolygon * poly, VMatrix * m)
{

	int       i;
	VPoint    tmp;

	for (i = 0; i < poly->numVtces; ++i) {
		VTransform(&(poly->vertex[i]), m, &tmp);
		poly->vertex[i] = tmp;
	}

	return poly;
}


static VPolygon *
_VClipPolygon(VPolygon * poly, VPoint * clipPlane)
{

	register int j, lastj, numPts = 0, clipped = 0;
	double    d1, d2, a;
	VPoint    tmpPoint[VmaxVP];
	VPolygon *p;

	if (poly->numVtces > 0) {

		lastj = poly->numVtces - 1;
		d1 = VDotProd(&(poly->vertex[poly->numVtces - 1]), clipPlane);
		numPts = 0;

/*
 *  Examine each vertex and determine if it is inside or outside of the
 *  specified clipping plane.
 */

		for (j = 0; j < poly->numVtces; ++j) {

/* Leading vertex inside? */

			if (d1 > 0.0)
				tmpPoint[numPts++] = poly->vertex[lastj];

			d2 = VDotProd(&(poly->vertex[j]), clipPlane);

/* Does the edge straddle the window? If so, add a vertex on the window */

			if (d1 * d2 < 0.0) {
				clipped = 1;
				a = d1 / (d1 - d2);
				tmpPoint[numPts].x = a * poly->vertex[j].x +
					(1.0 - a) * poly->vertex[lastj].x;
				tmpPoint[numPts].y = a * poly->vertex[j].y +
					(1.0 - a) * poly->vertex[lastj].y;
				tmpPoint[numPts++].z = a * poly->vertex[j].z +
					(1.0 - a) * poly->vertex[lastj].z;
			}

			lastj = j;
			d1 = d2;
		}
	}

/*
 *  If the polygon was completely out of bounds, delete this polygon.
 */

	if (numPts == 0) {
		p = (VPolygon *) NULL;
		VDestroyPolygon(poly);
#ifdef DEBUG
		fprintf(stderr, "VClipPolygon: polygon outside area of interest\n");
#endif
	}

/*
 *  If we did any clipping, return the clipped polygon.
 */

	else if (clipped) {
		p = VCreatePolygonFromTemplate(numPts, tmpPoint, poly);
#ifdef DEBUG
		fprintf(stderr, "VClipPolygon: Polygon has been clipped:\n");
		fprintf(stderr, "Before Clipping:\n");
		VPrintPolygon(stderr, poly);
		fprintf(stderr, "\nAfter Clipping:\n\n");
		VPrintPolygon(stderr, p);
#endif
		VDestroyPolygon(poly);
	}
	else
		p = poly;

	return p;
}


VPolygon *
VClipPolygon(VPolygon * poly, VPolygon * clipPoly)
{

	int       i;
	VPolygon *p = poly;

/*
 *  Clip against each clipping plane supplied, one at a time.
 */

	for (i = 0; i < clipPoly->numVtces; ++i) {

		if (p == (VPolygon *) NULL)
			break;

		p = _VClipPolygon(p, &(clipPoly->vertex[i]));

	}

	return p;
}


VPolygon *
VClipSidedPolygon(VPolygon * poly, VPolygon * clipPoly)
{

	int       i;
	VPolygon *p = poly;

	if (p->flags & PolyNormalValid) {
		if (VDotProd(&p->vertex[0], &p->normal) >= 0.0) {
			if (p->backColor) {
				p->flags |= PolyUseBackColor;
			}
			else if (p->flags & PolyClipBackface) {
				VDestroyPolygon(p);
				return (VPolygon *) NULL;
			}
		}
		else {
			p->flags &= ~(PolyUseBackColor);
		}
	}

/*
 *  Clip against each clipping plane supplied, one at a time.
 */

	for (i = 0; i < clipPoly->numVtces; ++i) {

		if (p == (VPolygon *) NULL)
			break;

		p = _VClipPolygon(p, &(clipPoly->vertex[i]));

	}

	return p;
}


static void ComputeRotationMatrix(double r, VPoint * e, VMatrix * m)
{
	double one64th = 1.0 / 64.0, ma;
	VPoint Ax, Ay,
		Wy = {0, 1, 0},
		Wz = {0, 0, 1};
	VMatrix tm, tm1;

	VIdentMatrix(&tm);
	if (r != 0.0) {
		VRotate(&tm, ZRotation, r * M_PI / 180.0);
	}

	if (fabs(e->x) < one64th && fabs(e->y) < one64th) {
		VCrossProd(&Wy, e, &Ax);
	}
	else {
		VCrossProd(&Wz, e, &Ax);
	}

	ma = sqrt(Ax.x * Ax.x + Ax.y * Ax.y + Ax.z * Ax.z);
	Ax.x /= ma;
	Ax.y /= ma;
	Ax.z /= ma;

	VCrossProd(e, &Ax, &Ay);

	ma = sqrt(Ay.x * Ay.x + Ay.y * Ay.y + Ay.z * Ay.z);
	Ay.x /= ma;
	Ay.y /= ma;
	Ay.z /= ma;

	VIdentMatrix(m);
	m->m[0][0] = Ax.x;
	m->m[1][0] = Ax.y;
	m->m[2][0] = Ax.z;

	m->m[0][1] = Ay.x;
	m->m[1][1] = Ay.y;
	m->m[2][1] = Ay.z;

	m->m[0][2] = e->x;
	m->m[1][2] = e->y;
	m->m[2][2] = e->z;

	VMatrixMultByRank(&tm, m, &tm1, 3);
	*m = tm1;
}


VPolygon *
ScalePolygon(VPolygon * in, VPoint * offset, VPoint * scale, VPoint *e, double r)
{
    int numVtces = in->numVtces, i;
    VPoint *vert;
    VPolygon *p;
    VPoint a, b, tmp, offset1;
    VMatrix m;

    p = memory_allocate(sizeof(VPolygon), NULL);
#ifdef DEBUG
    fprintf(stderr, "scaling %d-point polygon %g, %g, %g; rotation %g; offset %g, %g, %g\n",
	    in->numVtces, scale->x, scale->y, scale->z, r,
	    offset->x, offset->y, offset->z);
#endif

    *p = *in;
    p->numVtces = numVtces;
    vert = p->vertex = memory_allocate(sizeof(VPoint) * numVtces, NULL);

    ComputeRotationMatrix(r, e, &m);
    
    VTransform_ (offset, &m, &offset1);

    for (i = 0; i < numVtces; ++i) {
    	p->vertex[i] = in->vertex[i];
#ifdef notdef
    	p->vertex[i].x -= offset->x;
	p->vertex[i].y -= offset->y;
	p->vertex[i].z -= offset->z;
#endif
        VTransform_(&p->vertex[i], &m, &tmp);
#ifdef notdef
	tmp.x += offset1.x;
	tmp.y += offset1.y;
	tmp.z += offset1.z;
#endif
	p->vertex[i].x = tmp.x * scale->x;
	p->vertex[i].y = tmp.y * scale->y;
	p->vertex[i].z = tmp.z * scale->z;
#ifdef notdef
	p->vertex[i].x += offset->x;
	p->vertex[i].y += offset->y;
	p->vertex[i].z += offset->z;
	p->vertex[i].x += offset1.x;
	p->vertex[i].y += offset1.y;
	p->vertex[i].z += offset1.z;
#endif
    }

    if ((p->flags & PolyNormalValid) == 0) {

		if ((p->flags & PolyClipBackface) != 0
		|| p->backColor != (VColor_Type *) NULL) {
			a.x = vert[0].x - vert[1].x;
			a.y = vert[0].y - vert[1].y;
			a.z = vert[0].z - vert[1].z;
			b.x = vert[2].x - vert[1].x;
			b.y = vert[2].y - vert[1].y;
			b.z = vert[2].z - vert[1].z;
			VCrossProd(&a, &b, &p->normal);
			p->flags |= PolyNormalValid;
		}
    }

    return p;
}


