#include <SDL/SDL.h>
#include <gl/gl.h>
#include <gl/glu.h>

#include <cstdlib>

#include <iostream>


// Screen settings
const int WW = 1024;
const int WH = 768;

// Perspective settings
const double RATIO = (double)WW/WH;
const double FOVY = 60;
const double NEAR = 0.1;
const double FAR = 1000;

// Picking options
const int BUFFERSIZE = 512;

// Selection info
bool isSelected[6] = {false};
GLubyte Colors[3] = {255, 0, 0};
bool isDown = false;

// Rotation
double angleZ = 0;
double angleX = 0;
Uint32 last_time;
Uint32 current_time, ellapsed_time;

// Prototypes
void GLInit();
void DoEvents(bool &);
void Render();
void DrawCube();
void DrawLineCube();
void Picking(int, int);
void processHits(int, GLuint *);


/* Entry point */

int main(int argc, char **argv)
{
    SDL_Init(SDL_INIT_VIDEO);
    SDL_WM_SetCaption("Test - Picking et sélection d'objets", NULL);
    SDL_SetVideoMode(WW, WH, 32, SDL_OPENGL);

    GLInit();

    last_time = SDL_GetTicks();

    bool doLoop = true;
    while(doLoop)
    {
        DoEvents(doLoop);
        Render();
    }

    SDL_Quit();

    return 0;
}


/* Init OpenGL */

void GLInit()
{
    /*glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);*/
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
    glEnable(GL_COLOR_MATERIAL);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(FOVY, RATIO, NEAR, FAR);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}


/* Events */
void DoEvents(bool &doLoop)
{
    SDL_Event event;
    while(SDL_PollEvent(&event))
    {
        switch(event.type)
        {
            case SDL_QUIT:
                doLoop = false;
                break;

            case SDL_MOUSEBUTTONDOWN:
                if(event.button.button == SDL_BUTTON_LEFT)
                {
                    Picking(event.button.x, event.button.y);
                    isDown = true;
                    Colors[1] = 255;
                }
                break;

            case SDL_MOUSEBUTTONUP:
                if(event.button.button == SDL_BUTTON_LEFT)
                {
                    isDown = false;
                    Colors[1] = 0;
                }
        }
    }
    if(!isDown)
    {
        int mx, my;
        SDL_GetMouseState(&mx, &my);
        Picking(mx, my);
    }
}


/* Render */

void Render()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    gluLookAt(3, 3, 3, 0, 0, 0, 0, 0, 1);

    current_time = SDL_GetTicks();
    ellapsed_time = current_time - last_time;
    last_time = current_time;

    angleZ += 0.05 * ellapsed_time;
    angleX += 0.05 * ellapsed_time;

    glRotated(angleZ,0,0,1);
    glRotated(angleX,1,0,0);

    glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
    DrawCube();

    glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
    DrawLineCube();

    glFlush();
    SDL_GL_SwapBuffers();
}

/* Draw a basic Cube */

void DrawCube()
{
    glPushMatrix();

    glPushName(0);

    glLoadName(0);
    glBegin(GL_QUADS);
        if(isSelected[0])
            glColor3ubv(Colors);
        else
            glColor3ub(100, 100, 100);

            glVertex3d(1,1,1);
            glVertex3d(1,1,-1);
            glVertex3d(-1,1,-1);
            glVertex3d(-1,1,1);
    glEnd();

    glLoadName(1);
    glBegin(GL_QUADS);
        if(isSelected[1])
            glColor3ubv(Colors);
        else
            glColor3ub(100, 100, 100);

            glVertex3d(1,-1,1);
            glVertex3d(1,-1,-1);
            glVertex3d(1,1,-1);
            glVertex3d(1,1,1);
    glEnd();

    glLoadName(2);
    glBegin(GL_QUADS);
        if(isSelected[2])
            glColor3ubv(Colors);
        else
            glColor3ub(100, 100, 100);

        glVertex3d(-1,-1,1);
        glVertex3d(-1,-1,-1);
        glVertex3d(1,-1,-1);
        glVertex3d(1,-1,1);
    glEnd();

    glLoadName(3);
    glBegin(GL_QUADS);
        if(isSelected[3])
            glColor3ubv(Colors);
        else
            glColor3ub(100, 100, 100);

            glVertex3d(-1,1,1);
            glVertex3d(-1,1,-1);
            glVertex3d(-1,-1,-1);
            glVertex3d(-1,-1,1);
    glEnd();

    glLoadName(4);
    glBegin(GL_QUADS);
        if(isSelected[4])
            glColor3ubv(Colors);
        else
            glColor3ub(100, 100, 100);

            glVertex3d(1,1,-1);
            glVertex3d(1,-1,-1);
            glVertex3d(-1,-1,-1);
            glVertex3d(-1,1,-1);
    glEnd();

    glLoadName(5);
    glBegin(GL_QUADS);
        if(isSelected[5])
            glColor3ubv(Colors);
        else
            glColor3ub(100, 100, 100);

            glVertex3d(1,-1,1);
            glVertex3d(1,1,1);
            glVertex3d(-1,1,1);
            glVertex3d(-1,-1,1);
    glEnd();

    glPopName();

    glPopMatrix();
}

void DrawLineCube()
{
    glPushMatrix();

    glColor3ub(0, 0, 0);

    glBegin(GL_QUADS);
        glVertex3d(1,1,1);
        glVertex3d(1,1,-1);
        glVertex3d(-1,1,-1);
        glVertex3d(-1,1,1);

        glVertex3d(1,-1,1);
        glVertex3d(1,-1,-1);
        glVertex3d(1,1,-1);
        glVertex3d(1,1,1);

        glVertex3d(-1,-1,1);
        glVertex3d(-1,-1,-1);
        glVertex3d(1,-1,-1);
        glVertex3d(1,-1,1);

        glVertex3d(-1,1,1);
        glVertex3d(-1,1,-1);
        glVertex3d(-1,-1,-1);
        glVertex3d(-1,-1,1);

        glVertex3d(1,1,-1);
        glVertex3d(1,-1,-1);
        glVertex3d(-1,-1,-1);
        glVertex3d(-1,1,-1);

        glVertex3d(1,-1,1);
        glVertex3d(1,1,1);
        glVertex3d(-1,1,1);
        glVertex3d(-1,-1,1);
    glEnd();

    glPopMatrix();
}


/* Selecting an element */
void Picking(int x, int y)
{
    GLuint selBuf[BUFFERSIZE];
    glSelectBuffer(BUFFERSIZE, selBuf);


    glRenderMode(GL_SELECT);

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();

    GLint viewport[4];
    glGetIntegerv(GL_VIEWPORT, viewport);
    gluPickMatrix(x, viewport[3] - y, 5, 5, viewport);

    gluPerspective(FOVY, RATIO, NEAR, FAR);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glInitNames();

    gluLookAt(3, 3, 3, 0, 0, 0, 0, 0, 1);

    glRotated(angleZ,0,0,1);
    glRotated(angleX,1,0,0);

    glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
    DrawCube();

	glMatrixMode(GL_PROJECTION);
	glPopMatrix();

	glMatrixMode(GL_MODELVIEW);
	glFlush();


    for(int i = 0; i < 6; i++)
        isSelected[i] = 0;

    int hits = glRenderMode(GL_RENDER);

    if(hits != 0)
        processHits(hits, selBuf);
}

void processHits(int hits, GLuint *selBuf)
{
    GLuint names, *ptr, minZ,*ptrNames, numberOfNames;

    ptr = (GLuint*)selBuf;
    minZ = ~0;

    for (int i = 0; i < hits; i++)
    {
        names = *ptr;
        ptr++;
        if(*ptr < minZ)
        {
            numberOfNames = names;
            minZ = *ptr;
            ptrNames = ptr + 2;
        }
        ptr += names + 2;
    }

    isSelected[*ptrNames] = true;
}
