#include <stdlib.h>
#include "GL/glew.h"
#include "GL/glut.h"
#include "IL/il.h"
#include <math.h>
#include "Camera.h"
#include "Material.h"
#include "Asteroid.h"
#include <time.h>
#include "Input.h"
#include "Player.h"
#include "AsteroidsField.h"
#include "TextureObject.h"
#include "Planet.h"
#include "Skybox.h"

 
/*	
	Objects used for this small demo
	because of the small game there is no 
	special system to handle game object
*/
Material* asteroidMaterial;
TextureObject* asteroidTex;
Camera* cam;
AsteroidsField* astField;
Planet* bigPlanet;
Skybox* sky;
Input* keyboard;
Player* you;

// Light position
static float position0[] = { -50, 50, 80, 0.0f };

//--------------------------------------------------------------------------------------------------

// timer function that is called
// here the update pass of the game is full filed
void timer(int value) 
{
	glutPostRedisplay();
	glutTimerFunc(20, timer, 1);

	// call updates of the elements
	bigPlanet->update();
	sky->update();
	astField->update( you );

	// control of the spectator camera (if space bar is hit) 
	if(!you->isControled())
	{
		// Move
		if(keyboard->getKey('s'))
			cam->move(0,0,-1);
		if(keyboard->getKey('w'))
			cam->move(0,0,1);
		if(keyboard->getKey('a'))
			cam->move(-1,0,0);
		if(keyboard->getKey('d'))
			cam->move(1,0,0);
		if(keyboard->getKey('r'))
			cam->move(0,1,0);
		if(keyboard->getKey('f'))
			cam->move(0,-1,0);

		// Rotate
		if(keyboard->getKey('q'))
			cam->rotateY(-2);
		if(keyboard->getKey('e'))
			cam->rotateY(2);
		if(keyboard->getKey('2'))
			cam->rotateX(-2);
		if(keyboard->getKey('3'))
			cam->rotateX(2);
		if(keyboard->getKey('t'))
			cam->rotateZ(-2);
		if(keyboard->getKey('g'))
			cam->rotateZ(2);
	} 
	// if the space bar was not hit (or again) the player controls the ship
	else
	{
		you->update();
		astField->setPlayerSpeed(you->getSpeed());

		// collision Player-Asteroids
		if(astField->collisionTestWithPlayer(you->getBoundingVolume(), -20, 0))
			you->setSpeed(0);
	}
			
}

//--------------------------------------------------------------------------------------------------

// is called when a pushed key is recognized 
void keyPushed(unsigned char button, int x, int y) 
{
	// exit the game with escape
	if(button == 27)
		exit(0);

	// reach to input class
	keyboard->keyPushed(button, x, y);

	// switch to spectator mode with space bar (no game logic, so it's separated)
	if(you->isControled())
	{
		if(keyboard->getKey(' '))
			you->stopControl();
	} else {
		if(keyboard->getKey(' '))
			you->startControl();
	}
}
//--------------------------------------------------------------------------------------------------

// is called when a released key is recognized 
void keyReleased(unsigned char button, int x, int y) 
{
	// reach to input class
	keyboard->keyReleased(button, x, y);
}
//--------------------------------------------------------------------------------------------------
void specialKeyPushed(int key, int x, int y)
{
	// reach to input class
	keyboard->keySpecialPushed(key, x, y);
}
//--------------------------------------------------------------------------------------------------
void specialKeyReleased(int key, int x, int y)
{
	// reach to input class
	keyboard->keySpecialReleased(key, x, y);
}
//--------------------------------------------------------------------------------------------------

// this method is called each frame an handles all the draw calles
void display(void) {
	
	// clear buffers and set matrix mode
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// set camera orientation
	cam->render();
	
	// draw sky box (in this demo the sky box has only a front image
	sky->render();

	// draw the big planet at the bottom of the scene
	bigPlanet->render();
	
	// set the light (is static, so this it's not necessary)
	glLightfv(GL_LIGHT0, GL_POSITION, position0);

	// draw asteroids
		// activate the material for asteroids (this will be used for drawing until an other material is activated)
		asteroidMaterial->activate();	

		// to have a correct alpha blending the asteroid field is splitted into two parts
		
		astField->renderBeforePlayer();	

		you->render(); // draw the players ship

		astField->renderBehindPlayer();

	// switch front and backbuffer
	glutSwapBuffers();
}

//--------------------------------------------------------------------------------------------------

// Reshape-Function is called after the window size is changed
void reshape(int width, int height) {
	glViewport(0, 0, width, height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0, (float)width / (float)height, 0.05, 1000.0);
}

//--------------------------------------------------------------------------------------------------

// main method and entry point of the program
int main(int argc, char** argv) {

	// init input class
	keyboard = new Input();

	// init glut and register the functions
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(800, 600);
 	glutInitWindowPosition(0, 0);
	glutCreateWindow("Wings");
	glutReshapeFunc(reshape);
	glutDisplayFunc(display);
	glutKeyboardFunc(keyPushed);
	glutKeyboardUpFunc(keyReleased);
	glutSpecialFunc(specialKeyPushed);
	glutSpecialUpFunc(specialKeyReleased);
	
	glEnable(GL_DEPTH_TEST);
	glShadeModel(GL_SMOOTH);

	// initialize Devil, library for texture loading
	ilInit();


	// Nice Perspective Calculations
	glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);
	// Nice Point Smoothing	
	glHint(GL_POINT_SMOOTH_HINT,GL_NICEST);				

	glEnable(GL_NORMALIZE);


	// fog of depth
	//----------------------
	GLfloat fogColor[4] = {0.02, 0.02, 0.02, 1};
	glEnable (GL_FOG);
	//Now we set the fog mode, here i have set it to GL_EXP2, which is quite nice looking.
	glFogi (GL_FOG_MODE, GL_EXP2);
	//Here we set the actual color of our fog.
		glFogfv (GL_FOG_COLOR, fogColor);
	//Now the density.
		glFogf (GL_FOG_DENSITY, 0.003f);
	//And here I have set it up to look the nicest. In large projects, this may slow
		//down you application.
		glHint (GL_FOG_HINT, GL_NICEST);

	glDepthFunc(GL_LEQUAL);


	// init glew
	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
		/* Problem: glewInit failed, something is seriously wrong. */
		fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
	}
	if (glewGetExtension("GL_ARB_vertex_buffer_objects"))
	{
		fprintf(stdout, "Check:  vertex buffer objects\n");
	}
	fprintf(stdout, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));

	
	// clear buffers
	glClearColor(0.0, 0.0, 0.0, 1.0);
	glClearDepth(1.0);

	// Light
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	//Create light components for GL_LIGHT0
	float ambientLight0[] = { 0.0f, 0.0f, 0.0f, 1.0f };
	float diffuseLight0[] = { 1.0f, 1.0f, 1.0f, 1.0f };
	float specularLight0[] = { 1.0f, 1.0f, 1.0f, 1.0f };
	// Assign created components to GL_LIGHT0
	glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight0);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight0);
	glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight0);

	// set up alpha blending
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	
	// Material and TextureEnvironment
	glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE | GL_SPECULAR | GL_SHININESS);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	

	// turn on the random generator
	srand ( time(NULL) );


	// create our objects
	asteroidMaterial = new Material(Vec4f(0.7, 0.55, 0.4),  Vec4f(8, 0.6, 0.5), Vec4f(0.2, 0.2, 0.2), 64);
	asteroidTex = new TextureObject("Textures/asteroid_v8.jpg");

	cam = new Camera();

	you = new Player(cam, keyboard);
	you->spawn(Vec3f(0,0,-20),Vec3f(0,0,-1));
	you->startControl();

	astField = new AsteroidsField(	Vec3f(0,0,-250),	// Position
									100,100,500,		// hight, width, length
									250, //150,			// Number
									20,					// Variation ( Number of different models)
									10,2,				// max, min Size
									asteroidMaterial,
									asteroidTex,
									cam);	

	sky = new Skybox(cam, 1000);
	bigPlanet = new Planet( Vec3f(0, -860, -200), 800);

	glutTimerFunc(20, timer, 1);
	glutMainLoop();
	
	return(0);
}
