#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <glut.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <math.h>
#include <time.h>

const int numero_de_partes = 15; //Numero de partes del personaje
const int numero_de_animaciones = 3; //Numero de partes del personaje

const enum PARTES_DEL_CUERPO
{
	TORSO, HEAD,
	LUA, LLA, LH,
	LUL, LLL, LF,
	RUL, RLL, RF,
	RUA, RLA, RH,
	EYES
};
const enum ANIMACIONES {
	DEFAULT,
	SALUDAR,
	TIRO_A_GOL
};

typedef struct treenode {
	//GLfloat m[16];
	void (*f)();
	struct treenode *sibling;
	struct treenode *child;
} treenode;

static clock_t last_clock = clock();

// Propiedades de materiales
GLfloat qaBlack[]	= {0.0, 0.0, 0.0, 1.0};
GLfloat qaRed[]		= {1.0, 0.0, 0.0, 1.0};
GLfloat qaGreen[]	= {0.0, 1.0, 0.0, 1.0};
GLfloat qaBlue[]	= {0.0, 0.0, 1.0, 1.0};
GLfloat qaWhite[]	= {1.0, 1.0, 1.0, 1.0};

GLfloat chrome_amb[] = {.25, .25, .25, 1.0};
GLfloat chrome_dif[] = {.4 , .4 , .4 , 1.0};
GLfloat chrome_spe[] = {.77, .77, .77, 1.0};
GLfloat chrome_shi[] = {76.8};

GLfloat ruby_amb[] = {.17, .01, .01, .55};
GLfloat ruby_dif[] = {.61, .04, .04, .55};
GLfloat ruby_spe[] = {.72, .62, .62, .55};
GLfloat ruby_shi[] = {76.8};



// Definicion de menu para el click derecho
enum MENU_TYPE
{
		MENU_TRONCO,
		MENU_CABEZA,
	
		MENU_BRAZO_IZQUIERDO,
        MENU_ANTEBRAZO_IZQUIERDO,
        MENU_MANO_IZQUIERDA,

		MENU_MUSLO_IZQUIERDO,
		MENU_PIERNA_IZQUIERDA,
		MENU_PIE_IZQUIERDO,
		
		MENU_MUSLO_DERECHO,
		MENU_PIERNA_DERECHA,
		MENU_PIE_DERECHO,

		MENU_BRAZO_DERECHO,
        MENU_ANTEBRAZO_DERECHO,
        MENU_MANO_DERECHA,

		MENU_DEFAULT,
		MENU_SALUDAR,
		MENU_TIRO_A_GOL

};

bool esperar_frame (int framerate) {
	clock_t current_clock;
	current_clock = clock();
	if(current_clock - last_clock > CLOCKS_PER_SEC / framerate){
		last_clock = current_clock;
		return true;
	}
	return false;
}

void copiar_rotaciones(double src[][3],double dst[][3], int partes){
	for(int i = 0; i < partes; i++){
		for(int j = 0; j < 3; j++)
			dst[i][j] = src[i][j];
	}
}

void asignar_rotaciones(double dst[][3], int anim){
	switch(anim) {
	case DEFAULT:
		dst[TORSO][0] = 0;	dst[TORSO][1] = 0;	dst[TORSO][2] = 0;
		dst[HEAD][0] = 0;	dst[HEAD][1] = 0;	dst[HEAD][2] = 0;
		dst[LUA][0] = 0;	dst[LUA][1] = 0;	dst[LUA][2] = 0;
		dst[LLA][0] = 0;	dst[LLA][1] = 0;	dst[LLA][2] = 0;
		dst[LH][0] = 0;		dst[LH][1] = 0;		dst[LH][2] = 0;
		dst[LUL][0] = 0;	dst[LUL][1] = 0;	dst[LUL][2] = 0;
		dst[LLL][0] = 0;	dst[LLL][1] = 0;	dst[LLL][2] = 0;
		dst[LF][0] = 0;		dst[LF][1] = 0;		dst[LF][2] = 0;
		dst[RUL][0] = 0;	dst[RUL][1] = 0;	dst[RUL][2] = 0;
		dst[RLL][0] = 0;	dst[RLL][1] = 0;	dst[RLL][2] = 0;
		dst[RF][0] = 0;		dst[RF][1] = 0;		dst[RF][2] = 0;
		dst[RUA][0] = 0;	dst[RUA][1] = 0;	dst[RUA][2] = 0;
		dst[RLA][0] = 0;	dst[RLA][1] = 0;	dst[RLA][2] = 0;
		dst[RH][0] = 0;		dst[RH][1] = 0;		dst[RH][2] = 0;
		dst[EYES][0] = 0;	dst[EYES][1] = 0;	dst[EYES][2] = 0;
		break;
	case SALUDAR:
		dst[TORSO][0] = 0;	dst[TORSO][1] = 0;	dst[TORSO][2] = 0;
		dst[HEAD][0] = 0;	dst[HEAD][1] = 0;	dst[HEAD][2] = 0;
		dst[LUA][0] = 160;	dst[LUA][1] = 0;	dst[LUA][2] = -65;
		dst[LLA][0] = 0;	dst[LLA][1] = 0;	dst[LLA][2] = 35;
		dst[LH][0] = 0;		dst[LH][1] = 0;		dst[LH][2] = 0;
		dst[LUL][0] = 0;	dst[LUL][1] = 0;	dst[LUL][2] = 0;
		dst[LLL][0] = 0;	dst[LLL][1] = 0;	dst[LLL][2] = 0;
		dst[LF][0] = 0;		dst[LF][1] = 0;		dst[LF][2] = 0;
		dst[RUL][0] = 0;	dst[RUL][1] = 0;	dst[RUL][2] = 0;
		dst[RLL][0] = 0;	dst[RLL][1] = 0;	dst[RLL][2] = 0;
		dst[RF][0] = 0;		dst[RF][1] = 0;		dst[RF][2] = 0;
		dst[RUA][0] = 20;	dst[RUA][1] = 20;	dst[RUA][2] = -30;
		dst[RLA][0] = -50;	dst[RLA][1] = 0;	dst[RLA][2] = -120;
		dst[RH][0] = 0;		dst[RH][1] = 20;	dst[RH][2] = 0;
		dst[EYES][0] = 0;	dst[EYES][1] = 0;	dst[EYES][2] = 0;
		break;
	case TIRO_A_GOL:
		dst[TORSO][0] = 0;	dst[TORSO][1] = 0;	dst[TORSO][2] = 0;
		dst[HEAD][0] = 25;	dst[HEAD][1] = 20;	dst[HEAD][2] = 0;
		dst[LUA][0] = 95;	dst[LUA][1] = 15;	dst[LUA][2] = 20;
		dst[LLA][0] = -50;	dst[LLA][1] = 0;	dst[LLA][2] = 50;
		dst[LH][0] = 0;		dst[LH][1] = -5;	dst[LH][2] = -10;
		dst[LUL][0] = 0;	dst[LUL][1] = 20;	dst[LUL][2] = 10;
		dst[LLL][0] = 0;	dst[LLL][1] = 0;	dst[LLL][2] = 0;
		dst[LF][0] = 0;		dst[LF][1] = 0;		dst[LF][2] = 10;
		dst[RUL][0] = 25;	dst[RUL][1] = 30;	dst[RUL][2] = -30;
		dst[RLL][0] = 85;	dst[RLL][1] = 0;	dst[RLL][2] = 0;
		dst[RF][0] = 20;	dst[RF][1] = 0;		dst[RF][2] = 5;
		dst[RUA][0] = 125;	dst[RUA][1] = 5;	dst[RUA][2] = 10;
		dst[RLA][0] = -55;	dst[RLA][1] = 0;	dst[RLA][2] = -25;
		dst[RH][0] = 0;		dst[RH][1] = 5;		dst[RH][2] = 0;
		dst[EYES][0] = 0;	dst[EYES][1] = 0;	dst[EYES][2] = 0;
	}
}

void traverse(treenode *node) {
	// guardar la matriz actual porque las transformaciones a realizarse
	// sólo deben afectarle a él y a sus hijos
	glPushMatrix();
	// transformar relativo a su padre
	//glMultMatrixf(node->m);
	//glGetFloatv(GL_MODELVIEW, nodos[parte].m);
	// dibujar el nodo
	node->f();
	// primer recorrer los hijos (si hay)
	if (node->child != NULL)
		traverse(node->child);

	glPopMatrix();
	// después recorrer los hermanos (si hay)
	if(node->sibling != NULL)
		traverse(node->sibling);
}

void modifica_rotacion(double rotaciones[][3], int parte, double rx, double ry, double rz){
	rotaciones[parte][0] = rx;
	rotaciones[parte][1] = ry;
	rotaciones[parte][2] = rz;
}
void modifica_limite(double limites[][3][2], int parte, double minx, double maxx, double miny, double maxy, double minz, double maxz) {
	limites[parte][0][0] = minx;
	limites[parte][0][1] = maxx;

	limites[parte][1][0] = miny;
	limites[parte][1][1] = maxy;

	limites[parte][2][0] = minz;
	limites[parte][2][1] = maxz;
}
void modifica_color(double colores[][3], int parte, int r, int g, int b){
	colores[parte][0] = r;
	colores[parte][1] = g;
	colores[parte][2] = b;
}

void modifica_material(GLfloat materiales[][4][4], int parte, GLfloat amb[], GLfloat dif[], GLfloat spe[], GLfloat shi[]) {
	materiales[parte][0][0] = amb[0];
	materiales[parte][0][1] = amb[1];
	materiales[parte][0][2] = amb[2];
	materiales[parte][0][3] = amb[3];

	materiales[parte][1][0] = dif[0];
	materiales[parte][1][1] = dif[1];
	materiales[parte][1][2] = dif[2];
	materiales[parte][1][3] = dif[3];

	
	materiales[parte][2][0] = spe[0];
	materiales[parte][2][1] = spe[1];
	materiales[parte][2][2] = spe[2];
	materiales[parte][2][3] = spe[3];

	materiales[parte][3][0] = shi[0];
	materiales[parte][3][1] = 0;
	materiales[parte][3][2] = 0;
	materiales[parte][3][3] = 0;
	/*switch(tipo){
	case GL_AMBIENT:
		materiales[parte][0][0] = arr[0];
		materiales[parte][0][1] = arr[1];
		materiales[parte][0][2] = arr[2];
		materiales[parte][0][3] = arr[3];
		break;
	case GL_DIFFUSE:
		materiales[parte][1][0] = arr[0];
		materiales[parte][1][1] = arr[1];
		materiales[parte][1][2] = arr[2];
		materiales[parte][1][3] = arr[3];
		break;
	case GL_SPECULAR:
		materiales[parte][2][0] = arr[0];
		materiales[parte][2][1] = arr[1];
		materiales[parte][2][2] = arr[2];
		materiales[parte][2][3] = arr[3];
		break;
	case GL_SHININESS:
		materiales[parte][3][0] = arr[0];
		materiales[parte][3][1] = 0;
		materiales[parte][3][2] = 0;
		materiales[parte][3][3] = 0;
		break;
	}*/
}

void carga_color(double colores[][3], int parte){
	glColor3ub(colores[parte][0],
			   colores[parte][1],
			   colores[parte][2]);
}
void carga_material(GLfloat materiales[][4][4], int parte){
	GLenum face = GL_FRONT;

	glMaterialfv(face, GL_AMBIENT, materiales[parte][0]);
	glMaterialfv(face, GL_DIFFUSE, materiales[parte][1]);
	glMaterialfv(face, GL_SPECULAR, materiales[parte][2]);
	glMaterialfv(face, GL_SHININESS, materiales[parte][3]);
}

void leer_limite(double limites[][3][2], int parte, FILE *f){
	int i;
	double limite[6];
	
	fscanf_s(f,"%s",NULL);	//Leer nota del nombre de la parte en el archivo
	for(i = 0; i < 6; i++){	//Leer limites (6 enteros concecutivos: minx maxx miny maxy minz maxz)
		fscanf_s(f,"%lf",&limite[i]);
	}

	modifica_limite(limites, parte,limite[0],limite[1],limite[2],limite[3],limite[4],limite[5]);
}
void leer_color(double colores[][3], int parte, FILE *f){
	int i, rgb[3];
	for(i = 0; i < 6; i++)
		fscanf_s(f,"%d",&rgb[i]);
	modifica_color(colores, parte,rgb[0],rgb[1],rgb[2]);
}
void leer_material(GLfloat materiales[][4][4], int parte, FILE *f){
	GLfloat temp[4];
	GLfloat amb[4];
	GLfloat dif[4];
	GLfloat spe[4];
	GLfloat shi[1];

	for(int i = 0; i < 6; i++){
		fscanf_s(f,"%s",NULL);	//Leer nota del nombre de la parte en el archivo
		for(int j = 0; j < 3; j++){	// Leer propiedades de los materiales
			fscanf_s(f, "%f", &temp[0]);
			fscanf_s(f, "%f", &temp[1]);
			fscanf_s(f, "%f", &temp[2]);
			fscanf_s(f, "%f", &temp[3]);
			if(j == 0) amb[0] = temp[0]; amb[1] = temp[1]; amb[2] = temp[2]; amb[3] = temp[3];	// Cuando j = 0 lee las propiedades ambientales
			if(j == 1) dif[0] = temp[0]; dif[1] = temp[1]; dif[2] = temp[2]; dif[3] = temp[3];	// Cuando j = 1 lee las propiedades de difusion
			if(j == 2) spe[0] = temp[0]; spe[1] = temp[1]; spe[2] = temp[2]; spe[3] = temp[3];	// Cuando j = 2 lee las propiedades especulares
		}
		fscanf_s(f, "%f", &temp[0]);
		shi[0] = temp[0];
		modifica_material(materiales, i, amb, dif, spe, shi);	// Luego lee la cantidad de brillo
	}
}
void leer_rotaciones(double rotaciones[][3]){
	double temp[3];
	FILE* file;
	file = fopen("rotaciones.txt", "r");
	if(file != NULL){
		for(int i = 0; i < numero_de_partes; i++){	
			fscanf_s(file, "%lf %lf %lf", &temp[0], &temp[1], &temp[2]);
			modifica_rotacion(rotaciones, i, temp[0], temp[1], temp[2]);
		}
		printf("Rotaciones cargadas de 'rotaciones.txt' \n");
	}else{
		printf("Fallo la carga del archivo 'rotaciones.txt', verifica que existe el archivo e intenta otra vez.\n");
	}
}
void guardar_rotaciones(double rotaciones[][3]){
	FILE* file;
	file = fopen("rotaciones.txt", "w");
	for(int i = 0; i < numero_de_partes; i++){
		fprintf_s(file, "%lf %lf %lf\n", rotaciones[i][0], rotaciones[i][1], rotaciones[i][2]);
	}
	fclose(file);
	printf("Rotaciones guardadas en 'rotaciones.txt' \n");
}

void rotar(double rotaciones[][3], double limites[][3][2], int parte, int eje, double valor, bool limites_activados){ // eje: rotar en "x"(0), "y"(1) o "z"(2) | valor: valor relativo de rotacion
	rotaciones[parte][eje] += valor;

	if(rotaciones[parte][eje] >  360) rotaciones[parte][eje] -= 360;
	if(rotaciones[parte][eje] < -360) rotaciones[parte][eje] += 360;
	
	if(limites_activados){
		if(rotaciones[parte][eje] < limites[parte][eje][0]) //Si el angulo es menor que el minimo
			rotaciones[parte][eje] = limites[parte][eje][0];	// el angulo se cambia por el minimo
		if(rotaciones[parte][eje] > limites[parte][eje][1]) //Si el angulo es mayor al maximo
			rotaciones[parte][eje] = limites[parte][eje][1];	// el angulo se cambia por el maximo
	}
}