#include "include.h"


Dust * randomDust(Nave * nave) {
	return new Dust(Vector3D(rand() % 40 - 20 + nave->pos.x + nave->front.x * nave->vel * 20, rand() % 40 - 20 + nave->pos.y + nave->front.y * nave->vel * 20, rand() % 40 - 20 + nave->pos.z + nave->front.z * nave->vel * 20));
}

void siguienteNivel(Juego * actual) {
	Juego * j = new Juego(actual->puntos, actual->nave, actual->nivel + 1);
	setJuego(j);
	goTo(j);
	delete actual;
}

void perdiste(Juego * actual) {
	setJuego(new Juego());
	goTo(getMenu());
	delete actual;
}

void aplicarConfiguracionGeneral() {
		//Seteo Wireframe
	if (Config::wireframe == WF_PUNTOS)
		glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
	else if (Config::wireframe == WF_LINEAS)
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	else
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	
	//Seteo Facetado
	if (Config::facetado)
		glShadeModel(GL_SMOOTH);
	else
		glShadeModel(GL_FLAT);

	//Seteo aplicacion de Texturas
	if (Config::textura)
		 glEnable(GL_TEXTURE_2D);
	else
		glDisable(GL_TEXTURE_2D);
}

void aplicarConfiguracionModelo(Model * m) {
	if (Config::facetado) {
		// Seteo aplicacion de Materiales con facetado
		if (Config::material) {
			m->setMode(GLM_SMOOTH | GLM_MATERIAL);
		} else {
			m->setMode(GLM_SMOOTH);
		}
	} else {
		// Seteo aplicacion de Materiales sin facetado
		if (Config::material) {
			m->setMode(GLM_FLAT | GLM_MATERIAL);
		} else {
			m->setMode(GLM_FLAT);
		}
	}
}

void aplicarIluminacion() {
	// Configuro la iluminación
	GLfloat lmodel_ambient[] = { 0.3, 0.3, 0.3, 1.0 };
	
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
	glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1);

	GLfloat luz_color[] = {1.0, 1.0, 1.0, 1.0};
	switch(Config::luzColor){
		case COLOR_ROJO:
			luz_color[0] = 1;
			luz_color[1] = 0.5;
			luz_color[2] = 0.5;
			luz_color[3] = 1.0;
			break;
		case COLOR_VERDE:
			luz_color[0] = 0.5;
			luz_color[1] = 1;
			luz_color[2] = 0.5;
			luz_color[3] = 1.0;
			break;
		case COLOR_AZUL:
			luz_color[0] = 0.5;
			luz_color[1] = 0.5;
			luz_color[2] = 1;
			luz_color[3] = 1.0f;
			break;
		default: // blanco
			luz_color[0] = 1.0;
			luz_color[1] = 1.0;
			luz_color[2] = 1.0;
			luz_color[3] = 1.0;
			break;
	}

	GLfloat luz_amb[] = {1.0f, 1.0f, 1.0f, 1.0f};
    GLfloat luz_dif[] = {1.0f ,1.0f ,1.0f ,1.0f};
    GLfloat luz_esp[] = {1.0f, 1.0f, 1.0f, 1.0f};
	
	const GLfloat exp[] = { 1.0};
	GLfloat luz_pos[] = {0, -1, 0, 0};

	// Luz Solar
	glLightfv(GL_LIGHT0, GL_COLOR, luz_color);
	glLightfv(GL_LIGHT0, GL_AMBIENT, luz_amb);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, luz_dif);
	glLightfv(GL_LIGHT0, GL_SPECULAR, luz_esp);
	glLightfv(GL_LIGHT0, GL_POSITION, luz_pos);
}

void inicializar(Juego * yo) {
	yo->worldW = 40;
	yo->cantidadAsteroides = yo->nivel + 1;
	yo->asteroides = new Lista();
	for (int i=0; i < yo->cantidadAsteroides; i++) {
		Asteroide * a = new Asteroide();
		yo->asteroides->add(a);
	}
	yo->pausa = false;
	yo->balas = new Lista();
	yo->dust = new Lista();
	for (int i=0; i < 100; i++) {
		yo->dust->add(randomDust(yo->nave));
	}
	yo->vista = VISTA_ADENTRO;
	yo->skybox[0] = loadTexture("texturas/skybox_back.jpg");
	yo->skybox[1] = loadTexture("texturas/skybox_left.jpg");
	yo->skybox[2] = loadTexture("texturas/skybox_front.jpg");
	yo->skybox[3] = loadTexture("texturas/skybox_right.jpg");
	yo->skybox[4] = loadTexture("texturas/skybox_top.jpg");
	yo->skybox[5] = loadTexture("texturas/skybox_bottom.jpg");
	yo->mira = loadTexture("texturas/mira.png");
	yo->mouseX = yo->mouseY = 0;
}

void dibujarSkyBox(GLuint * skybox, double x, double y, double z, double size) {
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_TEXTURE_2D);
	
	glColor4f(1, 1, 1, 1);
	
	glPushMatrix();
	glTranslated(x - size/2, y + size/2, z - size/2);
	glScaled(size, size, size);

	glBindTexture(GL_TEXTURE_2D, skybox[0]);
	glBegin(GL_POLYGON);
		glTexCoord2f(0, 0); glVertex3f(0, 0, 0);
		glTexCoord2f(0, 1); glVertex3f(0, 0, 1);
		glTexCoord2f(1, 1); glVertex3f(1, 0, 1);
		glTexCoord2f(1, 0); glVertex3f(1, 0, 0);
	glEnd();
	glBindTexture(GL_TEXTURE_2D, skybox[3]);
	glBegin(GL_QUADS);
		glTexCoord2f(1, 0); glVertex3f(0, 0, 0);
		glTexCoord2f(1, 1); glVertex3f(0, 0, 1);
		glTexCoord2f(0, 1); glVertex3f(0, -1, 1);
		glTexCoord2f(0, 0); glVertex3f(0, -1, 0);
	glEnd();
	glBindTexture(GL_TEXTURE_2D, skybox[5]);
	glBegin(GL_QUADS);
		glTexCoord2f(1, 1); glVertex3f(0, 0, 0);
		glTexCoord2f(1, 0); glVertex3f(1, 0, 0);
		glTexCoord2f(0, 0); glVertex3f(1, -1, 0);
		glTexCoord2f(0, 1); glVertex3f(0, -1, 0);
	glEnd();
	glPopMatrix();

	glPushMatrix();
	glTranslated(x + size/2, y - size/2, z + size/2);
	glScaled(size, size, size);
	glBindTexture(GL_TEXTURE_2D, skybox[2]);
	glBegin(GL_QUADS);
		glTexCoord2f(0, 1); glVertex3f(0, 0, 0);
		glTexCoord2f(0, 0); glVertex3f(0, 0, -1);
		glTexCoord2f(1, 0); glVertex3f(-1, 0, -1);
		glTexCoord2f(1, 1); glVertex3f(-1, 0, 0);
	glEnd();
	glBindTexture(GL_TEXTURE_2D, skybox[1]);
	glBegin(GL_QUADS);
		glTexCoord2f(1, 1); glVertex3f(0, 0, 0);
		glTexCoord2f(1, 0); glVertex3f(0, 0, -1);
		glTexCoord2f(0, 0); glVertex3f(0, 1, -1);
		glTexCoord2f(0, 1); glVertex3f(0, 1, 0);
	glEnd();
	glBindTexture(GL_TEXTURE_2D, skybox[4]);
	glBegin(GL_QUADS);
		glTexCoord2f(0, 0); glVertex3f(0, 0, 0);
		glTexCoord2f(1, 0); glVertex3f(-1, 0, 0);
		glTexCoord2f(1, 1); glVertex3f(-1, 1, 0);
		glTexCoord2f(0, 1); glVertex3f(0, 1, 0);
	glEnd();
	glPopMatrix();

	glDisable(GL_TEXTURE_2D);
	glEnable(GL_DEPTH_TEST);
}

Juego::Juego() {
	this->puntos = 0;
	this->nivel = 1;

	this->nave = new Nave();

	inicializar(this);
}

Juego::Juego(int puntos, Nave * nave, int nivel) {
	this->puntos = puntos;
	this->nivel = nivel;

	nave->pos = Vector3D();
	this->nave = nave;

	inicializar(this);
}

void Juego::init() {
	glClearColor (0.0, 0.0, 0.0, 0.0);
}

void Juego::special(int key, int x, int y) {
	if (key == GLUT_KEY_HOME) {
		int x = rand() % this->worldW - this->worldW/2;
		int y = rand() % this->worldW - this->worldW/2;
		int z = rand() % this->worldW - this->worldW/2;
		this->nave->pos = Vector3D(x, y, z);
	}
}

void Juego::keyboard(unsigned char key, int x, int y) {
	if (key == 27) {
		goTo(getMenu());
	} else if (key == 'p' || key == 'P') {
		this->pausa = !this->pausa;
	} else if (key == 'q' || key == 'Q') {
		exit(0);
	} else if (key == 'c' || key == 'C') {
		this->vista = (this->vista + 1) % CANTIDAD_VISTAS;
	} else if (key == ' ') {
		this->balas->add(new Bala(this->nave->pos, this->nave->front, this->nave->vel + 1));
	}
}

void Juego::display() {
	glLoadIdentity();

	// seteo la cámara
	if (this->vista == VISTA_ARRIBA) {
		gluLookAt(this->nave->pos.x, this->nave->pos.y, this->nave->pos.z + 40,
				this->nave->pos.x, this->nave->pos.y, this->nave->pos.z,
				1.0, 0.0, 0.0);
	} else if (this->vista == VISTA_ADENTRO) {
		gluLookAt(0, 0, 0,
				this->nave->front.x, this->nave->front.y, this->nave->front.z,
				this->nave->up.x, this->nave->up.y, this->nave->up.z);
		glTranslated(-this->nave->pos.x, -this->nave->pos.y, -this->nave->pos.z);
	} else if (this->vista == VISTA_ATRAS) {
		gluLookAt(0, 0, 0,
				this->nave->front.x, this->nave->front.y, this->nave->front.z,
				this->nave->up.x, this->nave->up.y, this->nave->up.z);
		glTranslated(-this->nave->pos.x + this->nave->front.x*30, -this->nave->pos.y + this->nave->front.y*30, -this->nave->pos.z + this->nave->front.z*30);
	}

	// dibujamos el skybox
	dibujarSkyBox(this->skybox, this->nave->pos.x, this->nave->pos.y, this->nave->pos.z, 1000);


	// preparo para dibujar
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LESS);

	aplicarConfiguracionGeneral();
	//aplicarConfiguracionModelo(Nave::modelo);
	aplicarConfiguracionModelo(Asteroide::modelo);

	aplicarIluminacion();

	if (Config::luzEnabled){
		if (Config::luzSolar){
			glEnable(GL_LIGHT0);			
		}
		glEnable(GL_LIGHTING);
	}

	// dibujo nave
	if (this->vista != VISTA_ADENTRO) {
		glPushMatrix();
		this->nave->draw();
		glPopMatrix();
	}

	// dibujo asteroides
	Iterador * it = this->asteroides->iterador();
	while (it->hasNext()) {
		Asteroide * a = (Asteroide*)it->next();
		glPushMatrix();
		a->draw();
		glPopMatrix();
	}
	delete it;

	glDisable(GL_LIGHTING);
	glDisable(GL_LIGHT0);

	// dibujo balas
	it = this->balas->iterador();
	while (it->hasNext()) {
		Bala * bala = (Bala*) it->next();
		glPushMatrix();
		bala->draw();
		glPopMatrix();
	}
	delete it;

	// dibujo dust
	it = this->dust->iterador();
	while (it->hasNext()) {
		Dust * d = (Dust*)it->next();
		glPushMatrix();
		d->draw();
		glPopMatrix();
	}
	delete it;

	glDisable(GL_DEPTH_TEST);

	// vista ortogonal para desplegar datos en pantalla
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	if (this->width < this->height) {
		glOrtho(0, 1, this->height/(double)this->width, 0, 0, 20);
	} else {
		glOrtho(0, this->width/(double)this->height, 1, 0, 0, 20);
	}
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

	int vel = abs((int)(this->nave->vel * 99.0));
	bool s = this->nave->vel < 0;
	char vidas[] = {'V','i','d','a','s',':',' ','0'+(char)this->nave->vidas,'\0'};
	char puntos[] = {'P','u','n','t','o','s',':',' ','0'+this->puntos/100,'0'+this->puntos/10%10,'0'+this->puntos%10,'\0'};
	char nivel[] = {'N','i','v','e','l',':',' ','0'+this->nivel/10,'0'+this->nivel%10,'\0'};
	char velocidad[] = {'V','e','l','o','c','i','d','a','d',':',s?'-':' ','0'+vel/10,'0'+vel%10,'\0'};
	if (this->nave->vidas <= 1) {
		glColor3f(1, 0, 0);
	} else if (this->nave->vidas <= 2) {
		glColor3f(1, 1, 0);
	} else {
		glColor3f(0, 1, 0);
	}
	dibujarTexto(vidas, 0, 0, 0.05);
	glColor3f(1, 0.8, 0.2);
	dibujarTexto(puntos, 1*this->width/(double)this->height, 0, 0.05, true);
	glColor3f(1, 1, 1);
	dibujarTexto(nivel, 0, 0.95, 0.05);
	glColor3f(0, 0.7, 1);
	dibujarTexto(velocidad, 1*this->width/(double)this->height, 0.95, 0.05, true);

	// dibujo mira
	if (this->vista == VISTA_ADENTRO) {
		glColor3f(0.7, 0.7, 0.9);
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, this->mira);
		glBegin(GL_POLYGON); {
			glTexCoord2f(0, 0); glVertex3f(0.5*this->width/(double)this->height - 0.05, 0.45, 0);
			glTexCoord2f(0, 1); glVertex3f(0.5*this->width/(double)this->height - 0.05, 0.55, 0);
			glTexCoord2f(1, 1); glVertex3f(0.5*this->width/(double)this->height + 0.05, 0.55, 0);
			glTexCoord2f(1, 0); glVertex3f(0.5*this->width/(double)this->height + 0.05, 0.45, 0);
		} glEnd();
	}

	glPopMatrix();
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
}

void Juego::timer() {
	if (this->pausa) {
		return;
	}
	// movimiento teclado
	if (Keyboard::getInstance()->isKeyDown('a') || Keyboard::getInstance()->isKeyDown('A')) {
		this->nave->addRoll(-NAVE_VEL_ANG);
	}
	if (Keyboard::getInstance()->isKeyDown('d') || Keyboard::getInstance()->isKeyDown('D')) {
		this->nave->addRoll(NAVE_VEL_ANG);
	}
	if (Keyboard::getInstance()->isSpecialDown(GLUT_KEY_LEFT)) {
		this->nave->addYaw(NAVE_VEL_ANG);
	}
	if (Keyboard::getInstance()->isSpecialDown(GLUT_KEY_RIGHT)) {
		this->nave->addYaw(-NAVE_VEL_ANG);
	}
	if (Keyboard::getInstance()->isKeyDown('w') || Keyboard::getInstance()->isKeyDown('W')) {
		this->nave->addPitch(NAVE_VEL_ANG);
	}
	if (Keyboard::getInstance()->isKeyDown('s') || Keyboard::getInstance()->isKeyDown('S')) {
		this->nave->addPitch(-NAVE_VEL_ANG);
	}
	if (Keyboard::getInstance()->isSpecialDown(GLUT_KEY_DOWN)) {
		this->nave->acl = -NAVE_ACL;
	} else if (Keyboard::getInstance()->isSpecialDown(GLUT_KEY_UP)) {
		this->nave->acl = NAVE_ACL;
	} else {
		this->nave->acl = 0;
	}
	// movimiento mouse
	double dyaw = (0.5 - this->mouseX / (double)this->width) * 3;
	double dpitch = (0.5 - this->mouseY / (double)this->height) * 3;
	this->nave->addYaw(dyaw);
	this->nave->addPitch(dpitch);
	
	// actualizar posicion de la nave
	this->nave->update();
	
	// actualizo posición de los asteroides
	Iterador * it = this->asteroides->iterador();
	while (it->hasNext()) {
		Asteroide * a = (Asteroide *) it->next();
		a->update();
	}
	delete it;


	// actualizo posición de las balas
	it = this->balas->iterador();
	while (it->hasNext()) {
		Bala * bala = (Bala*)it->next();
		bala->update();
		if (bala->distancia(this->nave) > 100) {
			this->balas->remove(bala);
			delete bala;
		}
	}
	delete it;

	// verifico colision bala-asteroide
	it = this->balas->iterador();
	while (it->hasNext()) {
		Bala * bala = (Bala*) it->next();
		Iterador * it2 = this->asteroides->iterador();
		bool fin = false;
		while (!fin && it2->hasNext()) {
			Asteroide * a = (Asteroide*)it2->next();
			if (bala->colisiona(a)) {
				if (a->radio > 0.25) {
					Asteroide * a1 = new Asteroide(a->pos, a->radio / 2);
					Asteroide * a2 = new Asteroide(a->pos, a1->radio);
					this->asteroides->add(a1);
					this->asteroides->add(a2);
				}
				this->asteroides->remove(a);
				delete a;
				this->balas->remove(bala);
				delete bala;
				fin = true;
				this->puntos++;
			}
		}
		delete it2;
	}
	delete it;

	// verifico colision nave-asteroide
	it = this->asteroides->iterador();
	while (it->hasNext()) {
		Asteroide * a = (Asteroide*)it->next();
		if (a->colisiona(this->nave)) {
			this->nave->vidas--;
			this->asteroides->remove(a);
			delete a;
		}
	}
	delete it;

	// actualizo polvo espacial
	it = this->dust->iterador();
	while (it->hasNext()) {
		Dust * d = (Dust*)it->next();
		if (d->distancia(this->nave) > 40) {
			this->dust->remove(d);
			this->dust->add(randomDust(this->nave));
		}
	}
	delete it;

	// verifico fin de juego
	if (this->asteroides->count() == 0) {
		siguienteNivel(this);
	}
	if (this->nave->vidas <= 0) {
		perdiste(this);
	}

}

void Juego::reshape(int w, int h) {
	this->width = w;
	this->height = h;
	this->mouseX = w/2;
	this->mouseY = h/2;
	glViewport (0, 0, (GLsizei) w, (GLsizei) h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity ();
	gluPerspective(60, w/(float)h, 1.0, 2000);
	glMatrixMode(GL_MODELVIEW);
}

void Juego::motion(int x, int y) {
	this->mouseX = x;
	this->mouseY = y;
}

void Juego::mouse(int button, int state, int x, int y) {
	if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
		this->balas->add(new Bala(this->nave->pos, this->nave->front, this->nave->vel + 1));
	}
}
