

#include <windows.h>
#include <cwchar>

#include <cstdio>
#include <cstdlib>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>


#include <cmath>


// On passe aux extensions
// déclartions de tous les pointeurs de fonction
#include "glext.h"



// Les fonctions personnelles
// Compilation séparée !

#include "CTextures.h"
#include "CConsole.h"
#include "CFonte.h"
#include "CCamera.h"
#include "CSphere.h"
//#include "CSkyBox.h"


#include "CMaths.h"
#include "CLog.h"



#define w 800
#define h 600

#define ONE_SECOND 1

/*
typedef struct timeval {
        long    tv_sec;         // seconds
        long    tv_usec;        // and microseconds
}timeval;
*/

int   val_shot = 0;
int   hc=h, wc=w;
char  str[256]="";
bool  fsc=false,set_console=false,fps=true,sphere=false;

CConsole myCons;
CCamera  myCam;
CFonte   fonte;
CLog     c_log;
//CSkyBox  mSky;
CSphere  mySphere;


int   mousex, mousey;
float a,b,anglex,angley;


//---------------------------------------
// Déclaration des textures utilisées
//---------------------------------------

#define NOMBRE_TEXTURES   7

const char *textures[NOMBRE_TEXTURES] = {
   "./textures/asteroid.tga",
   "./textures/earth.tga",
   "./textures/earth_bump.tga",
   "./textures/earth_clouds.tga",
   "./textures/earth_night.tga",
   "./textures/Cursor.tga",
   "./textures/earth_bump.tga",
};

#define ASTEROID     (0)
#define EARTH        (1)
#define EARTH_BUMP   (2)
#define EARTH_CLOUDS (3)
#define EARTH_NIGHT  (4)
#define CURSOR       (5)

#define EARTH_DOT3   (6)

unsigned int id_textures[NOMBRE_TEXTURES];
unsigned int id_dot3=0;

const char fonte_tga[] = "./textures/font.tga";
unsigned int id_fonte;
float           angle  = 0, add_angle=0.01;


//---------------------------------------
// textures du skybox
//---------------------------------------
const char *sky_textures[6] = {
   "./textures/sky/1.tga",
   "./textures/sky/2.tga",
   "./textures/sky/3.tga",
   "./textures/sky/4.tga",
   "./textures/sky/5.tga",
   "./textures/sky/6.tga"
};


//----------------------------------------
// ptr de fonction

PFNGLMULTITEXCOORD1FARBPROC	        glMultiTexCoord1fARB	  = NULL;
PFNGLMULTITEXCOORD2FARBPROC	        glMultiTexCoord2fARB	  = NULL;
PFNGLMULTITEXCOORD3FARBPROC	        glMultiTexCoord3fARB	  = NULL;
PFNGLMULTITEXCOORD4FARBPROC	        glMultiTexCoord4fARB	  = NULL;
PFNGLACTIVETEXTUREARBPROC	          glActiveTextureARB	    = NULL;
PFNGLCLIENTACTIVETEXTUREARBPROC	    glClientActiveTextureARB= NULL;
PFNGLTEXIMAGE3DEXTPROC              glTexImage3DEXT         = NULL;


//----------------------------------------
// proto
void initLights();



//-------------------------------------//
//-------------------------------------//
//    Fonctions utiles globales        //
//-------------------------------------//
//-------------------------------------//



bool Chargement()
{
 c_log << "<u>Commencement du chargement ...</u>";
 // On va charger toutes les textures
 if (!gl_LoadAllTextures(textures, id_textures, 7))
 {
   c_log << "Fermeture du programme ...";
   return false;
 }

 // Là, on charge la texture dot3
 //gl_MakeDot3(id_dot3,textures[EARTH],GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR, GL_REPEAT, GL_REPEAT, 1);


    // Multi texturing
 char *extension = (char*)glGetString(GL_EXTENSIONS);
 if(strstr(extension, "GL_ARB_multitexture") == NULL)
    {
     c_log << "GL_ARB_multitexture n'est pas disponible ...";
     return false;
    }
 
 //c_log.printf ("<b>(EXTENSIONS)</b> %s",extension);

    // On affecte les pointeurs de fonction
	glMultiTexCoord1fARB	= (PFNGLMULTITEXCOORD1FARBPROC)		wglGetProcAddress("glMultiTexCoord1fARB");
	glMultiTexCoord2fARB	= (PFNGLMULTITEXCOORD2FARBPROC)		wglGetProcAddress("glMultiTexCoord2fARB");
	glMultiTexCoord3fARB	= (PFNGLMULTITEXCOORD3FARBPROC)		wglGetProcAddress("glMultiTexCoord3fARB");
	glMultiTexCoord4fARB	= (PFNGLMULTITEXCOORD4FARBPROC)		wglGetProcAddress("glMultiTexCoord4fARB");
	glActiveTextureARB		= (PFNGLACTIVETEXTUREARBPROC)		  wglGetProcAddress("glActiveTextureARB");
	glClientActiveTextureARB= (PFNGLCLIENTACTIVETEXTUREARBPROC)	wglGetProcAddress("glClientActiveTextureARB");		

 fonte.Build(fonte_tga);

 if (!myCons.Init())
 {
   c_log << "CConsole::Init - Erreur lors du chargement des smileys ou initialisation du map";
   return false;
 }

  myCam.Init        ();


  //if (!mSky.Init(sky_textures)){
  //    c_log << "Erreur lors du chargement des textures du skybox...";
  //   return false;
  //}

  //initLights ();

  
  //mySphere.Init(id_textures[EARTH],0,0,0,5,25);

  return true;
}



//---------------------------------------
// Calculer le fps et l'afficher
//---------------------------------------
void gettimeofday(timeval *t, void *__not_used_here__)
{
  t->tv_usec = (long)(clock());
  t->tv_sec += (long)(t->tv_usec / 1000);
}

void CalculateFrameRate()
{
    static float framesPerSecond    =  0.0f;
    static long lastTime            =  0;
    static char strFrameRate[50]    = "";

    struct timeval currentTime;

  	currentTime.tv_sec  = 0;
  	currentTime.tv_usec = 0; 
    gettimeofday(&currentTime, NULL);
    ++framesPerSecond;

    if( currentTime.tv_sec - lastTime >= ONE_SECOND ) {
        lastTime = currentTime.tv_sec;
        //if (fsc) {
           sprintf(str,"FPS: %3.1f",framesPerSecond);
        //}
        //else {
           //sprintf(strFrameRate, "Espace - FPS: %3.1f", (framesPerSecond));
           //glutSetWindowTitle(strFrameRate); // On affiche le framerate dans
                                             // dans la barre de titre
        //}
        framesPerSecond = 0.0;
    }
}


//---------------------------------------
// Dessiner la souris
//---------------------------------------
void gl_DrawMouse(int x, int y)
{
  glColor4f (1.0,1.0,1.0,1.0); // On remet la couleur a
                               // sa puissance maximale
  glEnable      (GL_BLEND);
  glBlendFunc   (GL_SRC_ALPHA,GL_ONE);

  glDisable (GL_LIGHTING);			//positionnement de la lumière avec
  glDisable (GL_LIGHT0  );			//ca y est.


	glBindTexture(GL_TEXTURE_2D, id_textures[CURSOR]);
	glDisable(GL_DEPTH_TEST);
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
		glLoadIdentity();
		glOrtho(0,wc,0,hc,-1,1);
		glMatrixMode(GL_MODELVIEW);

		glPushMatrix();  // sauvegarde de la matrice !!!!
			glLoadIdentity();
			glTranslated(x,hc - y,0);  // la position est bien TAILLE_Y - y !!!!
			                           // ( ct mon erreur :'( )
              
              // la texture fait 64x32 .. fé un peu grand pour
              // ce curseur ... on rétréci de moitié alors !
              glScalef(0.5,0.5,0);
              glBegin (GL_QUADS);
            		glTexCoord2i(0, 0);  glVertex2i(0,   0);
            		glTexCoord2i(1, 0);  glVertex2i(64,  0);
            		glTexCoord2i(1, 1);  glVertex2i(64, 32);
            		glTexCoord2i(0, 1);  glVertex2i(0,  32);
              glEnd ();

			glMatrixMode(GL_PROJECTION);
		glPopMatrix();
		glMatrixMode(GL_MODELVIEW);

	glPopMatrix(); // On recharge la matrice sauvegardée prédédement
	glEnable(GL_DEPTH_TEST);

  glDisable(GL_BLEND);
  
  glEnable (GL_LIGHTING);			//positionnement de la lumière avec
  glEnable (GL_LIGHT0  );			//ca y est.

}

//-----------------------------------------------------------------------------
// gl_DrawPlanet()
// dessin d'une planete par quadrique ...
//-----------------------------------------------------------------------------
void gl_DrawPlanet(GLuint texture_base,GLuint texture_nuages, GLuint texture_bump,bool effet_bump, float rotate)
{
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ZERO); // on blend sur la couche alpha pour qu'elle
                                      // ne soit pas du tout afficher (GL_ZERO)
      // Faire un truc merdique histoire qu'il y ait qqch
      // =)> support de base =)> une sphere texturée
      glRotatef            (-90.0, 1.0, 0.0, 0.0);
      GLUquadricObj*       q = NULL;
      q = gluNewQuadric    ();
      gluQuadricDrawStyle  (q, GLU_FILL);
      gluQuadricNormals    (q, GLU_SMOOTH);
      gluQuadricTexture    (q, GL_TRUE);
      gluQuadricOrientation(q, GLU_OUTSIDE);
      glBindTexture(GL_TEXTURE_2D,  texture_base);
      gluSphere            (q, 2.0f, 35, 35);
      gluDeleteQuadric     (q);
      
      glRotatef(rotate,0.0,1.0,0.0);
  
      q = gluNewQuadric    ();
      glBlendFunc          (GL_ONE, GL_ONE_MINUS_SRC_COLOR);
      gluQuadricDrawStyle  (q, GLU_FILL);
      gluQuadricNormals    (q, GLU_SMOOTH);
      gluQuadricTexture    (q, GL_TRUE);
      gluQuadricOrientation(q, GLU_OUTSIDE);
      glBindTexture        (GL_TEXTURE_2D,  texture_nuages);
      gluSphere            (q, 2.06f, 35, 35 );
      gluDeleteQuadric     (q);

  glDisable(GL_BLEND);
  glEnable(GL_TEXTURE_2D);
}


//-----------------------------------------------------------------------------
// initLights()
// Init  des lumières ...
//-----------------------------------------------------------------------------
void initLights()
{
	GLfloat mat_ambient[] = { 1.0f, 1.0f, 1.0f, 1.0f };
	GLfloat mat_diffuse[] = { 1.0f, 1.0f, 1.0f, 1.0f };
	glMaterialfv( GL_FRONT, GL_DIFFUSE, mat_diffuse );
	glMaterialfv( GL_FRONT, GL_AMBIENT, mat_ambient );
 
  	// Set light 0 to be a simple, bright directional light to use
    // on the mesh that will represent light 2
	GLfloat diffuse_light0[] = { 1.0f, 1.0f, 1.0f, 1.0f };
	GLfloat position_light0[] = { 0.5f, -0.5f, -0.5f, 0.0f };
	glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuse_light0 );
	glLightfv( GL_LIGHT0, GL_POSITION, position_light0 );

	// Set light 1 to be a simple, faint grey directional light so 
  // the walls and floor are slightly different shades of grey
	GLfloat diffuse_light1[] = { 0.5f, 0.5f, 0.5f, 1.0f };
	GLfloat position_light1[] = { 0.3f, -0.5f, 0.2f, 0.0f };
	//GLfloat position_light1[] = { 0.3f, -0.5f, -0.2f, 0.0f };
	glLightfv( GL_LIGHT1, GL_DIFFUSE, diffuse_light1 );
	glLightfv( GL_LIGHT1, GL_POSITION, position_light1 );

	// Light #2 will be the demo light used to light the floor and walls. 
	// It will be set up in render() since its type can be changed at
    // run-time.

	// Enable some dim, grey ambient lighting so objects that are not lit 
  // by the other lights are not completely black.
	GLfloat ambient_lightModel[] = { 0.5f, 0.5f, 0.5f, 1.0f };
	glLightModelfv( GL_LIGHT_MODEL_AMBIENT, ambient_lightModel );
}



//-------------------------------------//
//-------------------------------------//
//    Fonctions Principales d'OpenGL   //
//-------------------------------------//
//-------------------------------------//




//---------------------------------------
// Redessiner tout le buffer
//---------------------------------------
static void f_redraw()
{
  angle += add_angle;
/*
  glPushAttrib( GL_LIGHTING_BIT );
	
  GLfloat diffuse_light2[] = { 1.0f, 1.0f, 1.0f, 1.0f };
	GLfloat position_light2[] = { 4.5f, 4.5f, 4.5f, 1.0f };
	GLfloat linearAttenuation_light2[] = { 0.4f };
	glLightfv( GL_LIGHT2, GL_DIFFUSE, diffuse_light2 );
	glLightfv( GL_LIGHT2, GL_POSITION, position_light2 );
	glLightfv( GL_LIGHT2, GL_LINEAR_ATTENUATION , linearAttenuation_light2 );
*/
  glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
  glLoadIdentity();

  myCam.Rendering();
/*
  glDisable( GL_LIGHT0 );
  glEnable( GL_LIGHT1 );
  glEnable( GL_LIGHT2 );
*/

  glPushMatrix();
  glRotatef (angle,0,1,0);

  //---------------------------------------
  // Animation de toute la scène
  //---------------------------------------
  float scale   [2] = {1,1};
  //---------------------------------------

  gl_DrawPlanet  (id_textures[EARTH], id_textures[EARTH_CLOUDS], id_dot3 ,1, angle/8);

  //if (sphere)
      mySphere.Render ();
 // else
  //    gl_DrawPlanet  (id_textures[EARTH], id_textures[EARTH_CLOUDS], id_dot3 ,1, angle/8);

  //mSky.Render     ();
 
  gl_DrawMouse   (mousex, mousey);

  if (set_console)
     myCons.Render();

/*
    glEnable( GL_LIGHT0 );
    glDisable( GL_LIGHT1 );
    glDisable( GL_LIGHT2 );
*/
  if (fps) 
  {
    CalculateFrameRate ();
    glDisable (GL_LIGHTING);
    glDisable (GL_LIGHT0  );
        fonte.Print(wc-160,16,0,scale,str);
    glEnable  (GL_LIGHTING);
    glEnable  (GL_LIGHT0  );
  }


  glFlush();
  glutSwapBuffers();
}


//---------------------------------------
// Redimensionnement, cette fonction
// sert aussi a initialiser OpenGL
// etc.
//---------------------------------------
static void f_reshape(int wl,int hl)
{
  hc = hl; wc = wl;
  glViewport    (0,0,wl,hl);
  glMatrixMode  (GL_PROJECTION);
  glLoadIdentity();

  if ( hl==0 )
    gluPerspective (45,(float)wl, 1.0,10000.0);
  else
    gluPerspective (45,(float)wl/(float)hl, 1.0, 10000.0);

  glMatrixMode   (GL_MODELVIEW);
 	glEnable       (GL_TEXTURE_2D);
  glEnable       (GL_DEPTH_TEST);
  glLoadIdentity ();
}


//---------------------------------------
// Intération des KEY_SPECIAL avec le
// programme
//---------------------------------------
static void f_key(unsigned char key,int x,int y)
{
  if (key == 27  )      exit(0);

  if (set_console) {
     myCons.PushKey(key);
     return;
  }

  if (key == 'w' )      myCam.MoveForward (-0.01);
  if (key == 'x' )      myCam.MoveForward ( 0.01);

  if (key == ' ' )      myCam.Init        (     );
  if (key == 'r' )      add_angle        += 0.1;
  if (key == 'f' )      add_angle        -= 0.1;
  
  if (key == 13  )      add_angle             = 0;

  if (key == 'a' )      myCam.MoveForward ( -1.0);
  if (key == 'q' )      myCam.MoveForward (  1.0);

  if (key == 's' )      myCam.RotateZ     ( -1.0);
  if (key == 'd' )      myCam.RotateZ     (  1.0);
  
  
  if (key == '1' )      sphere ^= true;


}

//---------------------------------------
// Intération des KEY_SPECIAL avec le 
// programme
//---------------------------------------
static void f_special(int k,int x,int y)
{

      if ( k == GLUT_KEY_UP    )   myCam.RotateX     ( 2.0);
      if ( k == GLUT_KEY_DOWN  )   myCam.RotateX     (-2.0);
      if ( k == GLUT_KEY_LEFT  )   myCam.RotateY     (-2.0);
      if ( k == GLUT_KEY_RIGHT )   myCam.RotateY     ( 2.0);
      if ( k == GLUT_KEY_F1    )   glutFullScreen();

      if ( k == GLUT_KEY_F8    )   set_console ^= true;

}


//---------------------------------------
// Gestion de la sourie, uniquement
// une affectation des positions et des
// boutons
//---------------------------------------
static void f_mouse(int button, int state, int x, int y)
{
   mousex = x;
   mousey = y;
}

//---------------------------------------
// Gestion du déplacement de la sourie
//---------------------------------------
static void f_motion(int x, int y)
{
   anglex=0;
   angley=0;
   anglex += (a=(mousex - x));
   angley += (b=(mousey - y));

   myCam.RotateX(angley*-.1);
   myCam.RotateY(anglex*-.1);

   mousex = x;
   mousey = y;

}


//---------------------------------------
// Gestion de la sourie lorsqu'il n'y
// a pas de clics
//---------------------------------------
static void f_motion_wo_click(int x, int y)
{
   anglex=0;
   angley=0;
   anglex += (a=(mousex - x));
   angley += (b=(mousey - y));
   mousex = x;
   mousey = y;
}


//---------------------------------------
// C'est ici que l'on gère les valeurs
// retournée par glutCreateMenu
//---------------------------------------
static void f_menu(int value)
{
	switch (value)
	{
		case 0x10:
      fsc = (!fsc ? 1 : 0);
      glutFullScreen();
			return;

		case 0x20:
			exit(0);

    case 0x60:
         break;
    case 0x70:
         set_console ^= true;
         break;
		case 0x50:
         break;

    case 0x80:
         break;

	}
}

//---------------------------------------
// Creation du menu et du sub menu,
// le sous-menu étant simplement un menu
// attaché a une valeur du menu parent.
//---------------------------------------
void glutMakeMyMenus() {

	int menu,sub;

  sub = glutCreateMenu  (f_menu);
        glutAddMenuEntry ("   Plein écran     ",  0x10);
        glutAddMenuEntry ("  Switch lumière   ",  0x50);
        glutAddMenuEntry ("  Switch étoiles   ",  0x60);

	menu = glutCreateMenu  (f_menu);
        glutAddMenuEntry ("    [-EzSpace-]   ",   0xFF);
        glutAddSubMenu   ("     Affichages    ",  sub);
        glutAddMenuEntry ("      Console      ",  0x70);
        glutAddMenuEntry ("       Aide        ",  0x80);
    	  glutAddMenuEntry ("      Sortie       ",  0x20);
	glutAttachMenu         (GLUT_MIDDLE_BUTTON);

}

//---------------------------------------
// Fonction principale du programme
//---------------------------------------
int main(int argc, char *argv[])
{
  c_log << "EzConsole...";
  glutInitWindowSize     (w,h);
  glutInitDisplayMode    (GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA | GLUT_MULTISAMPLE);
  glutCreateWindow       (":: EzSpace :: version-dev ::");

  if (Chargement())
  {
    ShowCursor(false);
    // Boucle principale du prog OpenGL
        glutDisplayFunc        (f_redraw);
        glutReshapeFunc        (f_reshape);
        glutKeyboardFunc       (f_key);
        glutMouseFunc          (f_mouse);
        glutMotionFunc         (f_motion);
        glutPassiveMotionFunc  (f_motion_wo_click);
        glutSpecialFunc        (f_special);
        glutIdleFunc           (f_redraw);

        glutMakeMyMenus        ();
    glutMainLoop           ();
    ////myCons.print();
  }

  c_log << "That's all Folks ! ;-)";
  return 0;
}

