#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <GL/gl.h>
#include <GL/glu.h>

#include "forms.h"
#include "demo_forms.h"

#include "commandline2.h"
#include "glwin.h"
#include "glpic.h"
#include "jlmouse.h"
#include "jltrackball.h"

#include "subdobj.h"


FD_Main_Form *Main_Form;


GLWin W(512, 512, "Polygon");

JLMouse M;
JLTrackball TB;

SubdObj SDO;

HalfEdge *HE = NULL;
HalfEdge *next_HE = NULL;


Vec4 ObjCenter(0, 0, 0);
float radius = 2;

//int draw_neighbors = 0;


GLUquadric *my_quad = NULL;


void SetupForms(int argc, char *argv[]) {

	fl_initialize(&argc, argv, "HalfEdge Demo", 0, 0);

	Main_Form = create_form_Main_Form();

	fl_show_form(Main_Form->Main_Form, FL_PLACE_FREE, FL_FULLBORDER,
				 "HalfEdge Demo");
}


void SetupView() {

	W.MakeCurrent();

	glViewport(0, 0, W.x(), W.y());

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
//	glOrtho(-2, 2, -2, 2, 5, 15);
	glFrustum(-0.01, 0.01, -0.01, 0.01, 0.02, radius*5);
//	glTranslatef(0.0, 0.0, -10);

	glTranslatef(-ObjCenter.x(), -ObjCenter.y(), -ObjCenter.z());
	glTranslatef(0, 0, -(radius * 2.5));


	float ar = float(W.x()) / float(W.y());

	if (ar > 1.0f)
		glScalef(1.0/ar, 1, 1);
	else
		glScalef(1, ar, 1);
}

void SetupLighting() {

	glEnable(GL_NORMALIZE);

	static GLfloat light_diffuse0[] = { 0.8, 0.8, 0.8, 1.0 };

//	static GLfloat light_position0[] = { -2.0, 3.0, 1.0, 0.0 };
	static GLfloat light_position0[] = { -2.0, 3.0, 10.0, 0.0 };

	glMatrixMode(GL_MODELVIEW);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse0);
	glLightfv(GL_LIGHT0, GL_POSITION, light_position0);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);

	static GLfloat specular_color[] = { 0.2f, 0.2f, 0.2f, 0.0f };

	glMaterialf(GL_FRONT, GL_SHININESS, 30.0f);
	glMaterialfv(GL_FRONT, GL_SPECULAR, specular_color);
}

void Zoom(float z) {

	z /= W.x();

	z *= 5.0f;

	if (z > 0)
		radius *= (1 + z);

	if (z < 0)
		radius /= (1 - z);
}


#define VP(p) glVertex3f(p.x(), p.y(), p.z())
#define VPV(p,v) glVertex3f(p.x()+v.x(), p.y()+v.y(), p.z()+v.z())


void DrawBox(const Vec4 &C,
			 const Vec4 &x, const Vec4 &y, const Vec4 &z,
			 int gl_mode) {

	Vec4 v[8];

	for (int i=0; i<8; i++) {
		v[i] = C;
		if (i & 1) Vec4FastAdd(v[i], v[i], x); else Vec4FastSub(v[i], v[i], x);
		if (i & 2) Vec4FastAdd(v[i], v[i], y); else Vec4FastSub(v[i], v[i], y);
		if (i & 4) Vec4FastAdd(v[i], v[i], z); else Vec4FastSub(v[i], v[i], z);
	}


	// left:
	glBegin(gl_mode);
	glNormal3f(-x.x(), -x.y(), -x.z());
	VP(v[0]);
	VP(v[2]);
	VP(v[6]);
	VP(v[4]);
	glEnd();

	// right:
	glBegin(gl_mode);
	glNormal3f(x.x(), x.y(), x.z());
	VP(v[1]);
	VP(v[5]);
	VP(v[7]);
	VP(v[3]);
	glEnd();

	// bottom:
	glBegin(gl_mode);
	glNormal3f(-y.x(), -y.y(), -y.z());
	VP(v[0]);
	VP(v[4]);
	VP(v[5]);
	VP(v[1]);
	glEnd();

	// top:
	glBegin(gl_mode);
	glNormal3f(y.x(), y.y(), y.z());
	VP(v[2]);
	VP(v[6]);
	VP(v[7]);
	VP(v[3]);
	glEnd();

	// back:
	glBegin(gl_mode);
	glNormal3f(-z.x(), -z.y(), -z.z());
	VP(v[0]);
	VP(v[1]);
	VP(v[3]);
	VP(v[2]);
	glEnd();

	// front:
	glBegin(gl_mode);
	glNormal3f(z.x(), z.y(), z.z());
	VP(v[4]);
	VP(v[6]);
	VP(v[7]);
	VP(v[5]);
	glEnd();


	glEnd();
}


void DrawSphere(const Vec4 &C, float r) {

	glPushMatrix();

	glTranslatef(C.x(), C.y(), C.z());

	gluSphere(my_quad, r, 16, 12);

	glPopMatrix();
}

void DrawCurve(Vec4 &P0, Vec4 &P1, Vec4 &P2) {

	Vec4 tmp0, tmp1, P;

	glBegin(GL_LINE_STRIP);

	for (int i=0; i<25; i++) {

		float t = float(i) / 24.0;

		Vec3_FastWeightedSum(tmp0, P0, 1-t, P1, t);
		Vec3_FastWeightedSum(tmp1, P1, 1-t, P2, t);
		Vec3_FastWeightedSum(P, tmp0, 1-t, tmp1, t);

		VP(P);
	}

	glEnd();
}


void DrawHalfEdge(HalfEdge *he, int gl_mode) {

	Vec4 Center, Forward, Right, Up;

	SDO.CalcEdgeFrame(he, Center, Forward, Right, Up);


	Forward.Negate();

	Up *= 0.125;
	Right *= 0.25;
	Vec4FastAddScale(Center, Center, Right, -1.35);

	DrawBox(Center, Right, Up, Forward, gl_mode);
}

void DrawVertex(HalfEdge *he) {

	Vec4 L(he->_v->Point(), he->_other_half->_v->Point());

	DrawSphere(he->_v->Point(), L.Length() * 0.05);
}

void DrawLinks(HalfEdge *he, int vertex_only) {

	Vec4 C1, F1, R1, U1;
	Vec4 C2, F2, R2, U2;
	Vec4 C3, F3, R3, U3;
	Vec4 V;

	SDO.CalcEdgeFrame(he, C1, F1, R1, U1);
	SDO.CalcEdgeFrame(he->_link->_other_half, C2, F2, R2, U2);
	SDO.CalcEdgeFrame(he->_other_half, C3, F3, R3, U3);

	Vec4 OrigC1 = C1;

	R1 *= 0.25;
	Vec4FastAddScale(C1, C1, R1, -1.35);
	R2 *= 0.25;
	Vec4FastAddScale(C2, C2, R2, -1.35);
	R3 *= 0.25;
	Vec4FastAddScale(C3, C3, R3, -1.35);


	if (!vertex_only) {

		Vec4FastAddScale(V, he->_v->Point(), U1, 0.5);
		DrawCurve(C1, V, C2);

		Vec4FastAddScale(V, OrigC1, U1, 0.5);
		Vec4FastAddScale(V, V, R1, 2);
		DrawCurve(C1, V, C3);
	}

	Vec4FastAdd(V, C1, F1);
	DrawCurve(C1, V, he->_v->Point());
}



void Redraw(int flash_link, int flash_other_half) {

	static GLfloat he_color[] = { 1.0, 0.0, 0.5, 1.0 };


	W.Clear(0.15, 0.20, 0.25);

	SetupView();

	glCullFace(GL_BACK);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();


//	glColor3f(1, 0, 0);
//	glRectf(-0.4, -0.4, 0.4, 0.4);


	Matrix m;
	TB.BuildRotationMatrix(m);

	glMultMatrixf((float *) m);


	glEnable(GL_LIGHTING);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, he_color);
	DrawHalfEdge(HE, GL_QUADS);
	DrawVertex(HE);

	glDisable(GL_LIGHTING);


	if (fl_get_button(Main_Form->button_neighbors)) {

		glLineWidth(3);
		glColor3f(1, 0, 0.5);
		DrawLinks(HE, 0);

		glLineWidth(2);

		if (flash_link)
			glColor3f(1, 0, 0.5);
		else
			glColor3f(0, 1, 0);
		DrawHalfEdge(HE->_link->_other_half, GL_LINE_LOOP);

		if (flash_other_half)
			glColor3f(1, 0, 0.5);
		else
			glColor3f(0, 1, 0);
		DrawHalfEdge(HE->_other_half, GL_LINE_LOOP);

		glLineWidth(1);
	}

	else {
		glLineWidth(3);
		glColor3f(1, 0, 0.5);
		DrawLinks(HE, 1);
		glLineWidth(1);
	}


	glDisable(GL_LIGHTING);
	SDO.DrawPainters();


	W.SwapBuffers();
}


void main(int argc, char *argv[]) {

	W.DoubleBuffer();
	W.ZBuffer();
	W.Open(ButtonMotionMask | ButtonReleaseMask);
	W.Clear();
	W.SwapBuffers();
	SetupView();


	SetupForms(argc, argv);

	fl_set_button(Main_Form->button_flash, 1);


	int res = 4;
	int seed = 1;
	float scale = 1.0f;
	float amp = 0.3f;
	int round = 0;
	JLString filename;



	CommandLine2 CL(argc, argv);

	CL >> "-res" >> res;
	CL >> "-scale" >> scale;
	CL >> "-amp" >> amp;
	CL >> "-seed" >> seed;
	CL >> "-round" >> round;
	CL >> "-load" >> filename;


	SDO.ReadWavefrontFile(filename);

	SDO.CalcCenterAndRadius(ObjCenter, radius);
	cerr << "radius: " << radius << endl;


	srand48(seed);

	SetupLighting();

	my_quad = gluNewQuadric();
	if (!my_quad) {
		cerr << "Error: could not allocate quadric" << endl;
		return;
	}


	M.Ortho2(-1, 1, -1, 1);


	HE = SDO.GetFirstHalfEdge();


	int done = 0;
	int redraw= 0;

	int zooming = 0;
	int zooming_y, last_zooming_y;

	int flashing_link = 0;
	int flashing_other_half = 0;

//	int flash = 1;


	while (!done) {

		int did_anything = 0;

		if (M.IsMouseDown()) {
			float x, y;
			M.GetFloatXY(x, y);
			TB.Drag(x, -y);
			redraw= 1;
		}
		else if (TB.IsSpinning()) {
			TB.Spin();
			redraw= 1;
		}


		int flash_link = 0;
		int flash_other_half = 0;

		if (flashing_link) {
			flashing_link--;
			flash_link = !(flashing_link & 1);
			if (!flashing_link) {
				HE = next_HE;
				flash_link = 0;
			}
			else
				sginap(10);
			redraw= 1;
		}

		if (flashing_other_half) {
			flashing_other_half--;
			flash_other_half = !(flashing_other_half & 1);
			if (!flashing_other_half) {
				HE = next_HE;
				flash_other_half = 0;
			}
			else
				sginap(10);
			redraw= 1;
		}


		if (redraw) {
			Redraw(flash_link, flash_other_half);
			redraw= 0;
			did_anything = 1;
		}


		XEvent event;
		float x, y;

		if (!flashing_link && !flashing_other_half) {


			FL_OBJECT *obj = fl_check_forms();

			if (obj) {

				if (obj == Main_Form->button_quit)
					done = 1;

				else if (obj == Main_Form->button_neighbors)
					redraw = 1;

				else if (obj == Main_Form->button_link) {
					// simulating split-edge!
					next_HE = HE->_link->_other_half;
					flashing_link = 10
						* fl_get_button(
								Main_Form->button_flash)
						* fl_get_button(
								Main_Form->button_neighbors) + 1;
					redraw = 1;
				}

				else if (obj == Main_Form->button_sym) {
					// simulating split-edge!
					next_HE = HE->_other_half;
					flashing_other_half = 10
						* fl_get_button(
								Main_Form->button_flash)
						* fl_get_button(
								Main_Form->button_neighbors) + 1;
					redraw = 1;
				}

			}

			
			while (W.EventPending()) {

				W.NextEvent(event);
				switch (event.type) {
					case KeyPress:
						switch (W.GetAscii(event)) {
							case 27:
								done = 1;
								break;
							case 'c':
								SDO.CatmullClark();
								HE = SDO.GetFirstHalfEdge();
								redraw = 1;
								break;
							case 'd':
								SDO.DooSabin();
								HE = SDO.GetFirstHalfEdge();
								redraw = 1;
								break;
							case 'r':
								TB.Reset();
								redraw= 1;
								break;

						}
						break;
					case Expose:
						redraw= 1;
						break;
					case ConfigureNotify:
						x = event.xconfigure.width;
						y = event.xconfigure.height;
						W.SetSize(x, y);
						redraw = 1;
						break;
					case ButtonPress:
						switch (event.xbutton.button) {
							case 3:
								M.MouseDown();
								M.Update(event.xbutton.x, event.xbutton.y);
								M.GetFloatXY(x, y);
								TB.Click(x, -y);
								break;
							case 2:
								zooming_y = last_zooming_y = event.xbutton.y;
								zooming = 1;
								break;
						}
						break;
					case MotionNotify:
						switch (event.xbutton.button) {
							case 3:
								if (M.IsMouseDown()) {
									M.Update(event.xbutton.x, event.xbutton.y);
									M.GetFloatXY(x, y);
								}
								break;
							case 2:
								if (zooming) {
									zooming_y = event.xbutton.y;
									Zoom(zooming_y - last_zooming_y);
									last_zooming_y = zooming_y;
									redraw = 1;
								}
								break;
						}
						break;
					case ButtonRelease:
						switch (event.xbutton.button) {
							case 3:
								M.MouseUp();
								break;
							case 2:
								zooming = 0;
								break;
						}
						break;
				}

				did_anything = 1;
			}
		}


		if (!did_anything && !M.IsMouseDown())
			sginap(10);
	}
}


