//------------------------------------------------------------------------------
// Includes                          
//------------------------------------------------------------------------------

    #include <windows.h>                        // Header pour les Applications Windows
    #include <stdlib.h>
    #include <stdio.h>                          // Header d'Entrée/Sortie Standard
    #include <GL/glut.h>                        // Header OpenGL Utility Toolkit (GLUT)
    #include <string.h>                         // Header pour sprintf

//------------------------------------------------------------------------------
// Constantes
//------------------------------------------------------------------------------

    #define EXIT {fclose(fichier);return -1;}   // ferme un fichier
    #define CTOI(C) (*(int*)&C)                 // récupère en int un nombre pointé par un char*

//------------------------------------------------------------------------------
// Variables
//------------------------------------------------------------------------------

    double  a=0;                                // pour la rotation
    int     id_tex=1, id_a1=4, id_a2=1, id_b1=1, id_b2=1, id_d=3;
    
    GLenum  TabAlpha[8] = {GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_ALWAYS};
    GLenum  TabBlend[9] = {GL_ZERO, GL_ONE, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA_SATURATE};
    GLenum  TabDepth[8] = {GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_ALWAYS};

    char    *TabTexName[7] = {"grille.tga", "shotgun.tga", "sky.tga", "sol2.tga", "crosshd.tga", "fireboule.tga", "fireboule2.tga" };
    char    *TabAlphaName[8] = {"GL_NEVER", "GL_LESS", "GL_EQUAL", "GL_LEQUAL", "GL_GREATER", "GL_NOTEQUAL", "GL_GEQUAL", "GL_ALWAYS"};
    char    *TabBlendName[9] = {"GL_ZERO", "GL_ONE", "GL_DST_COLOR", "GL_ONE_MINUS_DST_COLOR", "GL_SRC_ALPHA", "GL_ONE_MINUS_SRC_ALPHA", "GL_DST_ALPHA", "GL_ONE_MINUS_DST_ALPHA", "GL_SRC_ALPHA_SATURATE"};
    char    *TabDepthName[8] = {"GL_NEVER", "GL_LESS", "GL_EQUAL", "GL_LEQUAL", "GL_GREATER", "GL_NOTEQUAL", "GL_GEQUAL", "GL_ALWAYS"};

    GLuint  Name[50];
    int     DepthOn=1, BlendOn=1, AlphaOn=1;
    
    // Gestion du temps
    struct TEMPS
    {
        int tps;                // temps depuis le début
        float non_ecoule;       // temps non ecoulé pendant la pause
        float ecart;            // ecart de temps entre deux frames
        long int frame;         // nombre de frames depuis le début
        int frame_fps;          // nombre réinitialisé pour les FPS
        int base;               // temps réinitialisé pour les FPS
        int pause;              // pause du temps
        float fps;              // nombre de FPS
    };
    struct TEMPS temps;

//------------------------------------------------------------------------------
// Charge une image TGA 32 bits non compressée
//------------------------------------------------------------------------------

int    LoadTGA(char *filename, int nb) // Loads A TGA File Into Memory
 {    
    GLubyte TGAheader[12]={0,0,2,0,0,0,0,0,0,0,0,0};// Uncompressed TGA Header
    GLubyte TGAcompare[12];                         // Used To Compare TGA Header
    GLubyte header[6];                              // First 6 Useful Bytes From The Header
    int     imageSize;                              // Used To Store The Image Size When Setting Aside Ram
    int     type;                                   // Set The Default GL Mode To RBGA (32 BPP)
    GLubyte *imageData;                             // Données de l'image, jusqu'à 32 bits
    int     bpp;                                    // Bits Par Pixel de l'image
    int     Width, Height;                          // Taille de l'image
                                                    
    // Lit le fichier et son header
    FILE *fichier = fopen(filename, "rb");          // Open The TGA File                                                    
    if (fread(TGAcompare,1,sizeof(TGAcompare),fichier)!=sizeof(TGAcompare)) EXIT; // Are There 12 Bytes To Read?
    if (memcmp(TGAheader,TGAcompare,sizeof(TGAheader))!=0)                  EXIT; // Does The Header Match What We Want?
    if (fread(header,1,sizeof(header),fichier)!=sizeof(header))             EXIT; // If So Read Next 6 Header Bytes

    // Récupère les infos de l'image
    Width  = header[1] * 256 + header[0];           // Determine The TGA Width    (highbyte*256+lowbyte)
    Height = header[3] * 256 + header[2];           // Determine The TGA Height    (highbyte*256+lowbyte)
    bpp = header[4];                                // Grab The TGA's Bits Per Pixel (24 or 32)
    if (bpp==24) type=GL_RGB;                       // If So Set The 'type' To GL_RGB
    else type=GL_RGBA;                              // If So Set The 'type' To GL_RGBA
    imageSize = Width*Height*bpp/8;                 // Calculate The Memory Required For The TGA Data

    // Charge l'image
    imageData=(GLubyte *)malloc(imageSize);         // Reserve Memory To Hold The TGA Data
    if (fread(imageData, 1, imageSize, fichier)!=imageSize) // Does The Image Size Match The Memory Reserved?
     {
        free ( imageData );
        EXIT;
     }

    fclose (fichier);                               // Close The File
    
    // Inverse R et B
    int     t, i;
    for(i=0; i<imageSize;i+=bpp/8)              // Loop Through The Image Data
     {                                              // Swaps The 1st And 3rd Bytes ('R'ed and 'B'lue)
        t=imageData[i];                             // Temporarily Store The Value At Image Data 'i'
        imageData[i]=imageData[i+2];                // Set The 1st Byte To The Value Of The 3rd Byte
        imageData[i+2]=t;                           // Set The 3rd Byte To The Value In 'temp' (1st Byte Value)
     }

    // Build A Texture From The Data
    glPixelStorei(GL_UNPACK_ALIGNMENT,1);
    glGenTextures(nb, &Name[nb]);                   // Generate OpenGL texture IDs                                             
    glBindTexture(GL_TEXTURE_2D, Name[nb]);         // Bind Our Texture
    
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
    
    glTexImage2D(GL_TEXTURE_2D, 0, type, Width, Height, 0, type, GL_UNSIGNED_BYTE, imageData);
    // target, mipmap lvl, nb coul, larg, haut, large bord, type coul, code compo, img)

    return Name[nb];
 }

//------------------------------------------------------------------------------
// Fonction de redimensionnement
//------------------------------------------------------------------------------

void    Reshape(int w,int h)
 {
    glViewport(0,0,w,h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45,(float)(w)/(float)(h),1,100);
 }

//------------------------------------------------------------------------------
// Fonction d'initialisation
//------------------------------------------------------------------------------

void    InitGL()
 {
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);           // Arrière Plan Noir
    glClearDepth(1.0f);                             // Configuration de la profondeur du buffer

    glAlphaFunc(TabAlpha[id_a1],id_a2*0.1);
    if (AlphaOn) glEnable(GL_ALPHA_TEST);
    else         glDisable(GL_ALPHA_TEST);

    glBlendFunc(TabBlend[id_b1], TabBlend[id_b2]);
    if (BlendOn) glEnable(GL_BLEND);
    else         glDisable(GL_BLEND);
    
    glDepthFunc(TabDepth[id_d]);
    if (DepthOn) glEnable(GL_DEPTH_TEST);
    else         glDisable(GL_DEPTH_TEST);
    
    glShadeModel(GL_SMOOTH);                        // Smooth Shading Activé, il doit lisser les formes (?)

    glEnable(GL_TEXTURE_2D);                        // Textures 2D activées

    glHint(GL_FOG_HINT,GL_NICEST);
    glHint(GL_LINE_SMOOTH_HINT,GL_NICEST);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);
    glHint(GL_POINT_SMOOTH_HINT,GL_NICEST);
    glHint(GL_POLYGON_SMOOTH_HINT,GL_NICEST);
 }
 
//------------------------------------------------------------------------------
// Modifie le titre de la fenêtre
//------------------------------------------------------------------------------

void    Titre ( )
 {
    char    title[255];

    sprintf(title, "Tex = %s (%d)", TabTexName[id_tex-1], id_tex);
    if (AlphaOn)
        sprintf(title, "%s | Alpha = %s (%d) - %0.1f", title, TabAlphaName[id_a1], id_a1, id_a2*0.1);
    if (BlendOn)
        sprintf(title, "%s | Blend = %s (%d) - %s (%d)", title, TabBlendName[id_b1], id_b1, TabBlendName[id_b2], id_b2);
    if (DepthOn)
        sprintf(title, "%s | Depth = %s (%d)", title, TabDepthName[id_d], id_d);
    if ( temps.tps - temps.base > 83 )                     // Si ca fait une seconde
    {
        temps.fps = temps.frame_fps * 1000.0 / ( temps.tps - temps.base );
        temps.base = temps.tps;                            // Le temps de base est actualisé
        temps.frame_fps = 0;                                  // On recommence à compter
        sprintf ( title, "%s | FPS : %4.2f", title, temps.fps );
    }

    glutSetWindowTitle(title);
    InitGL();
 }

//------------------------------------------------------------------------------
// Gestion des touches standard
//------------------------------------------------------------------------------

void    GestionClavier(unsigned char key, int x, int y)  
 {  
    switch (key)
     {
        //-----------------------------
        case 'a' :  // activation ou désactivation APLHA
            AlphaOn = 1-AlphaOn;
            Titre();
        break;
        case 'z' :  // 1er paramètre ALPHA modifié
            if (id_a1 < 7) id_a1++; else id_a1=0;
            Titre();
        break;
        case 'e' :  // 2è paramètre ALPHA modifié
            if (id_a2 < 10) id_a2++; else id_a2=0;
            Titre();
        break;
        //-----------------------------
        case 'b' :  // activation ou désactivation BLEND
            BlendOn = 1-BlendOn;
            Titre();
        break;
        case 'v' :  // 1er paramètre BLEND modifié
            if (id_b1 < 8) id_b1++; else id_b1=0;
            Titre();
        break;
        case 'n' :  // 2è paramètre BLEND modifié
            if (id_b2 < 8) id_b2++; else id_b2=0;
            Titre();
        break;
        case 32 :  // 1er et 2è paramètre BLEND modifiés
            id_b2++;
            if (id_b2 == 9) { id_b2 = 0; id_b1++; }
            if (id_b1 == 8) { id_b2 = 0; id_b1 = 0; }
            Titre();
        break;
        //-----------------------------
        case 'd' :  // activation ou désactivation DEPTH
            DepthOn = 1-DepthOn;
            Titre();
        break;
        case 'f' :  // 2è paramètre DEPTH modifié
            if (id_d < 7) id_d++; else id_d=0;
            Titre();
        break;
        //-----------------------------
        case 13 :  // modification de la texture
            if (id_tex < 7) id_tex++; else id_tex=1;
            Titre();
        break;
        //-----------------------------
        case 27 :  // quitter
            exit(0);
        break;
        default :
            Titre();
        break;
     }
 }

//------------------------------------------------------------------------------
// Fonction de dessin
//------------------------------------------------------------------------------

void    Draw()
 {
    // Mise à jour du temps avec glut
    temps.frame++;
    temps.frame_fps++;
    temps.ecart = (double)(( glutGet ( GLUT_ELAPSED_TIME ) - temps.tps - 0.0001) / 1000);
    temps.tps = glutGet ( GLUT_ELAPSED_TIME );

    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    glMatrixMode(GL_MODELVIEW);

    a+=temps.ecart*100;

    // cube
    glLoadIdentity();
    gluLookAt(3,2,3,0,0,0,0,1,0);
    glRotated(a,0,1,0);
    
    glEnable(GL_TEXTURE_2D);
    glDisable(GL_COLOR);

    glBindTexture(GL_TEXTURE_2D, id_tex);
    glBegin(GL_QUADS);

        glTexCoord2i(0,0);glVertex3i(-1,-1,-1);
        glTexCoord2i(1,0);glVertex3i(+1,-1,-1);
        glTexCoord2i(1,1);glVertex3i(+1,+1,-1);
        glTexCoord2i(0,1);glVertex3i(-1,+1,-1);

        glTexCoord2i(0,0);glVertex3i(-1,-1,+1);
        glTexCoord2i(1,0);glVertex3i(+1,-1,+1);
        glTexCoord2i(1,1);glVertex3i(+1,+1,+1);
        glTexCoord2i(0,1);glVertex3i(-1,+1,+1);

        glTexCoord2i(0,0);glVertex3i(+1,-1,-1);
        glTexCoord2i(1,0);glVertex3i(+1,-1,+1);
        glTexCoord2i(1,1);glVertex3i(+1,+1,+1);
        glTexCoord2i(0,1);glVertex3i(+1,+1,-1);

        glTexCoord2i(0,0);glVertex3i(-1,-1,-1);
        glTexCoord2i(1,0);glVertex3i(-1,-1,+1);
        glTexCoord2i(1,1);glVertex3i(-1,+1,+1);
        glTexCoord2i(0,1);glVertex3i(-1,+1,-1);

        glTexCoord2i(0,0);glVertex3i(-1,+1,-1);
        glTexCoord2i(1,0);glVertex3i(+1,+1,-1);
        glTexCoord2i(1,1);glVertex3i(+1,+1,+1);
        glTexCoord2i(0,1);glVertex3i(-1,+1,+1);

        glTexCoord2i(0,0);glVertex3i(-1,-1,+1);
        glTexCoord2i(1,0);glVertex3i(+1,-1,+1);
        glTexCoord2i(1,1);glVertex3i(+1,-1,-1);
        glTexCoord2i(0,1);glVertex3i(-1,-1,-1);

    glEnd();
    
    glDisable(GL_TEXTURE_2D);
    glEnable(GL_COLOR);

    glBegin(GL_QUADS);
      // derrière
      glColor3f(0,0,0); glVertex3f(-0.1,0.1,-0.1);
      glColor3f(1,0,0); glVertex3f(0.1,0.1,-0.1);
      glColor3f(1,1,0); glVertex3f(0.1,-0.1,-0.1);
      glColor3f(0,1,0); glVertex3f(-0.1,-0.1,-0.1);
      // devant
      glColor3f(0,0,1); glVertex3f(-0.1,0.1,0.1);
      glColor3f(1,0,1); glVertex3f(0.1,0.1,0.1);
      glColor3f(1,1,1); glVertex3f(0.1,-0.1,0.1);
      glColor3f(0,1,1); glVertex3f(-0.1,-0.1,0.1);
      // gauche
      glColor3f(0,0,0); glVertex3f(-0.1,0.1,-0.1);
      glColor3f(0,1,0); glVertex3f(-0.1,-0.1,-0.1);
      glColor3f(0,1,1); glVertex3f(-0.1,-0.1,0.1);
      glColor3f(0,0,1); glVertex3f(-0.1,0.1,0.1);
      // droite
      glColor3f(1,0,0); glVertex3f(0.1,0.1,-0.1);
      glColor3f(1,1,0); glVertex3f(0.1,-0.1,-0.1);
      glColor3f(1,1,1); glVertex3f(0.1,-0.1,0.1);
      glColor3f(1,0,1); glVertex3f(0.1,0.1,0.1);
      // haut
      glColor3f(0,0,0); glVertex3f(-0.1,0.1,-0.1);
      glColor3f(1,0,0); glVertex3f(0.1,0.1,-0.1);
      glColor3f(1,0,1); glVertex3f(0.1,0.1,0.1);
      glColor3f(0,0,1); glVertex3f(-0.1,0.1,0.1);
      // bas
      glColor3f(0,1,0); glVertex3f(-0.1,-0.1,-0.1);
      glColor3f(1,1,0); glVertex3f(0.1,-0.1,-0.1);
      glColor3f(1,1,1); glVertex3f(0.1,-0.1,0.1);
      glColor3f(0,1,1); glVertex3f(-0.1,-0.1,0.1);
    glEnd();
    glColor3f(1,1,1);

    glutSwapBuffers();
    glutPostRedisplay();
 }

//------------------------------------------------------------------------------
// Fonction Principale
//------------------------------------------------------------------------------

int main( int argc, char *argv[ ], char *envp[ ] )
 {
    glutInit(&argc,argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowSize(640,480);
    glutInitWindowPosition(50,50);
    glutCreateWindow("");
    Titre();

    InitGL();

    LoadTGA("grille.tga", 1);
    LoadTGA("shotgun.tga", 1);
    LoadTGA("sky.tga", 1);
    LoadTGA("sol2.tga", 1);
    LoadTGA("crosshd.tga", 1);
    LoadTGA("fireboule.tga", 1);
    LoadTGA("fireboule2.tga", 1);
    
    glutReshapeFunc(Reshape);
    glutDisplayFunc(Draw);
    glutKeyboardFunc(GestionClavier);
    glutMainLoop();

    return 0;
 }

//------------------------------------------------------------------------------
// THE END
//------------------------------------------------------------------------------