#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;
int dual = 0;


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

int draw_neighbors = 0;
int draw_all_links = 0;


float vertex_size = 0;
int calc_new_vertex_size = 1;


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 + 10);
//	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);
}


void Rot(HalfEdge **he, int &dual) {

	if (dual)
		*he = (*he)->_other_half;
	dual = !dual;
}


HalfEdge* ONext(HalfEdge *he, int dual) {

	HalfEdge *he2 = he;

	if (dual) {
		he2 = he->_other_half->_link;
	}
	else {
		he2 = he->_link->_other_half;
		while (he2->_link->_other_half != he)
			he2 = he2->_link->_other_half;
		he2 = he2->_other_half;
	}

	return he2;
}



#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 DrawQuadEdge(HalfEdge *he, int dual, int gl_mode) {

	Vec4 Center, Forward, Right, Up;

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


	Up *= 0.125;
	Right *= 0.25;
	Forward *= 0.25;

	if (dual)
		Vec4FastAddScale(Center, Center, Right, 2);
	else
		Vec4FastAddScale(Center, Center, Forward, -2);

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

void DrawVertex(HalfEdge *he, int dual) {

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

	if (calc_new_vertex_size) {
		vertex_size = L.Length() * 0.05;
		calc_new_vertex_size = 0;
	}

	if (dual)
		DrawSphere(SDO.CalcAveragePoint(he->_other_half), vertex_size);
	else
		DrawSphere(he->_other_half->_v->Point(), vertex_size);
}


void DrawLink(HalfEdge *he1, HalfEdge *he2, int dual) {

	Vec4 C1, F1, R1, U1;
	Vec4 C2, F2, R2, U2;
	Vec4 V;

	SDO.CalcEdgeFrame(he1, C1, F1, R1, U1);
	SDO.CalcEdgeFrame(he2, C2, F2, R2, U2);


	R1 *= 0.25;
	F1 *= 0.25;
	if (dual)
		Vec4FastAddScale(C1, C1, R1, 2);
	else
		Vec4FastAddScale(C1, C1, F1, -2);

	R2 *= 0.25;
	F2 *= 0.25;
	if (dual)
		Vec4FastAddScale(C2, C2, R2, 2);
	else
		Vec4FastAddScale(C2, C2, F2, -2);


	Vec4 Ave;
	if (dual) {
		Ave = SDO.CalcAveragePoint(he1->_other_half);
		Vec3_FastWeightedSum(Ave, Ave, 0.25,
								  he1->_other_half->_v->Point(), 0.75);
	}
	else {
		Ave = SDO.CalcAveragePoint(he1);
		Vec3_FastWeightedSum(Ave, Ave, 0.75,
								  he1->_other_half->_v->Point(), 0.25);
	}

	Vec4FastAddScale(Ave, Ave, U1, 0.1);
	Vec4FastAddScale(Ave, Ave, U2, 0.1);

	DrawCurve(C1, Ave, C2);
}


void Redraw(int flash_link) {

	static GLfloat he_color[] = { 1.0, 0.0, 0.5, 1.0 };
	static GLfloat he_green_color[] = { 0.0, 1.0, 0.0, 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);

	DrawQuadEdge(HE, dual, GL_QUADS);
	DrawVertex(HE, dual);

	glDisable(GL_LIGHTING);

	glColor3f(1.0, 0, 0.5);
	glLineWidth(2);
	DrawQuadEdge(HE, !dual, GL_LINE_STRIP);
	DrawQuadEdge(HE->_other_half, dual, GL_LINE_STRIP);
	DrawQuadEdge(HE->_other_half, !dual, GL_LINE_STRIP);
	glLineWidth(1);


	if (draw_neighbors) {

		if (flash_link)
			glMaterialfv(GL_FRONT, GL_DIFFUSE, he_color);
		else
			glMaterialfv(GL_FRONT, GL_DIFFUSE, he_green_color);
		
		HalfEdge *HE2 = ONext(HE, dual);


		glEnable(GL_LIGHTING);
		DrawQuadEdge(HE2, dual, GL_QUADS);

		glDisable(GL_LIGHTING);

		glLineWidth(2);
		glColor3f(0, 1, 0);
		DrawQuadEdge(HE2, !dual, GL_LINE_STRIP);
		DrawQuadEdge(HE2->_other_half, dual, GL_LINE_STRIP);
		DrawQuadEdge(HE2->_other_half, !dual, GL_LINE_STRIP);


		glLineWidth(3);
		glColor3f(1, 0, 0.5);
		DrawLink(HE, HE2, dual);

		glLineWidth(1);
	}

	if (draw_all_links) {

		glMaterialfv(GL_FRONT, GL_DIFFUSE, he_green_color);
		

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

			if ((i!=0) || (!draw_neighbors)) {

				HalfEdge *HE2 = ONext(HE, dual);

				glEnable(GL_LIGHTING);
				DrawQuadEdge(HE2, dual, GL_QUADS);

				glDisable(GL_LIGHTING);

				glLineWidth(2);
				glColor3f(0, 1, 0);
				DrawQuadEdge(HE2, !dual, GL_LINE_STRIP);
				DrawQuadEdge(HE2->_other_half, dual, GL_LINE_STRIP);
				DrawQuadEdge(HE2->_other_half, !dual, GL_LINE_STRIP);


				glLineWidth(3);
				glColor3f(0, 1, 0);
				DrawLink(HE, HE2, dual);

				glLineWidth(1);
			}

			Rot(&HE, dual);
		}
	}


	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);



	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 flash = 1;


	fl_set_button(Main_Form->button_flash, flash);
	fl_set_button(Main_Form->button_neighbors, draw_neighbors);
	fl_set_button(Main_Form->button_show_all_links, draw_all_links);


	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;

		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 (redraw) {
			Redraw(flash_link);
			redraw= 0;
			did_anything = 1;
		}


		XEvent event;
		float x, y;

		if (!flashing_link) {


			FL_OBJECT *obj = fl_check_forms();

			if (obj) {

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

				else if (obj == Main_Form->button_flash) {
					flash = fl_get_button(Main_Form->button_flash);
					redraw = 1;
				}
				else if (obj == Main_Form->button_neighbors) {
					draw_neighbors = fl_get_button(Main_Form->button_neighbors);
					redraw = 1;
				}
				else if (obj == Main_Form->button_show_all_links) {
					draw_all_links = fl_get_button(Main_Form->
														button_show_all_links);
					redraw = 1;
				}

				else if (obj == Main_Form->button_rot) {
					Rot(&HE, dual);
					calc_new_vertex_size = 1;
					redraw= 1;
				}
				else if (obj == Main_Form->button_onext) {
					next_HE = ONext(HE, dual);
					flashing_link = 10*flash*draw_neighbors+1;
					redraw= 1;
				}
				else if (obj == Main_Form->button_sym) {
					HE = HE->_other_half;
					calc_new_vertex_size = 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();
								calc_new_vertex_size = 1;
								redraw = 1;
								break;
							case 'd':
								SDO.DooSabin();
								HE = SDO.GetFirstHalfEdge();
								calc_new_vertex_size = 1;
								redraw = 1;
								break;



							case 'f':
								flash = !flash;
								fl_set_button(Main_Form->button_flash, flash);
								break;

							case 'n':
								draw_neighbors = !draw_neighbors;
								fl_set_button(Main_Form->button_neighbors,
											  draw_neighbors);
								redraw = 1;
								break;
							
							case 'o':
								next_HE = ONext(HE, dual);
								flashing_link = 10*flash*draw_neighbors+1;
								redraw= 1;
								break;

							case 's':
								HE = HE->_other_half;
								calc_new_vertex_size = 1;
								redraw= 1;
								break;

							case 'r':
								Rot(&HE, dual);
								calc_new_vertex_size = 1;
								redraw= 1;
								break;


		/*
							case 's':
								cerr << "saving to 'cell.obj'..." << endl;
								C.SaveWavefront("cell.obj");
								cerr << "done" << endl;
								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);
	}
}


