//Si estamos en Windows...
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif //fin windows

//Linkar las librerías necesarias
//MSVC lo hace así:
#pragma comment (lib,"opengl32.lib")
#pragma comment (lib,"glu32.lib")
#pragma comment (lib,"Libs\\SDL.lib")
//SDLmain depende de RELEASE/DEBUG
#ifdef _DEBUG
#pragma comment (lib,"Libs\\SDLmaind.lib")
#else 
#pragma comment (lib,"Libs\\SDLmain.lib")
#endif

//OpenGL includes
#include <GL/gl.h>	
#include <GL/glu.h>	
//SDL Include
#include "..\SDL-1.2.9\include\SDL.h"
//SDL Image
#pragma comment (lib,"Libs\\SDL_image.lib")
#include "..\SDL-1.2.9\include\SDL_image.h"
#include "glext.h"
#include <math.h>

// libreria para cargar modelos 3ds
#include "Model_3DS.h"

// inicializaciones
// para rotaciones y otros movimientos
float rot_tierra=0;
float rot_luna=0;
float rot_meteo=0;
float rot_meteo2=0;
float rot_meteo3=0;
float rot_meteo4=0;
float rot_nave=0;
float travel_nave=1.0f;
float size_nave=0;

// para los modelos
Model_3DS r2d2;
Model_3DS nave;
Model_3DS meteorito;

// para las texturas
GLint fondo;
GLint tierra;
GLint nubes;
GLint luna;

// valores que usaremos para la luz
GLfloat light_ambient[] = {0.1f, 0.1f, 0.1f, 1.0f};
GLfloat light_diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat light_specular[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat light_position[] = {-5, 0, -15, 1.0f};
GLfloat light_sd[] = {0.0f,-1.0f, 0.0f};

// para la vista y la camara
GLfloat eye_pos[] = {0.0f,0.0f,0.0f};
GLfloat look_at[] = {0.0f,0.0f,-1.0f};
GLfloat up_vector[] = {0.0f,1.0f,0.0f};

// funcion para poder cargar texturas
// atentos al final de esta funcion al tema de los mipmaps
GLuint LoadTexture(const char * fileName, GLint mode)
{
	SDL_Surface *image=NULL; //Original image
	SDL_Surface *image2=NULL;//Image ready for texture

	//Pixelformat for R8G8B8A8, it's the maximum divisor of pixelformats, and will ALLWAYS work
	SDL_PixelFormat formato;
	formato.BitsPerPixel=32;
	formato.BytesPerPixel=4;
	formato.Rmask=0x000000FF;
	formato.Gmask=0x0000FF00;
	formato.Bmask=0x00FF0000;
	formato.Amask=0xFF000000;
	formato.Rloss=0;
	formato.Gloss=0;
	formato.Bloss=0;
	formato.Rshift=0;
	formato.Gshift=8;
	formato.Bshift=16;
	formato.Ashift=24;
	formato.Aloss=0;
	formato.alpha=255;
	formato.colorkey=0;
	formato.palette=0;


	image = IMG_Load(fileName); //Load Original image
	if (!image)
		return 0;

	//Convert it to RGBA8 (pixelformat above)
	image2 = SDL_ConvertSurface(image,&formato,SDL_HWSURFACE); 
	if (!image)
	{
		SDL_FreeSurface(image);
		return 0;
	}
	
	//Size in bytes of image (width x height x bytesperpixel)
	GLuint imageSize=(image2->w)*(image2->h)*4;
	GLubyte * ImageData = new GLubyte[imageSize];
	GLubyte * TempData = new GLubyte[imageSize];
	//copy 	SDL data over our pointer
	memcpy(ImageData, image2->pixels, imageSize);
	//Damn SDL Loader gives flipped textures :_(
	//We have to unflip
	//Flip Verticaly
	for (GLuint j=0;j<(imageSize);j+=4)
	{
		TempData[j] = ImageData[imageSize-4 - j];
		TempData[j+1] = ImageData[imageSize-4 - j+1];
		TempData[j+2] = ImageData[imageSize-4 - j+2];
		TempData[j+3] = ImageData[imageSize-4 - j+3];
	}
	//Flip Horizontaly
	GLuint w = 0, x = 0, y = 0;
	for (GLuint i = 0; i < imageSize; i+=4)		
	{
		//Calculate position in 2d Array
		w = ((y + 1) * image2->w*4) - (x*4)-4;
		ImageData[i] = TempData[w];
		ImageData[i+1] = TempData[w+1];
		ImageData[i+2] = TempData[w+2];
		ImageData[i+3] = TempData[w+3];
		x++;
		//Check image bounds
		if (x == image2->w)
		{
			x = 0;
			y++;
		}
	}
	//Now we have correct texture data into ImageData, so
	delete TempData;
	GLuint texID;
	//Generate OpenGL texture
	glGenTextures(1, &texID);					
	glBindTexture(GL_TEXTURE_2D, texID);		
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // To remove annoying borders around textures
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Need to check whether CLAMP_TO_EDGE is available!!

	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAX_ANISOTROPY_EXT,8); //MAX ANISOTROPY
	glTexParameteri(GL_TEXTURE_2D,GL_GENERATE_MIPMAP_SGIS,GL_TRUE); //MIPMAP GENERATION

	// dependiendo de nuestra si nuestra tarjeta grafica permite generar automaticamente los mipmaps
	// usaremos gluBuild2Mipmaps o caso negativo glTexImage2D
	// a priori, las nVIDIA y ATI lo permiten, las Intel no.
	//glTexImage2D(GL_TEXTURE_2D, 0, mode, image2->w,image2->h, 0, GL_RGBA, GL_UNSIGNED_BYTE,ImageData);
	gluBuild2DMipmaps(GL_TEXTURE_2D, mode, image2->w, image2->h, GL_RGBA, GL_UNSIGNED_BYTE, ImageData);

	//Done using ImageData, free it
	delete ImageData;
	//And free SDL Surfaces used.
	SDL_FreeSurface(image);
	SDL_FreeSurface(image2);
	return texID;
}


/* Función de dibujado */
void DrawGLScene()
{
	// especificamos los valores
	rot_tierra+=0.06f;
	rot_luna+=0.035f;
	rot_meteo+=0.03f;
	rot_meteo2+=0.1f;
	rot_meteo3+=0.02f;
	rot_meteo4+=0.2f;
	travel_nave+=3.0f;
	size_nave+=1.0f;;
	rot_nave+=0.5f;

	// borramos los buffers
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	// cargamos la matriz identidad
	glLoadIdentity();

	// nos llevamos el fondo hacia atras
	glTranslatef(0,0,-400);
	glColor4f(1.0f,1.0f,1.0f,1.0f);

	// habilitamos texturas
	glEnable(GL_TEXTURE_2D);
	// modo projection,
	glMatrixMode(GL_PROJECTION);

	// ############ FONDO ##########

	// vamos a pasar a modo ortho para poner el fondo asi que
	// guardamos el estado para no tener que volver a configurar la perspectiva
	glPushMatrix();

	glLoadIdentity();
	// con las coords que queramos, coords opengl, no pixels
	glOrtho(0,8,0,6,0.1f,500.0f);
	// quitamos la luz, para que no le afecte y se vea uniforme
	glDisable(GL_LIGHTING);
	// asignamos la textura a usar
	glBindTexture(GL_TEXTURE_2D,fondo);
	// deshabilitamos que lo que escribamos ahora escriba en el buffer z
	glDisable(GL_DEPTH_TEST);
	// ponemos el fondo y la textura ajustada
	glBegin(GL_QUADS);
		glTexCoord2f(0,0);
		glVertex2i(0,0);	
		glTexCoord2f(0,1);
		glVertex2i(0,6);	
		glTexCoord2f(1,1);
		glVertex2i(8,6);	
		glTexCoord2f(1,0);
		glVertex2i(8,0);		
	glEnd();
	
	// volvemos a activar todas las cosas y restauramos el modo perpectiva
	glEnable(GL_DEPTH_TEST);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	
	// ############ TIERRA ############

	// cargamos la matriz identidad
	glLoadIdentity();
	// recargamos el gluLookAt, esto sera necesario siempre que carguemos la identidad
	gluLookAt(eye_pos[0],eye_pos[1],eye_pos[2],
		look_at[0],look_at[1],look_at[2],
		up_vector[0],up_vector[1],up_vector[2]);

	// ponemos la luces y especificamos el material
	glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
	glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
	glLightfv(GL_LIGHT0, GL_POSITION, light_position);

	GLfloat mat_a[] = {1.0, 1.0, 1.0, 1.0};
	GLfloat mat_d[] = {1.0, 1.0, 1.0, 1.0};
	GLfloat mat_s[] = {0.0, 0.0, 0.0, 1.0};
	GLfloat mat_e[] = {0.0, 0.0, 0.0, 1.0};
	GLfloat low_sh[] = {10};

	glMaterialfv(GL_FRONT, GL_AMBIENT, mat_a);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_d);
	glMaterialfv(GL_FRONT, GL_SPECULAR, mat_s);
	glMaterialfv(GL_FRONT, GL_EMISSION, mat_e);
	glMaterialfv(GL_FRONT, GL_SHININESS, low_sh);

	// activamos la iluminacion
	glEnable(GL_LIGHTING);
	// activamos light0
	glEnable(GL_LIGHT0);

	// nos movemos hacia z's negativas para situar nuestro planeta tierra
	glTranslatef(0,0,-60.0f);
	glColor4f(1.0f,1.0f,1.0f,1.0f);
	// textura para la tierra
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D,tierra);
	
	// la ponemos a rotar sobre el eje y
	glRotatef(rot_tierra,0.0f,1.0f,0.0f);
	// inicialmente la coordenadas hacen que la textura la veamos de lado
	// asi que lo ponemos bien con un cambio de 90grados en -x
	glRotatef(90.0f,-1.0f,0.0f,0.0f);
	// ahora creamos la esfera de la tierra
	/* realmente usar estas funciones de GLU aqui es un poco cerdo porque
	cada vez los objetos se generan y penaliza el rendimiento, para evitar
	esto veanse las 'display lists' (google powered)*/
	GLUquadricObj * tobj;
	tobj = gluNewQuadric();			       
	gluQuadricNormals(tobj, GL_SMOOTH);	// para que haya una normal por vertice en lugar de por cara
	gluQuadricTexture(tobj, GL_TRUE);
	gluSphere(tobj,15.0f,50,50);
	gluDeleteQuadric(tobj);
	// ahora vamos a poner las nubes con otra esfera transparente un poquito mas grande
	// activamos el Blending para la transparencia
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
	glEnable(GL_BLEND);
	glRotatef(rot_tierra,0.0f,1.0f,0.0f);
	// ponemos la textura de las nubes
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D,nubes);
	GLUquadricObj * tobj_nubes;
	tobj_nubes=gluNewQuadric();			       
	gluQuadricNormals(tobj_nubes, GL_SMOOTH);	
	gluQuadricTexture(tobj_nubes, GL_TRUE);
	gluSphere(tobj_nubes,15.1f,50,50);
	gluDeleteQuadric(tobj_nubes);
	// desactivamos el blending para que no siga penalizando el rendimiento
	glDisable(GL_BLEND);

	// ######### LUNA #############
	// identidad
	glLoadIdentity();
	// recargamos el gluLookAt
	gluLookAt(eye_pos[0],eye_pos[1],eye_pos[2],
		look_at[0],look_at[1],look_at[2],
		up_vector[0],up_vector[1],up_vector[2]);
	
	// activamos la iluminacion
	glEnable(GL_LIGHTING);
	// activamos light0
	glEnable(GL_LIGHT0);

	glColor4f(1.0f,1.0f,1.0f,1.0f);

	// textura para la luna
	glBindTexture(GL_TEXTURE_2D,luna);
	glEnable(GL_TEXTURE_2D);
	// movemos la luna hasta el centro de la tierra
	glTranslatef(0.0f,0.0f,-60.0f);
	// la rotamos sobre si misma
	glRotatef(rot_luna,0.3f,1.0f,0.2f);
	// la desplazamos fuera de la tierra y como mantiene el origen de la tierra
	// como origen para su rotate empezara a orbitarla
	glTranslatef(-25.0f,0.0f,0.0f);
	// creamos esfera luna
	GLUquadricObj * tobj2;
	tobj2 = gluNewQuadric();			       
	gluQuadricNormals(tobj2, GL_SMOOTH);	
	gluQuadricTexture(tobj2, GL_TRUE);
	gluSphere(tobj2,2.0f,25,25);
	gluDeleteQuadric(tobj2);

	// ######## R2D2 ############
	glLoadIdentity();
	// recargamos el gluLookAt
	gluLookAt(eye_pos[0],eye_pos[1],eye_pos[2],
		look_at[0],look_at[1],look_at[2],
		up_vector[0],up_vector[1],up_vector[2]);

	// activamos la iluminacion
	glEnable(GL_LIGHTING);
	// activamos light0
	glEnable(GL_LIGHT0);

	// activamos las texturas
	glEnable(GL_TEXTURE_2D);
	// lo coloco en algun lugar de la escena que me guste
	glTranslatef(8.0f,-8.0f,-28.0f);
	glRotatef(-45.0f,0.0f,1.0f,0.0f);
	glRotatef(-25.0f,0.0f,0.0f,1.0f);
	glScalef(0.25f,0.25f,0.25f);
	// gracias a la libreria Model_3DS lo dibujo
	r2d2.Draw();

	// ####### TRANSBORDADOR ##########
	
	glLoadIdentity();
	// recargamos el gluLookAt
	gluLookAt(eye_pos[0],eye_pos[1],eye_pos[2],
		look_at[0],look_at[1],look_at[2],
		up_vector[0],up_vector[1],up_vector[2]);
	
	// activamos la iluminacion
	glEnable(GL_LIGHTING);
	// activamos light0
	glEnable(GL_LIGHT0);

	// activamos las texturas
	glEnable(GL_TEXTURE_2D);
	// lo colocamos en lo que va a ser su punto de despegue
	glTranslatef(-5.0f,0.0f,-45.0f);
	glRotatef(-20.0f,0.0f,1.0f,-5.0f);
	// hacemos un efecto cutre de vuelo acercandose desde la tierra hacia nosotros
	glTranslatef(-travel_nave*0.001f,0.0f,travel_nave*0.02f);
	glScalef(size_nave*0.0005f,size_nave*0.0005f,size_nave*0.0005f);
	glRotatef(rot_nave,0.0f,0.0f,1.0f);
	// pintamos el modelo
	nave.Draw();
	
	// ########## METEORITO 1 ###########
	glLoadIdentity();
	// recargamos el gluLookAt
	gluLookAt(eye_pos[0],eye_pos[1],eye_pos[2],
		look_at[0],look_at[1],look_at[2],
		up_vector[0],up_vector[1],up_vector[2]);

	// activamos la iluminacion
	glEnable(GL_LIGHTING);
	// activamos light0
	glEnable(GL_LIGHT0);

	// mas de lo mismo, le ponemos una orbita que nos guste
	glEnable(GL_TEXTURE_2D);
	glTranslatef(5.0f,0.0f,-95.0f);
	glRotatef(rot_meteo,0.4f,1.0f,0.0f);
	glTranslatef(-15.0f,0.0f,60.0f);
	glRotatef(rot_meteo2,0.0f,0.0f,1.0f);
	glScalef(2.0f,2.0f,2.0f);
	meteorito.Draw();
	
	// ##### METEORITO 2 #########
	glLoadIdentity();
	// recargamos el gluLookAt
	gluLookAt(eye_pos[0],eye_pos[1],eye_pos[2],
		look_at[0],look_at[1],look_at[2],
		up_vector[0],up_vector[1],up_vector[2]);

	// activamos la iluminacion
	glEnable(GL_LIGHTING);
	// activamos light0
	glEnable(GL_LIGHT0);

	// de nuevo
	glEnable(GL_TEXTURE_2D);
	glTranslatef(5.0f,0.0f,-35.0f);
	glRotatef(rot_meteo3,-0.2f,1.0f,0.0f);
	glTranslatef(35.0f,20.0f,-60.0f);
	glRotatef(rot_meteo4,0.0f,0.0f,-1.0f);
	glScalef(2.0f,2.0f,2.0f);
	// reaprovechamos el mismo modelo
	meteorito.Draw();
	
	glDisable(GL_TEXTURE_2D);
	// estamos usando doble buffer ;)
	SDL_GL_SwapBuffers();
}


/* Función de inicio de OpenGL */
void initGL(int witdh, int height)
{
	//Tamaño de la pantalla OpenGL
	glViewport(0, 0, witdh, height);
	//Color para borrar la pantalla
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);			
	//Profundidad para borrar el buffer Z
	glClearDepth(500);					
	//Función para la comprobación en Z
	glDepthFunc(GL_LESS);			
	//Activar comprobación en Z
	glEnable(GL_DEPTH_TEST);
	//Activar sombreado suave
	glShadeModel(GL_SMOOTH);
	//Función para el blending
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
	//Ponemos la luz ambiental a cero
	GLfloat ambient[] = {0.0, 0.0, 0.0, 0.0};
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT,ambient);

	//Configuración de las matrices OpenGL
	//La de textura, inicializamos a la identidad
	glMatrixMode(GL_TEXTURE); 
	glLoadIdentity();	

	//La projection, para decir cómo renderizar la escena
	//Inicializamos con perspectiva
	glMatrixMode(GL_PROJECTION); 
	glLoadIdentity();								
	gluPerspective(45.0f,(GLfloat)witdh/(GLfloat)height,0.1f,500.0f);

	//La modelview, para decir que transformaciones sufren los vértices que enviamos
	//Inicializamos a la identidad
	glMatrixMode(GL_MODELVIEW); 
	glLoadIdentity();
}


int main(int argc, char **argv) 
{  
	
	//////////////////////////////////////////////////////////
	//En un futuro hacer algo con los argumentos de entrada //
	argc;                                                   //
	argv;                                                   //
	//////////////////////////////////////////////////////////


	
	if ( SDL_Init(SDL_INIT_VIDEO) < 0 )
		return 1;

	SDL_WM_SetIcon(SDL_LoadBMP("icono.bmp"), NULL);

	int flags = SDL_OPENGL | SDL_RESIZABLE;// | SDL_FULLSCREEN;

	SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 24 );
	SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
	SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, 0 );

	SDL_Surface * screen;

	if ( (screen = SDL_SetVideoMode(800, 600, 32, flags)) == NULL )
	{
		SDL_Quit();
		return 2;
	}
	
	SDL_WM_SetCaption("Curso OpenGL", NULL);
	SDL_ShowCursor(1);

	initGL(800,600);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	
	SDL_GL_SwapBuffers();
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	SDL_GL_SwapBuffers();

	int done=0;
	
	SDL_EnableUNICODE(1);
	SDL_EnableKeyRepeat(20, 0);

	// para cargar las texturas
	fondo = LoadTexture("texture/fondo.jpg",GL_RGB);
	tierra = LoadTexture("texture/EarthMap.jpg",GL_RGB);
	// ojo! esta se carga con RGBA para que tengamos disponible en canal alpha para hacer blending
	nubes = LoadTexture("texture/EarthClouds.png",GL_RGBA);
	luna = LoadTexture("texture/luna2.jpg",GL_RGB);
	
	// para cargar los modelos 3ds
	r2d2.Load("model/r2d2.3ds");
	nave.Load("model/export3.3ds");
	meteorito.Load("model/exported1.3ds");
	
	while ( ! done )
	{
		DrawGLScene();
		
		/* This could go in a separate function */
		SDL_Event event;
		while ( SDL_PollEvent(&event) )
		{
			if ( event.type == SDL_QUIT )
				done = 1;
			
			if ( event.type == SDL_KEYDOWN )
			{
				switch (event.key.keysym.unicode)
				{
					case SDLK_ESCAPE:
						done = 1;
						break;
					case SDLK_BACKSPACE:
						break;
					case SDLK_RETURN:
						if (event.key.keysym.mod & KMOD_ALT)
						{
							//Fullscreen
						}
						break;
					default:
						break;
				}

				switch (event.key.keysym.sym)
				{
					//LIGHT POS CONTROL	
					case SDLK_DOWN:
						light_position[2]+=0.1f;
						break;
					case SDLK_RIGHT:
						light_position[0]+=0.1f;
						break;
					case SDLK_UP:
						light_position[2]-=0.1f;
						break;
					case SDLK_LEFT:
						light_position[0]-=0.1f;
						break;
					case SDLK_a:
						light_position[1]+=0.1f;
						break;
					case SDLK_z:
						light_position[1]-=0.1f;
						break;
					//CAMERA POS CONTROL
					case SDLK_j:
						eye_pos[2]+=0.1f;
						look_at[2]+=0.1f;
						break;
					case SDLK_u:
						eye_pos[2]-=0.1f;
						look_at[2]-=0.1f;
						break;
					case SDLK_k:
						eye_pos[0]+=0.1f;
						look_at[0]+=0.1f;
						break;
					case SDLK_h:
						eye_pos[0]-=0.1f;
						look_at[0]-=0.1f;
						break;
					case SDLK_s:
						eye_pos[1]+=0.1f;
						look_at[1]+=0.1f;
						break;
					case SDLK_x:
						eye_pos[1]-=0.1f;
						look_at[1]-=0.1f;
						break;
				}
				
			
			}
		
			if ( event.type == SDL_MOUSEMOTION )
			{
				//event.motion.x;
				//event.motion.y;
				//event.motion.xrel;
				//event.motion.yrel;
			}
			
			if (( event.type == SDL_MOUSEBUTTONDOWN) | (event.type == SDL_MOUSEBUTTONUP) )
			{
				if (event.button.button==SDL_BUTTON_LEFT)
				{
					//event.button.state;
				}
			}

			// If there is a resize event.
			if ( event.type == SDL_VIDEORESIZE )
			{
				// Request SDL to resize the window to the size -
				// - and depth etc. that we specify.

				if ( SDL_SetVideoMode(event.resize.w, event.resize.h, 32, flags) == NULL )
				{
					SDL_Quit();
					return 2;
				}
				initGL(event.resize.w, event.resize.h);
			}
		}
		
	}
	//Exit routines
	SDL_Quit();

	return 0;
}

