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

    #ifdef __WIN32__
        #include <windows.h>
    #endif
    #include <GL/glut.h>
    #include <stdio.h>
    #include <stdlib.h>    
    #include <math.h>
    #include <time.h>

    #ifndef bool
        typedef int bool;
        #define false (1==2)
        #define true (1==1)
    #endif
    
//------------------------------------------------------------------------------
// Constantes & Variables
//------------------------------------------------------------------------------    
    
    #define EXIT    {fclose(fichier); return -1;}
    #define CTOI(C) (*(int*)&C)
    
    #define WIDTH   640           // largeur
    #define HEIGHT  480           // hauteur
    
    GLuint  Name[50];             // nombre de textures
    int     Size=16;              // taille des boules
    int     CurrentW;             // largeur actuelle de la fenêtre
    int     CurrentH;             // hauteur actuelle de la fenêtre
    
    struct struct_jeu
     {
        float   pourc;            // pourcentage rempli
        long int  score;          // score du joueur
        int     boules;           // boules durant la partie
        int     zones;            // nombre de zones créées
        float   load;             // chargement du niveau (> 0 : gagné | < 0 : perdu )
     } jeu;
        
    struct struct_boule
     {
        float   posX;             // positon X de la boule
        float   posY;             // positon Y de la boule
        float   movX;             // déplacement X de la boule
        float   movY;             // déplacement Y de la boule
        int     zone;             // zone où est la boule
     } boules[100];               // nombre maximal de boule
     
    struct struct_zone
     {
        int     debX;             // origine X d'une zone
        int     debY;             // origine Y d'une zone
        int     finX;             // longeur X d'une zone
        int     finY;             // hauteur Y d'une zone
     } zones[100];                // nombre maximal de zone (autant que de boules)
     
    // Gestion du temps
    struct TEMPS
    {
        int tps;                // temps depuis le début
        int 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;
     
//------------------------------------------------------------------------------
// Affiche la chaine à l'écran
//------------------------------------------------------------------------------

void render_string(void* font, const char* string)
 {
    char* p = (char*) string;
    while (*p != '\0') glutBitmapCharacter(font, *p++);
 }   
          
//------------------------------------------------------------------------------
// Initialisation du jeu
//------------------------------------------------------------------------------

void   Init_Game ( )
 {
    int     x;

    jeu.zones = 1;
    jeu.pourc = 0;
    jeu.load = 0;
    
    zones[0].debX = 0;
    zones[0].debY = 0;
    zones[0].finX = WIDTH;
    zones[0].finY = HEIGHT;        
            
    for ( x = 0; x < jeu.boules; x++)
     {
        boules[x].posX = (int)(rand( )%(WIDTH - Size));
        boules[x].posY = (int)(rand( )%(HEIGHT - Size));
        boules[x].movX = (int)(rand( )%10-5);
        boules[x].movY = (int)(rand( )%10-5);
        boules[x].zone = 0;
     }
    char msg[255];
    sprintf (msg, "Game Balls | %02.2f%% | %ld points | %d %s | %d %s | by BeLZeL", jeu.pourc, jeu.score, jeu.boules, (jeu.boules<=1)?"balle":"balles", jeu.zones, (jeu.zones<=1)?"zone":"zones" );
    glutSetWindowTitle(msg);
 }
     
//------------------------------------------------------------------------------
// Gestion des boutons de la souris
//------------------------------------------------------------------------------

void    SourisClic ( int button, int state, int MouseX, int MouseY )
 {
    int     Czone=-1;               // quelle zone cliquée, si Czone = -1, alors la zone n'est pas valide
    int     Count=jeu.zones;        // combien de zones affichées
    int     x, y;                   // pour les boucles
    
    MouseX = (int) ( MouseX * WIDTH / (float) ( CurrentW ) );
    MouseY = (int) ( MouseY * HEIGHT / (float) ( CurrentH ) );
    
    bool gauche, droite, haut, bas, milieu;
    // true : balle
    // false : pas de balle
    gauche = droite = haut = bas = milieu = false;
    
    if ( jeu.load == 0 )
    {
        // on détecte la zone cliquée
        for ( x = 0; x < jeu.zones; x++ )
         {
            // par rapport à la souris
            if ( ( (MouseX > zones[x].debX) && (MouseX < zones[x].finX) ) && ( (MouseY > zones[x].debY) && (MouseY < zones[x].finY) ) )
                Czone = x;
            // par rapport aux boules
            for ( y = 0; y < jeu.boules; y++ )
                if ( ( (boules[y].posX > zones[x].debX) && (boules[y].posX < zones[x].finX) ) && ( (boules[y].posY > zones[x].debY) && (boules[y].posY < zones[x].finY) ) )
                    boules[y].zone = x;
         }

        // clic gauche - trait vertical
        if ( (button==GLUT_LEFT_BUTTON) && (state==GLUT_DOWN) && (Czone != -1) )
         {
            // recherche les positions des balles par rapport au clic
            for ( x = 0; x < jeu.boules; x++ )
             {
                if (boules[x].zone == Czone)
                 {
                    if ((MouseX > boules[x].posX + Size) && (zones[Czone].debX < boules[x].posX + Size) && (zones[Czone].finX > boules[x].posX + Size) && (!gauche)) gauche = true;
                    if ((MouseX < boules[x].posX) && (zones[Czone].debX < boules[x].posX) && (zones[Czone].finX > boules[x].posX) && (!droite)) droite = true;
                    if ((MouseX > boules[x].posX) && (MouseX <= boules[x].posX + Size) && (!milieu)) milieu = true;
                 }
             }

            // définition de la nouvelle zone
            if ((gauche) && (droite))
             {
                zones[Count].debY = zones[Czone].debY;
                zones[Count].finX = zones[Czone].finX;
                zones[Count].finY = zones[Czone].finY;
                zones[Count].debX = MouseX+1;
                zones[Czone].finX = MouseX-1;
                jeu.zones++;
             }
            else
             {
                if (gauche) zones[Czone].finX = MouseX;
                if (droite) zones[Czone].debX = MouseX;
             }
            if (milieu) jeu.load=-1;
         }

        // clic droit - trait horizontal
        if ( (button==GLUT_RIGHT_BUTTON) && (state==GLUT_DOWN) && (Czone != -1) )
         {
            // recherche des positions des balles par rapport au clic
            for ( x = 0; x < jeu.boules; x++ )
             {
                if (boules[x].zone == Czone)
                 {
                    if ((MouseY > boules[x].posY + Size) && (zones[Czone].debY < boules[x].posY + Size) && (zones[Czone].finY > boules[x].posY + Size) && (!haut)) haut = true;
                    if ((MouseY < boules[x].posY) && (zones[Czone].debY < boules[x].posY) && (zones[Czone].finY > boules[x].posY) && (!bas)) bas = true;
                    if ((MouseY > boules[x].posY) && (MouseY <= boules[x].posY + Size) && (!milieu)) milieu = true;
                 }
             }

            // définition de la nouvelle zone
            if ((haut) && (bas))
             {
                zones[Count].debX = zones[Czone].debX;
                zones[Count].finX = zones[Czone].finX;
                zones[Count].finY = zones[Czone].finY;
                zones[Count].debY = MouseY+1;
                zones[Czone].finY = MouseY-1;
                jeu.zones++;
             }
            else
             {
                if (haut) zones[Czone].finY = MouseY;
                if (bas)  zones[Czone].debY = MouseY;
             }
            if (milieu) jeu.load=-1;
         }

        if (Czone != -1)
         {
            char msg[255];
            long int SurfZone = 0;
            long int old_pourc = jeu.pourc;

            for ( x = 0; x < jeu.zones; x++)
                SurfZone += (zones[x].finX - zones[x].debX) * (zones[x].finY - zones[x].debY);

            jeu.pourc = 100-SurfZone/(float)(640*480*0.01);
            jeu.score += (int)(jeu.pourc - old_pourc);
            sprintf (msg, "Game Balls | %02.2f%% | %ld points | %d %s | %d %s | by BeLZeL", jeu.pourc, jeu.score, jeu.boules, (jeu.boules<=1)?"balle":"balles", jeu.zones, (jeu.zones<=1)?"zone":"zones" );
            glutSetWindowTitle(msg);
         }

        // partie finie, on recommence en plus dur ;)
        if ((jeu.pourc > 75) && (jeu.load==0))
         {
            jeu.boules+=2;
            jeu.load=1;
         }
     }
 }     

//------------------------------------------------------------------------------
// Dessine les boules
//------------------------------------------------------------------------------

void   Draw_Boules ( )
 {
    int     x, y;
    
    glBindTexture(GL_TEXTURE_2D, Name[0] );
    for ( x = 0; x < jeu.boules; x++)
     {
        // affichage la boule à l'écran
        glBegin(GL_QUADS);
            glTexCoord2d ( 0, 1 ); glVertex2d(      boules[x].posX,      boules[x].posY );
            glTexCoord2d ( 0, 0 ); glVertex2d(      boules[x].posX, boules[x].posY+Size );
            glTexCoord2d ( 1, 0 ); glVertex2d( boules[x].posX+Size, boules[x].posY+Size );
            glTexCoord2d ( 1, 1 ); glVertex2d( boules[x].posX+Size,      boules[x].posY );
        glEnd();
        
        // Collisions entre les boules
        for ( y = 0; y < jeu.boules; y++)
            if ( pow(boules[x].posX+boules[x].movX*temps.ecart*100-boules[y].posX-boules[y].movX*temps.ecart*100,2) + pow(boules[x].posY+boules[x].movY*temps.ecart*100-boules[y].posY-boules[y].movY*temps.ecart*100,2) <= pow(Size, 2) )
             {
                int t;
                t = boules[x].movX;
                boules[x].movX = boules[y].movX;
                boules[y].movX = t;

                t = boules[x].movY;
                boules[x].movY = boules[y].movY;
                boules[y].movY = t;
             }
             
        // Collisions entre les zones             
        // gauche et droite
        if (( boules[x].posX + boules[x].movX*temps.ecart*100 <= zones[boules[x].zone].debX ) || ( boules[x].posX + boules[x].movX*temps.ecart*100 >= zones[boules[x].zone].finX - Size ))
            boules[x].movX = -boules[x].movX;
        boules[x].posX += boules[x].movX*temps.ecart*100;

        // haut et bas
        if (( boules[x].posY + boules[x].movY*temps.ecart*100 <= zones[boules[x].zone].debY ) || ( boules[x].posY + boules[x].movY*temps.ecart*100 >= zones[boules[x].zone].finY - Size ))
            boules[x].movY = -boules[x].movY;
        boules[x].posY += boules[x].movY*temps.ecart*100;
     }
 }
 
//------------------------------------------------------------------------------
// 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
    int     i;
                                                    
    // 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
    for(i=0; i<imageSize;i+=bpp/8)              // Loop Through The Image Data
     {                                              // Swaps The 1st And 3rd Bytes ('R'ed and 'B'lue)
        int 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); // Linear Filtered (ca lisser les textures mais moins ien que le TriLinear Filtering)
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // Linear Filtered
    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);

    return Name[nb];                                // Texture Building Went Ok, Return True
 }
    
//------------------------------------------------------------------------------
// ReSize Function
//------------------------------------------------------------------------------

void    ReshapeGL ( int Width, int Height )
 {    
    if (Height==0) Height=1;
    CurrentW = Width;
    CurrentH = Height;
    glViewport ( 0, 0, Width, Height );
    glMatrixMode ( GL_PROJECTION );
    glLoadIdentity ( );
    glOrtho ( 0, WIDTH, HEIGHT, 0, 0, 0.2 );               // Fait un écran Ortho
 }

//------------------------------------------------------------------------------
// Initialisation Function
//------------------------------------------------------------------------------

void    InitGL ( )
 {
    glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f );        // Black Background
    glClearDepth(1.0f);                             // Configuration de la profondeur du buffer    
    
    glDepthFunc ( GL_LEQUAL );
    glEnable ( GL_DEPTH_TEST );

    glAlphaFunc ( GL_GREATER, 0.5f );
    glEnable ( GL_ALPHA_TEST );
        
    glEnable ( GL_TEXTURE_2D );
    glEnable ( GL_COLOR );                          // Colors Enabled
 }

//------------------------------------------------------------------------------
// Draw Function
//------------------------------------------------------------------------------

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

    glDisable ( GL_COLOR );
    glEnable ( GL_TEXTURE_2D );
    
    if (jeu.load != 0)
     {
        glMatrixMode ( GL_MODELVIEW );
        glLoadIdentity ( );             
        glPushMatrix ( );

        // gagné sinon perdu
        if (jeu.load > 0)
         {                    
            glColor3f ( 1,1,1 );
            // BACKGROUND
            glBindTexture ( GL_TEXTURE_2D, Name[1] );
            glBegin ( GL_QUADS );
                glTexCoord2d ( 0, 1 ); glVertex2d(     0,      0 );
                glTexCoord2d ( 0, 0 ); glVertex2d(     0, HEIGHT );
                glTexCoord2d ( 1, 0 ); glVertex2d( WIDTH, HEIGHT );
                glTexCoord2d ( 1, 1 ); glVertex2d( WIDTH,      0 );    
            glEnd ( );
            
            glDisable ( GL_TEXTURE_2D );
            glEnable ( GL_COLOR );            
         }
        else
         {         
            glDisable ( GL_TEXTURE_2D );
            glEnable ( GL_COLOR );
            
            // LE FOND NOIR
            glColor3d ( 0, 0, 0 );    
            glBegin ( GL_QUADS );
                glVertex2d( 0, 0 );
                glVertex2d( WIDTH, 0 );
                glVertex2d( WIDTH, HEIGHT );
                glVertex2d( 0, HEIGHT );
            glEnd ( );
            
            // LA BORDURE
            glLineWidth(10);
            glColor3f ( 0.9, 0.2, 0.3 );    
            glBegin ( GL_LINE_LOOP );
                glVertex2d(     0,      0 );
                glVertex2d(     0, HEIGHT );
                glVertex2d( WIDTH, HEIGHT );
                glVertex2d( WIDTH,      0 );  
            glEnd ( );        
         }
        
        // 2D - OBLIGATOIRE
        glPopMatrix ( );    
        glMatrixMode ( GL_PROJECTION );
        glLoadIdentity ( );
        glOrtho ( 0, WIDTH, HEIGHT, 0, 0, 0.2 );               // Fait un écran Ortho            
        
        char str[255];
        
        // gagné sinon perdu
        if (jeu.load > 0)
         {
            glColor3f ( 0, 0, 0 );
            sprintf ( str, "Niveau fini ... passage au niveau suivant !" );
            glRasterPos2f ( 10, 100 );
            render_string ( GLUT_BITMAP_HELVETICA_18, str );
            sprintf ( str, "Pourcentage : %2.2f/100!", jeu.pourc);
            glRasterPos2f ( 10, 130 );
            render_string ( GLUT_BITMAP_HELVETICA_18, str );
            sprintf ( str, "Score : %ld", jeu.score);
            glRasterPos2f ( 10, 160 );
            render_string ( GLUT_BITMAP_HELVETICA_18, str );
            sprintf ( str, "Niveau suivant -> %d balles", jeu.boules);
            glRasterPos2f ( 10, 190 );
            render_string ( GLUT_BITMAP_HELVETICA_18, str );
         }
        else
         {
            glColor3f ( 0.9, 0.2, 0.1 );
            sprintf ( str, "Jeu fini (Gaimauveur) !!! Booooouuuuuuuh que c'est dommage !" );
            glRasterPos2f ( 10, 100 );
            render_string ( GLUT_BITMAP_HELVETICA_18, str );
            if (jeu.boules <= 100) sprintf ( str, "Exceptionnel ! %d balles, tu peux maintenant te reposer !", jeu.boules);
            if (jeu.boules <= 30) sprintf ( str, "T'es un dieu, c'est clair ! %d balles quand meme !", jeu.boules);
            if (jeu.boules <= 15) sprintf ( str, "Waou, pas facile d'arriver à %d balles !", jeu.boules);
            if (jeu.boules <= 9) sprintf ( str, "Et %d balles, c'est bien !", jeu.boules);
            if (jeu.boules <= 5) sprintf ( str, "Mais %d balles, c'est po mal !", jeu.boules);
            if (jeu.boules <= 3) sprintf ( str, "Pis %d balles, c'est po terrible !", jeu.boules);
            glRasterPos2f ( 10, 130 );
            render_string ( GLUT_BITMAP_HELVETICA_18, str );            
            sprintf ( str, "Score : %ld", jeu.score);
            glRasterPos2f ( 10, 160 );
            render_string ( GLUT_BITMAP_HELVETICA_18, str );
         }

        (jeu.load > 0)?(jeu.load+=temps.ecart*1000):(jeu.load-=temps.ecart*1000);

        // chargement du nouveau niveau
        if (jeu.load >= 3000)
         {
            Init_Game ( );
            jeu.load=0;            
         }
        // chargement du nouveau niveau
        if (jeu.load <= -4000)
         {
            jeu.boules=1;
            jeu.score = 0;            
            Init_Game ( );
            jeu.load=0;
         }

        glDisable ( GL_COLOR );
        glEnable ( GL_TEXTURE_2D );

        glutSwapBuffers ( );                           // Draw The Frame
        glutPostRedisplay ( );                         // Restart DrawGL     
     }
    else
     {
    
    // BACKGROUND
    glBindTexture ( GL_TEXTURE_2D, Name[1] );
    glBegin ( GL_QUADS );
        glTexCoord2d ( 0, 1 ); glVertex2d(     0,      0 );
        glTexCoord2d ( 0, 0 ); glVertex2d(     0, HEIGHT );
        glTexCoord2d ( 1, 0 ); glVertex2d( WIDTH, HEIGHT );
        glTexCoord2d ( 1, 1 ); glVertex2d( WIDTH,      0 );    
    glEnd ( );

    glDisable ( GL_TEXTURE_2D );
    glEnable ( GL_COLOR );

    for ( x = 0; x < jeu.zones; x++ )
     {
    // LES ZONES
    glColor3d ( 0, 0, 0 );    
    glBegin ( GL_QUADS );
        glVertex2d( zones[x].debX, zones[x].debY );
        glVertex2d( zones[x].finX, zones[x].debY );
        glVertex2d( zones[x].finX, zones[x].finY );
        glVertex2d( zones[x].debX, zones[x].finY );  
    glEnd ( );
     }
    
    // LA BORDURE
    glLineWidth(10);
    glColor3f ( 0.9, 0.2, 0.3 );    
    glBegin ( GL_LINE_LOOP );
        glVertex2d(     0,      0 );
        glVertex2d(     0, HEIGHT );
        glVertex2d( WIDTH, HEIGHT );
        glVertex2d( WIDTH,      0 );  
    glEnd ( );
    glColor3d ( 1, 1, 1 );

    glDisable ( GL_COLOR );
    glEnable ( GL_TEXTURE_2D );
    
    // BOULE
    Draw_Boules ( );
  
    glutSwapBuffers ( );                           // Draw The Frame
    glutPostRedisplay ( );                         // Restart DrawGL
     }
 }

//------------------------------------------------------------------------------
// Main Function
//------------------------------------------------------------------------------

int main( int argc, char *argv[ ], char *envp[ ] )
 {    
    srand ( time ( NULL ) );

    // Create The Window
    glutInit ( &argc, argv );
    glutInitDisplayMode ( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH );
    glutInitWindowSize ( WIDTH, HEIGHT );
    glutInitWindowPosition ( 50, 50 );
    glutCreateWindow ( "" );

    jeu.boules = 1;               // nombre de boule au départ        
    InitGL ( );
    Init_Game ( );
    glutSetCursor(GLUT_CURSOR_FULL_CROSSHAIR);
    LoadTGA ( "ball.tga", 0 );
    LoadTGA ( "wall.tga", 1 );

    glutReshapeFunc ( ReshapeGL );
    glutDisplayFunc ( DrawGL );
    glutMouseFunc ( SourisClic );
    
    glutMainLoop ( );
    return 0;
 }

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