/*
 * Client.cpp
 *
 *  Created on: 14.01.2011
 *      Author: gmueller
 */

#include "Client.h"
#include "Network.h"
#include "Explosion.h"
#include "Time.h"

#include <GL/glfw.h>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <sstream>

GLUquadricObj *quadratic;
Explosion explosion;

void setup_light() {
	GLfloat LightAmbient[] = { 0.5f, 0.5f, 0.5f, 1.0f };
	GLfloat LightDiffuse[] = { 0.5f, 0.5f, 0.5f, 1.0f };
	GLfloat LightPosition[] = { 1.0f, 1.0f, 1.0f, 0.0f };

	// setup directional light
	glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmbient);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiffuse);
	glLightfv(GL_LIGHT0, GL_POSITION, LightPosition);
	glEnable( GL_LIGHT0);

	glEnable( GL_LIGHTING);
}

void setup_opengl() {

	// Open OpenGL window
	if (!glfwOpenWindow(640, 480, 0, 0, 0, 0, 0, 0, GLFW_WINDOW)) {
		fprintf(stderr, "Failed to open GLFW window\n");
		glfwTerminate();
		exit(EXIT_FAILURE);
	}
	//glfwDisable(GLFW_MOUSE_CURSOR);
	glfwSetWindowTitle("Gremlin Lan Party Game");
	// Enable sticky keys
	glfwEnable(GLFW_STICKY_KEYS);

	// general settings
	glShadeModel( GL_SMOOTH);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	glEnable( GL_CULL_FACE);

	// setup depth buffer
	glClearDepth(1.0f);
	glEnable( GL_DEPTH_TEST);
	glDepthFunc( GL_LEQUAL);

	// Enable vertical sync (on cards that support it)
	glfwSwapInterval(0);
}

void draw_team(team_t *team) {
	size_t i = 0;
	glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, team->color);
	glMatrixMode( GL_MODELVIEW);
	glPushMatrix();
	glTranslated(team->x, team->y, team->z);
	gluSphere(quadratic, 50.f, 32, 32);
	glPopMatrix();
}

void draw_player(player_t *player) {
	if (player->status == 0)
		return;
	glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, player->team->color);
	glMatrixMode( GL_MODELVIEW);
	glPushMatrix();
	glTranslated(player->x, player->y, player->z);
	gluSphere(quadratic, 10.f, 32, 32);
	glPopMatrix();
}

void draw_bomb(bomb_t *bomb) {
	if (bomb->status != 1)
		return;
	GLfloat red[] = { 1.0f, 0.0f, 0.0f, 1.0f };
	glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, red);
	glMatrixMode( GL_MODELVIEW);
	glPushMatrix();
	glTranslated(bomb->x, bomb->y, bomb->z);
	gluSphere(quadratic, 3.f, 4, 4);
	glPopMatrix();
}
void draw_point(point_t *point) {
	if (point->status == 0)
		return;
	GLfloat red[] = { 0.0f, 0.0f, 1.0f, 1.0f };
	glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, red);
	glMatrixMode( GL_MODELVIEW);
	glPushMatrix();
	glTranslated(point->x, point->y, point->z);
	gluSphere(quadratic, 3.f, 12, 12);
	glPopMatrix();
}

void setup_explosion() {

	// Loads particle texture
	GLuint textures[5];

	glGenTextures(5, textures);

	glBindTexture(GL_TEXTURE_2D, textures[0]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glfwLoadTexture2D("data/explosion/explosion.tga", GLFW_ALPHA_MAP_BIT);

	glBindTexture(GL_TEXTURE_2D, textures[1]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glfwLoadTexture2D("data/explosion/flash.tga", 0);

	glBindTexture(GL_TEXTURE_2D, textures[2]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glfwLoadTexture2D("data/explosion/spark.tga", 0);

	glBindTexture(GL_TEXTURE_2D, textures[3]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glfwLoadTexture2D("data/explosion/point.tga", GLFW_ALPHA_MAP_BIT);

	glBindTexture(GL_TEXTURE_2D, textures[4]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glfwLoadTexture2D("data/explosion/wave.tga", 0);

	explosion.initialize(textures[0], textures[1], textures[2], textures[3],
			textures[4]);
}

void explosion_callback(double x, double y, double z, void *data) {
	explosion.add(x, y, z);
}

GLuint wallTex = 0;

void draw_box() {

	if (wallTex == 0) {
		glGenTextures(1, &wallTex);
		glBindTexture(GL_TEXTURE_2D, wallTex);
		glEnable( GL_TEXTURE_2D);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
				GL_LINEAR_MIPMAP_LINEAR);
		glfwLoadTexture2D("data/wall.tga", GLFW_BUILD_MIPMAPS_BIT);
	} else {
		glBindTexture(GL_TEXTURE_2D, wallTex);
		glEnable( GL_TEXTURE_2D);
	}

	GLfloat red[] = { 1.0f, 1.0f, 1.0f, 1.0f };
	glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, red);

	// Enable/Disable features
	glPushAttrib( GL_ENABLE_BIT);
	glEnable( GL_TEXTURE_2D);
	//glDisable( GL_DEPTH_TEST);
	//	glEnable( GL_LIGHTING);
	//	glDisable( GL_BLEND);
	glMatrixMode( GL_MODELVIEW);
	glPushMatrix();
	glTranslated(2000.0, 0.0, 0.0);
	//glScaled(5000.0f, 5000.0f, 5000.0f);
	float s = 3000.0f, t = 10.0f;
	// Just in case we set all vertices to white.
	glColor4f(1, 1, 1, 1);

	// Render the front quad
	glBegin( GL_QUADS);
	glTexCoord2f(0, t);
	glNormal3f(0.0, 0.0, 1.0);
	glVertex3f(-s, s, -s);

	glTexCoord2f(0, 0);
	glNormal3f(0.0, 0.0, 1.0);
	glVertex3f(-s, -s, -s);

	glTexCoord2f(t, 0);
	glNormal3f(0.0, 0.0, 1.0);
	glVertex3f(s, -s, -s);

	glTexCoord2f(t, t);
	glNormal3f(0.0, 0.0, 1.0);
	glVertex3f(s, s, -s);
	glEnd();

	// Render the left quad
	glBegin(GL_QUADS);
	glTexCoord2f(0, 0);
	glVertex3f(s, -s, s);

	glTexCoord2f(0, t);
	glVertex3f(s, s, s);

	glTexCoord2f(t, t);
	glVertex3f(s, s, -s);

	glTexCoord2f(t, 0);
	glVertex3f(s, -s, -s);
	glEnd();

	// Render the back quad
	//	glBindTexture(GL_TEXTURE_2D, _skybox[2]);
	glBegin(GL_QUADS);
	glTexCoord2f(0, 0);
	glVertex3f(-s, -s, s);
	glTexCoord2f(0, t);
	glVertex3f(-s, s, s);
	glTexCoord2f(t, t);
	glVertex3f(s, s, s);
	glTexCoord2f(t, 0);
	glVertex3f(s, -s, s);

	glEnd();

	// Render the right quad
	//	glBindTexture(GL_TEXTURE_2D, _skybox[3]);
	glBegin(GL_QUADS);
	glTexCoord2f(0, 0);
	glVertex3f(-s, -s, -s);
	glTexCoord2f(0, t);
	glVertex3f(-s, s, -s);
	glTexCoord2f(t, t);
	glVertex3f(-s, s, s);
	glTexCoord2f(t, 0);
	glVertex3f(-s, -s, s);
	glEnd();

	// Render the top quad
	//glBindTexture(GL_TEXTURE_2D, _skybox[4]);
	glBegin(GL_QUADS);
	glTexCoord2f(0, t);
	glVertex3f(-s, s, -s);

	glTexCoord2f(t, t);
	glVertex3f(s, s, -s);

	glTexCoord2f(t, 0);
	glVertex3f(s, s, s);

	glTexCoord2f(0, 0);
	glVertex3f(-s, s, s);

	glEnd();

	// Render the bottom quad
	//glBindTexture(GL_TEXTURE_2D, _skybox[5]);
	glBegin(GL_QUADS);
	glTexCoord2f(0, 0);
	glVertex3f(-s, -s, -s);
	glTexCoord2f(0, t);
	glVertex3f(-s, -s, s);
	glTexCoord2f(t, t);
	glVertex3f(s, -s, s);
	glTexCoord2f(t, 0);
	glVertex3f(s, -s, -s);
	glEnd();

	// Restore enable bits and matrix
	glPopAttrib();
	glPopMatrix();
}

void Client::accelerate(double x, double y, double z) {
	if (game.local_player == 0)
		return;
	player_accelerate_message_t msg;
	msg.msg_id = MESSAGE_PLAYER_ACCELERATE;
	msg.player_id = game.local_player->id;
	msg.x = x;
	msg.y = y;
	msg.z = z;
	network.sendMessage((uint8_t*) &msg, sizeof(msg));
}

void Client::drop_bomb(double rx, double ry, double rz, double ttl) {
	bomb_drop_meesage_t msg;
	msg.msg_id = MESSAGE_BOMB_DROP;
	msg.x = game.local_player->x + rx * 20;
	msg.y = game.local_player->y + ry * 20;
	msg.z = game.local_player->z + rz * 20;
	msg.vx = game.local_player->vx + rx * 100;
	msg.vy = game.local_player->vy + ry * 100;
	msg.vz = game.local_player->vz + rz * 100;
	msg.ttl = ttl;
	network.sendMessage((uint8_t*) &msg, sizeof(msg));
}

void Client::loadConsoleFont() {
	GLuint font_id = 0;

	glGenTextures(1, &font_id);
	glBindTexture(GL_TEXTURE_2D, font_id);

	// Set texture parameters
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	// Upload texture from file to texture memory
	glfwLoadTexture2D("data/fonts/console.tga", 0);
	oglf_load(&font, "data/fonts/console.fnt", font_id);
}

void Client::prepareFrame(double rx, double ry, double rz) {
	// Get window size (may be different than the requested size)
	glfwGetWindowSize(&width, &height);
	height = height > 0 ? height : 1;

	// Set viewport
	glViewport(0, 0, width, height);

	// Clear color buffer
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Select and setup the projection matrix
	glMatrixMode( GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0f, (GLfloat) width / (GLfloat) height, 1.0f, 10000.0f);

	// Select and setup the modelview matrix
	glMatrixMode( GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(camX, camY, camZ, camX + rx * 10.0f, camY + ry * 10.0f, camZ + rz
			* 10.0f, 0.0f, 1.0f, 0.0f);

}

void Client::initialize(Arguments &arg) {
	Application::initialize(arg);

	// Initialise GLFW
	if (!glfwInit()) {
		fprintf(stderr, "Failed to initialize GLFW\n");
		exit(EXIT_FAILURE);
	}

	game_setup_explosion_callback(&game, explosion_callback, 0);
	setup_opengl();
	setup_explosion();
	quadratic = gluNewQuadric();
	gluQuadricNormals(quadratic, GLU_SMOOTH);
	gluQuadricTexture(quadratic, GL_TRUE);

	if (isMaster()) {
		team_t *team = game_team(&game, 0);
		game.local_player = game_spawn_player(&game, team);
	}

	size_t i;
	last_bomb = time - 5.;

	glfwGetMousePos(&last_x, &last_y);
	accelerate_schudule.setExact(true);
	accelerate_schudule.setInterval(0.05);

	loadConsoleFont();
}

void Client::update() {

	Application::update();

	int x, y;
	glfwGetMousePos(&x, &y);
	if (glfwGetMouseButton(GLFW_MOUSE_BUTTON_2) == GLFW_PRESS) {
		phi += (x - last_x) * 0.01;
		theta += (y - last_y) * -0.01;
		if (theta > 1.5)
			theta = 1.5;
		if (theta < -1.5)
			theta = -1.5;
	}
	last_x = x;
	last_y = y;
	double rx = cos(phi) * cos(theta);
	double ry = sin(theta);
	double rz = sin(phi) * cos(theta);

	if (accelerate_schudule.next(time)) {
		double t = accelerate_schudule.getInterval();
		double v = 50.0 * t;
		if (glfwGetKey('W')) {
			accelerate(rx * v, ry * v, rz * v);
		} else if (glfwGetKey('S')) {
			accelerate(rx * -v, ry * -v, rz * -v);
		}

		if (glfwGetKey('A')) {
			accelerate(rz * v, 0, -rx * v);
		} else if (glfwGetKey('D')) {
			accelerate(-rz * v, 0, rx * v);
		}

		if (glfwGetKey(GLFW_KEY_SPACE)) {
			accelerate(game.local_player->vx * -0.1, game.local_player->vy
					* -0.1, game.local_player->vz * -0.1);
		}
	}
	if (glfwGetKey(GLFW_KEY_LCTRL)) {
		if (time - last_bomb > 1.0) {
			last_bomb = time;
			drop_bomb(rx, ry, rz, 5.0);
		}
	}

	if (game.local_player) {
		camX = game.local_player->x;
		camY = game.local_player->y;
		camZ = game.local_player->z;
	} else {
		camX = 1000.0;
		camY = 1000.0;
		camZ = 1000.0;
	}
	prepareFrame(rx, ry, rz);
	setup_light();
	glEnable( GL_LIGHT0);
	glEnable( GL_LIGHTING);
	glEnable( GL_CULL_FACE);
	glDisable( GL_TEXTURE_2D);
	for (size_t i = 0; i < GAME_TEAM_COUNT; i++)
		draw_team(&game.team[i]);

	for (size_t i = 0; i < GAME_PLAYER_COUNT; i++)
		draw_player(&game.player[i]);

	for (size_t i = 0; i < GAME_BOMB_COUNT; i++)
		draw_bomb(&game.bomb[i]);

	for (size_t i = 0; i < GAME_POINT_COUNT; i++)
		draw_point(&game.point[i]);

	draw_box();

	glDisable(GL_LIGHT0);
	glDisable(GL_LIGHTING);
	glDisable(GL_CULL_FACE);
	explosion.update(dt * 1000.0, camX, camY, camZ);
	explosion.render();
	oglf_begin(&font, width, height);
	size_t fy = 1;
	if (game.local_player) {
		std::stringstream sstr;
		sstr << "Points: " << game.local_player->points;
		oglf_print(&font, 10, 25 * fy++, sstr.str().c_str());

	}

	for (size_t i = 0; i < GAME_TEAM_COUNT; i++) {
		std::stringstream sstr;
		if (game.local_player && &game.team[i] == game.local_player->team) {
			sstr << "Team " << i << " (yours) : " << game.team[i].points;
		} else {
			sstr << "Team " << i << " (other) : " << game.team[i].points;
		}
		oglf_print(&font, 10, 25 * fy++, sstr.str().c_str());
	}

	{
		std::stringstream sstr;
		sstr << "FPS: " << (int) (1 / dt) << " Time: " << floor(dt * 10000.0
				+ 0.5) / 10.0;
		oglf_print(&font, 10, 25 * fy++, sstr.str().c_str());
	}

	oglf_end();

	// Swap buffers
	glfwSwapBuffers();

	// Check if the ESC key was pressed or the window was closed
	if (glfwGetKey(GLFW_KEY_ESC))
		stop();

	if (glfwGetWindowParam(GLFW_OPENED) != GL_TRUE)
		stop();
}

void Client::shutdown() {

	Application::shutdown();

	gluDeleteQuadric(quadratic);
	oglf_destroy(&font);
	glfwTerminate();
}
