/*
 * This file is part of PlotLTR
 *
 * (c) 2002 Matthias Transier, Praktische Informatik IV, University of Mannheim
 *
 *   PlotLTR is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   PlotLTR is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with Foobar; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */                                                                             

#include <GL/glut.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <gts.h>

typedef enum{LOAD, PACKETS, DROPS} displaymode;

static GLfloat spin[3], dist;
static float max;
static int timeslot, slots, fieldsize, render, duration,
	   animation, writePPMs, useZ, drawNodes;
static displaymode mode = LOAD;

static GSList *tFrames = NULL;

void output(char *string)
{
	int len, i;

	glRasterPos2f(0, 0);
	len = (int) strlen(string);
	for (i = 0; i < len; i++)
		glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, string[i]);
}

void writePPM(int frame)
{
	int i, j, w, h;
	FILE *OUT;
	GLubyte *pixeldata;
	char filename[] = "frameXXXX.ppm";

	w = glutGet(GLUT_WINDOW_WIDTH);
	h = glutGet(GLUT_WINDOW_HEIGHT);
	pixeldata = (GLubyte*) calloc(w * h * 3, sizeof(GLubyte));
	glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, pixeldata);
	sprintf(filename, "frame%04d.ppm", frame);
	OUT = fopen(filename, "w");
	fprintf(OUT, "P6\n%d %d\n255\n", w, h);
	for (j = h - 1; j >= 0; j--)
		for (i = 0; i < w; i++)
			fprintf(OUT, "%c%c%c",
				pixeldata[3 * (w * j + i)],
				pixeldata[3 * (w * j + i) + 1],
				pixeldata[3 * (w * j + i) + 2]);
	fclose(OUT);
	free(pixeldata);
}

void animate(int value)
{
	if (value && timeslot + 1 < slots) {
		timeslot++;
		glutPostRedisplay();
		glutTimerFunc((double)duration / slots * 1000, animate, animation);
		if (writePPMs)
			glutTimerFunc(0, writePPM, timeslot);
	} else
		animation = 0;
}

void surface2strips(GPtrArray *vertices)
{
	int i;
	GSList *list = NULL;
	GtsTriangle *t;
	GtsSurface *surface;
	GtsVertex *v1, *v2, *v3;

	/* create triangle enclosing all the vertices (needed for delaunay) */
	for (i = 0; i < vertices->len; i++)
		list = g_slist_prepend(list, g_ptr_array_index(vertices, i));
	t = gts_triangle_enclosing(gts_triangle_class(), list, 1);
	g_slist_free(list);
 
	surface = gts_surface_new(gts_surface_class(), gts_face_class(),
					gts_edge_class(), gts_vertex_class());
	gts_surface_add_face(surface, gts_face_new(gts_face_class(), t->e1, t->e2, t->e3));

	/* add all vertices to the surface */
	for (i = 0; i < vertices->len; i++) {
		v1 = g_ptr_array_index(vertices, i);
		if (gts_delaunay_add_vertex(surface, v1, NULL) != NULL)
			printf("error adding node %d!\n", i);
	}
	g_ptr_array_free(vertices, TRUE);

	/* remove enclosing triangle */
	gts_triangle_vertices(t, &v1, &v2, &v3);
	gts_allow_floating_vertices = TRUE;
	gts_object_destroy(GTS_OBJECT(v1));
	gts_object_destroy(GTS_OBJECT(v2));
	gts_object_destroy(GTS_OBJECT(v3));
	gts_allow_floating_vertices = FALSE;

	if (gts_delaunay_check(surface) != NULL)
		printf("error: triangulation not delaunay-conform!\n");

	tFrames = g_slist_append(tFrames, gts_surface_strip(surface));
}

int init(char* filename)
{
	int i, xmax, ymax, pmax, dmax, p, d;
	GLfloat x, y, z;
	float t, slottime;
	FILE *DATA;
	GPtrArray *vertices, *packets, *drops;
	GSList *frames = NULL;

	dist = 100;
	timeslot = 0;
	render = GL_LINE_STRIP;

	animation = 0;
	writePPMs = 0;
	useZ = 1;
	drawNodes = 0;

	glClearColor(0.0, 0.0, 0.0, 0.0);
	glShadeModel(GL_SMOOTH);
	glEnable(GL_DEPTH_TEST);
	glPointSize(2);
	glEnable(GL_POINT_SMOOTH);

	DATA = fopen(filename, "r");
	if (fscanf(DATA, "# x=%d, y=%d, n=%d, stop=%d\n",
			&xmax, &ymax, &fieldsize, &duration) < 4) {
		printf("Error reading input file!\n");
		return 1;
	}

	max = 0;
	slottime = -1;

	do {
		fscanf(DATA, "%*s %f _%d_ (%f/%f) %f\n", &t, &i, &x, &y, &z);

		if (t != slottime) {
			vertices = g_ptr_array_new();
			g_ptr_array_set_size(vertices, fieldsize);
			frames = g_slist_append(frames, vertices);
			slottime = t;
		}

		g_ptr_array_index(vertices, i) =
			gts_vertex_new(gts_vertex_class(),
					x * 100. / xmax, 100. - y * 100. / ymax, z * 10.);

		if (GTS_POINT(g_ptr_array_index(vertices, i))->z > max)
			max = GTS_POINT(g_ptr_array_index(vertices, i))->z;

	} while (getc(DATA) != EOF);

	fclose(DATA);

	/* read node data (node pos_x/pos_y packets drops) */
	strcpy(rindex(filename, '.') + 1, "nod");
	if ((DATA = fopen(filename, "r")) > 0) {
		fscanf(DATA, "# pmax = %d, dmax = %d\n", &pmax, &dmax);
		if (dmax == 0)
		  dmax = 1;

		/* append one frame for packets and one for drops */
		packets = g_ptr_array_new();
		g_ptr_array_set_size(packets, fieldsize);
		frames = g_slist_append(frames, packets);
		drops = g_ptr_array_new();
		g_ptr_array_set_size(drops, fieldsize);
		frames = g_slist_append(frames, drops);
	
		while (fscanf(DATA, "%d %d %d\n", &i, &p, &d) == 3) {
			g_ptr_array_index(packets, i) =
				gts_vertex_new(gts_vertex_class(),
					GTS_POINT(g_ptr_array_index(vertices, i))->x,
					GTS_POINT(g_ptr_array_index(vertices, i))->y, log(1 + (exp(1) - 1) * p / pmax) * max);
			g_ptr_array_index(drops, i) =
				gts_vertex_new(gts_vertex_class(),
					GTS_POINT(g_ptr_array_index(vertices, i))->x,
					GTS_POINT(g_ptr_array_index(vertices, i))->y, log(1 + (exp(1) - 1) * d / dmax) * max);
		}

		fclose(DATA);
	}

	slots = g_slist_length(frames);

	/* convert surface of each frame to triangle strips */
	for (i = 0; i < slots; i++) {
		vertices = g_slist_nth_data(frames, i);
		surface2strips(vertices);

		printf("Processing frames: %d/%d (%d%%) done.\r",
			g_slist_length(tFrames), slots,
			(int)(100. * g_slist_length(tFrames) / slots + .5));
		fflush(stdout);
	}
	g_slist_free(frames);

	/* prevent packets and drops from being animated */
	slots -= 2;
	
	return 0;
}

void displayFrame(int frame)
{
	int i, j;
	GSList *tStripList, *tStrip;
	GtsTriangle *t;
	GtsVertex *v1, *v2, *v3;

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	glPushMatrix();
	glTranslatef(0, 0, -dist);
	glRotatef(spin[0], 1.0, 0.0, 0.0);
	glRotatef(spin[1], 0.0, 1.0, 0.0);
	glRotatef(spin[2], 0.0, 0.0, 1.0);
	glTranslatef(-100 / 2, -100 / 2, -(max / 2));

	tStripList = g_slist_nth_data(tFrames, frame);

	for (i = 0; i < g_slist_length(tStripList); i++) {
		tStrip = g_slist_nth_data(tStripList, i);

		for (j = 0; j < g_slist_length(tStrip); j++) {
			glBegin(render);

			t = g_slist_nth_data(tStrip, j);
			gts_triangle_vertices(t, &v1, &v2, &v3);

			if (GTS_POINT(v1)->z / max > 0.25)
				glColor3f(1, 1 - (GTS_POINT(v1)->z / max - 0.25) * 4 / 3, 0);
			else
				glColor3f(GTS_POINT(v1)->z / max * 4, 1, 0);
			glVertex3f(GTS_POINT(v1)->x, GTS_POINT(v1)->y, useZ ? GTS_POINT(v1)->z : 0);

			if (GTS_POINT(v2)->z / max > 0.25)
				glColor3f(1, 1 - (GTS_POINT(v2)->z / max - 0.25) * 4 / 3, 0);
			else
				glColor3f(GTS_POINT(v2)->z / max * 4, 1, 0);
			glVertex3f(GTS_POINT(v2)->x, GTS_POINT(v2)->y, useZ ? GTS_POINT(v2)->z : 0);

			if (GTS_POINT(v3)->z / max > 0.25)
				glColor3f(1, 1 - (GTS_POINT(v3)->z / max - 0.25) * 4 / 3, 0);
			else
				glColor3f(GTS_POINT(v3)->z / max * 4, 1, 0);
			glVertex3f(GTS_POINT(v3)->x, GTS_POINT(v3)->y, useZ ? GTS_POINT(v3)->z : 0);

			if (render == GL_LINE_STRIP) {
				if (GTS_POINT(v1)->z / max > 0.25)
					glColor3f(1, 1 - (GTS_POINT(v1)->z / max - 0.25) * 4 / 3, 0);
				else
					glColor3f(GTS_POINT(v1)->z / max * 4, 1, 0);
				glVertex3f(GTS_POINT(v1)->x, GTS_POINT(v1)->y, useZ ? GTS_POINT(v1)->z : 0);
			}

			glEnd();

			if (drawNodes) {
				glBegin(GL_LINES);
				glColor3f(1, 1, 1);

				glVertex3f(GTS_POINT(v1)->x, GTS_POINT(v1)->y,
						useZ ? GTS_POINT(v1)->z : 0);
				glVertex3f(GTS_POINT(v1)->x, GTS_POINT(v1)->y,
						(useZ ? GTS_POINT(v1)->z : 0) + 1);

				glVertex3f(GTS_POINT(v2)->x, GTS_POINT(v2)->y,
						useZ ? GTS_POINT(v2)->z : 0);
				glVertex3f(GTS_POINT(v2)->x, GTS_POINT(v2)->y,
						(useZ ? GTS_POINT(v2)->z : 0) + 1);

				glVertex3f(GTS_POINT(v3)->x, GTS_POINT(v3)->y,
						useZ ? GTS_POINT(v3)->z : 0);
				glVertex3f(GTS_POINT(v3)->x, GTS_POINT(v3)->y,
						(useZ ? GTS_POINT(v3)->z : 0) + 1);

				glEnd();
			}
		}
	}
	glPopMatrix();

	glTranslatef(-0.04, -0.04, -0.1);
	glColor3f(0, 0, 1);
}

void display(void)
{
	char message[10];

	switch (mode) {
		case LOAD:
			displayFrame(timeslot);
			sprintf(message, "%.1f s", (timeslot + 1.) * duration / slots);
			break;
		case PACKETS:
			displayFrame(slots);
			sprintf(message, "Packets");
			break;
		case DROPS:
			displayFrame(slots + 1);
			sprintf(message, "Drops");
			break;
	}
	output(message);

	glutSwapBuffers();
}

void reshape(int w, int h)
{
	glViewport(0, 0, (GLsizei) w, (GLsizei) h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(50, (GLfloat) w / (GLfloat) h, 0.1, 200);
	glMatrixMode(GL_MODELVIEW);
}

void keyboard(unsigned char key, int x, int y)
{
	switch (key) {
		case 'x':
		case 'y':
		case 'z':
			spin[key - 'x'] = (GLfloat) (((int)spin[key - 'x'] + 2) % 360);
			glutPostRedisplay();
			break;
		case 'X':
		case 'Y':
		case 'Z':
			spin[key - 'X'] = (GLfloat) (((int)spin[key - 'X'] - 2) % 360);
			glutPostRedisplay();
			break;
		case '+':
			if (timeslot + 1 < slots)
				timeslot++;
			glutPostRedisplay();
			break;
		case '-':
			if (timeslot > 0)
				timeslot--;
			glutPostRedisplay();
			break;
		case '1':
			timeslot = 0;
			glutPostRedisplay();
			break;
		case 's':
			writePPM(timeslot);
			break;
		case 'a':
			animation = 1 - animation;
			if (writePPMs)
				writePPM(timeslot);
			glutTimerFunc((double)duration / slots * 1000, animate, animation);
			break;
		case 'w':
			writePPMs = 1 - writePPMs;
			break;
		case 'p':
			render = GL_POINTS;
			glutPostRedisplay();
			break;
		case 'f':
			render = GL_LINE_STRIP;
			glutPostRedisplay();
			break;
		case 't':
			render = GL_TRIANGLES;
			glutPostRedisplay();
			break;
		case '2':
			useZ = 0;
			glutPostRedisplay();
			break;
		case '3':
			useZ = 1;
			glutPostRedisplay();
			break;
		case 'n':
			drawNodes = 1 - drawNodes;
			glutPostRedisplay();
			break;
		case 27:
		case 'q':
			exit(0);
	}
}

void mouse(int button, int state, int x, int y)
{
	switch(button) {
		case GLUT_LEFT_BUTTON:
			if (state == GLUT_DOWN) {
				dist--;
				glutPostRedisplay();
			}
			break;
		case GLUT_RIGHT_BUTTON:
				dist++;
				glutPostRedisplay();
			break;
	}
}

void menu(int item)
{
	switch (item) {
		case 0:
			exit(0);
			break;
		case 1:
			mode = LOAD;
			glutPostRedisplay();
			break;
		case 2:
			mode = PACKETS;
			glutPostRedisplay();
			break;
		case 3:
			mode = DROPS;
			glutPostRedisplay();
			break;
	}
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(500, 500);
	glutInitWindowPosition(100, 100);
	glutCreateWindow("Load on MAC layer");

	if (init(argv[1]) == 0) {

		glutDisplayFunc(display);
		glutReshapeFunc(reshape);
		glutKeyboardFunc(keyboard);
		glutMouseFunc(mouse);
		glutCreateMenu(menu);
		glutAddMenuEntry("Display load", 1);
		glutAddMenuEntry("Display packets", 2);
		glutAddMenuEntry("Display drops", 3);
		glutAddMenuEntry("Exit", 0);
		glutAttachMenu(GLUT_MIDDLE_BUTTON);

		glutMainLoop();
		return 0;
	} else {
		return 1;
	}
}

