#include <SDL/SDL.h>
#include <GL/gl.h>
#include <GL/glu.h>

#define LARGEUR_ECRAN 800
#define HAUTEUR_ECRAN 600
#define ROT_EPSILON 1

int angleX=0,angleY=0;

enum Rotations
{
   ROTATION_X = 1,
   ROTATION_Y,
   ROTATION_Z
};

enum Faces
{
   X1 = 1,
   X2, Y1, Y2, Z1, Z2
};

class cube
{
 public:

   int x, y, z;
   GLdouble matrix[16];
   void drawSquare();
   void changePositions(int face, int rotation);
   void rotate(int axe);
};

cube rubiks[26];
cube *rotX1[9], *rotX2[9], *rotY1[9], *rotY2[9], *rotZ1[9], *rotZ2[9];
GLdouble rotateX[16], rotateY[16], rotateZ[16];

void dessinerRepere(unsigned int echelle = 1)
{
   glPushMatrix();

   glScalef(echelle, echelle, echelle);
   glBegin(GL_LINES);

   glColor3ub(255, 0, 0);       //X en rouge
   glVertex3d(0, 0, 0);
   glVertex3d(1, 0, 0);

   glColor3ub(0, 255, 0);       //Y en vert
   glVertex3d(0, 0, 0);
   glVertex3d(0, 1, 0);

   glColor3ub(0, 0, 255);       //Z en bleu
   glVertex3d(0, 0, 0);
   glVertex3d(0, 0, 1);

   glEnd();

   glPopMatrix();
}

void cube::drawSquare()
{
/*
   glPushMatrix();
	glMultMatrixd(matrix);
	dessinerRepere();
	glPopMatrix();
*/
   glPushMatrix();
//   glMultMatrixd(rotateX);
   glMultMatrixd(matrix);
   glBegin(GL_QUADS);

   glColor3ub(255, 0, 0);       //face rouge
   glVertex3d(1, 1, 1);
   glVertex3d(1, 1, -1);
   glVertex3d(-1, 1, -1);
   glVertex3d(-1, 1, 1);

   glColor3ub(0, 0, 255);
   glVertex3d(1, 1, 1);
   glVertex3d(-1, 1, 1);
   glVertex3d(-1, -1, 1);
   glVertex3d(1, -1, 1);

   glColor3ub(0, 255, 0);
   glVertex3d(1, 1, 1);
   glVertex3d(1, -1, 1);
   glVertex3d(1, -1, -1);
   glVertex3d(1, 1, -1);

   glColor3ub(255, 255, 0);
   glVertex3d(1, 1, -1);
   glVertex3d(-1, 1, -1);
   glVertex3d(-1, -1, -1);
   glVertex3d(1, -1, -1);

   glColor3ub(0, 255, 255);
   glVertex3d(-1, 1, -1);
   glVertex3d(-1, 1, 1);
   glVertex3d(-1, -1, 1);
   glVertex3d(-1, -1, -1);

   glColor3ub(255, 0, 255);
   glVertex3d(1, -1, 1);
   glVertex3d(1, -1, -1);
   glVertex3d(-1, -1, -1);
   glVertex3d(-1, -1, 1);

   glEnd();
   glPopMatrix();
}



void cube::rotate(int axe)
{
   glPushMatrix();
   switch (axe)
   {
   case ROTATION_X:
      glLoadMatrixd(rotateX);
      break;
   case ROTATION_Y:
      glLoadMatrixd(rotateY);
      break;
   case ROTATION_Z:
      glLoadMatrixd(rotateZ);
      break;
   }

   glMultMatrixd(matrix);
   glGetDoublev(GL_MODELVIEW_MATRIX, matrix);
   glPopMatrix();
}

void cube::changePositions(int face, int rotation)
{
   int i, buffer;

   switch (rotation)
   {
   case ROTATION_X:
      if (face == X1)
      {
         if (x == 0 && y == 0)
         {
            y = 2 - z;
            z = 0;
         }
         else if (x == 0 && y == 2)
         {
            y = 2 - z;
            z = 2;
         }
         else if (x == 0 && y == 1)
         {
            buffer = y;
            y = 2 - z;
            z = buffer;
         }

      }
      else
      {
         if (x == 2 && y == 0)
         {
            y = 2 - z;
            z = 0;
         }
         else if (x == 2 && y == 2)
         {
            y = 2 - z;
            z = 2;
         }
         else if (x == 2 && y == 1)
         {
            buffer = y;
            y = 2 - z;
            z = buffer;
         }

      }
      break;
   case ROTATION_Y:
      if (face == Y1)
      {
         if (y == 0 && z == 0)
         {
            z = 2 - x;
            x = 0;
         }
         else if (y == 0 && z == 2)
         {
            z = 2 - x;
            x = 2;
         }
         else if (y == 0 && z == 1)
         {
            buffer = z;
            z = 2 - x;
            x = buffer;
         }
      }
      else
      {
         if (y == 2 && z == 0)
         {
            z = 2 - x;
            x = 0;
         }
         else if (y == 2 && z == 2)
         {
            z = 2 - x;
            x = 2;
         }
         else if (y == 2 && z == 1)
         {
            buffer = z;
            z = 2 - x;
            x = buffer;
         }
      }
      break;
   case ROTATION_Z:
      if (face == Z1)
      {
         if (z == 0 && y == 0)
         {
            y = x;
            x = 2;
         }
         else if (z == 0 && y == 2)
         {
            y = x;
            x = 0;
         }
         else if (z == 0 && y == 1)
         {
            buffer = x;
            x = y;
            y = buffer;
         }

      }
      else
      {

         if (z == 2 && y == 0)
         {
            y = x;
            x = 2;
         }
         else if (z == 2 && y == 2)
         {
            y = x;
            x = 0;
         }
         else if (z == 2 && y == 1)
         {
            buffer = x;
            x = y;
            y = buffer;
         }

      }
      break;
   }
}
void initRubiksCube()
{
   glMatrixMode(GL_MODELVIEW);  //affichage en projection orthogonale
   int i;
   for (i = 0; i < 13; i++)
   {
      glLoadIdentity();
      glTranslated(-2 + 2 * (i % 3), -2 + ((i / 3) % 3) * 2, -2 + (i / 9) * 2);
      glGetDoublev(GL_MODELVIEW_MATRIX, rubiks[i].matrix);

      rubiks[i].x = i % 3;
      rubiks[i].y = (i / 3) % 3;
      rubiks[i].z = i / 9;
   }
   for (i = 14; i <= 26; i++)
   {
      glLoadIdentity();
      glTranslated(-2 + 2 * (i % 3), -2 + ((i / 3) % 3) * 2, -2 + (i / 9) * 2);
      glGetDoublev(GL_MODELVIEW_MATRIX, rubiks[i - 1].matrix);

      rubiks[i - 1].x = i % 3;
      rubiks[i - 1].y = (i / 3) % 3;
      rubiks[i - 1].z = i / 9;
   }
}


void initRotFaces()
{
}


void CreateRotationMatrix()
{
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glRotated(ROT_EPSILON, 1, 0, 0);
   glGetDoublev(GL_MODELVIEW_MATRIX, rotateX);
   glLoadIdentity();
   glRotated(ROT_EPSILON, 0, 1, 0);
   glGetDoublev(GL_MODELVIEW_MATRIX, rotateY);
   glLoadIdentity();
   glRotated(ROT_EPSILON, 0, 0, 1);
   glGetDoublev(GL_MODELVIEW_MATRIX, rotateZ);
}



void DrawScene()
{
	int i;

      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

      glMatrixMode(GL_MODELVIEW);       //affichage en projection orthogonale
      glLoadIdentity();

      gluLookAt(8, 8, 8, 0, 0, 0, 0, 0, 1);     //definition de la camera: position(xyz), orientation(xyz), verticale(xyz)

		glRotated(angleX, 1, 0, 0);
      glRotated(angleY, 0, 1, 0);

		dessinerRepere(50);

      for (i = 0; i < 26; i++)
         rubiks[i].drawSquare();
      
		glFlush();
      SDL_GL_SwapBuffers();


}
void rotateFace(int face)
{
   int i, frames = 0;
	Uint32 start_time=0,end_time=0,ellapsed_time=0;

   while (frames < 90)
   {
		start_time = SDL_GetTicks();

      for (i = 0; i < 26; i++)
      {
         switch (face)
         {
         case X1:
            if (rubiks[i].x == 0)
               rubiks[i].rotate(ROTATION_X);
            break;
         case X2:
            if (rubiks[i].x == 2)
               rubiks[i].rotate(ROTATION_X);
            break;

         case Y1:
            if (rubiks[i].y == 0)
               rubiks[i].rotate(ROTATION_Y);
            break;
         case Y2:
            if (rubiks[i].y == 2)
               rubiks[i].rotate(ROTATION_Y);
            break;

         case Z1:
            if (rubiks[i].z == 0)
               rubiks[i].rotate(ROTATION_Z);
            break;
         case Z2:
            if (rubiks[i].z == 2)
               rubiks[i].rotate(ROTATION_Z);
            break;
         }
      }
      frames++;

      DrawScene();

      while (SDL_GetTicks()- start_time < 10)
      {
      	wait();
      }

   }
   for (i = 0; i < 26; i++)
   {
      switch (face)
      {
      case X1:
         rubiks[i].changePositions(X1, ROTATION_X);
         break;
      case X2:
         rubiks[i].changePositions(X2, ROTATION_X);
         break;

      case Y1:
         rubiks[i].changePositions(Y1, ROTATION_Y);
         break;
      case Y2:
         rubiks[i].changePositions(Y2, ROTATION_Y);
         break;

      case Z1:
         rubiks[i].changePositions(Z1, ROTATION_Z);
         break;
      case Z2:
         rubiks[i].changePositions(Z2, ROTATION_Z);
         break;
      }
   }
}


int main(int argc, char *argv[])
{
   SDL_Init(SDL_INIT_VIDEO);
   SDL_WM_SetCaption("bac-O-sable opengl: 3D", NULL);
   SDL_SetVideoMode(LARGEUR_ECRAN, HAUTEUR_ECRAN, 32, SDL_OPENGL);

   glEnable(GL_DEPTH_TEST);

   int i,frames = 0;
   bool continuer = true, rotating = false;
   SDL_Event event;

   CreateRotationMatrix();

   glMatrixMode(GL_PROJECTION); //affichage en projection orthogonale
   glLoadIdentity();
   gluPerspective(90, (double) 4 / 3, 0.1, 1000);       //fov, ratio, near, far

   SDL_EnableKeyRepeat(1, 1);
   initRubiksCube();

   while (continuer)
   {
      SDL_WaitEvent(&event);
      switch (event.type)
      {
      case SDL_QUIT:
         continuer = false;
         break;
      case SDL_KEYDOWN:
         switch (event.key.keysym.sym)
         {
         case SDLK_a:
            rotateFace(X1);
            break;
         case SDLK_z:
            rotateFace(Y1);
            break;
         case SDLK_e:
            rotateFace(Z1);
            break;
         case SDLK_q:
            rotateFace(X2);
            break;
         case SDLK_s:
            rotateFace(Y2);
            break;
			case SDLK_d:
            rotateFace(Z2);
            break;
			case SDLK_UP:
            angleX++;
            break;			
			case SDLK_DOWN:
            angleX--;
            break;			
			case SDLK_LEFT:
            angleY--;
            break;
			case SDLK_RIGHT:
            angleY++;
            break;
         }			
      }      

		DrawScene();
   }

   SDL_Quit();

   return 0;
}
