/* main.c */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include <GL/glut.h>

#include "vectors.h"
#include "image.h"
#include "trackball.h"

static void init             (void);
static void display_callback (void);
static void mouse_callback   (int button, int state, int x, int y);
static void motion_callback  (int x, int y);
static void special_callback (int key, int x, int y);
static void draw_sky_box     (void);

GLuint cube_map_texture_ID;
typedef struct _GlobalState GlobalState;

struct _GlobalState
{
  float beginx, beginy; /* position of mouse */
  float quat[4];        /* orientation of object */
  float dquat[4];
  float zoom;           /* field of view in degrees */
  int   width, height;  /* window size */
  int   buttons;        /* which buttons are currently pressed */
  float azimuth;
};

GlobalState info =
{
  1.0f, 1.0f,
  { 0.0f, 0.0f, 0.0f, 1.0f },
  { 0.0f, 0.0f, 0.0f, 1.0f },
  80.0f,
  700, 700,
  0,
  0.0f
};

static void
init (void)
{
  int i;
// Liste des faces successives pour la création des textures de CubeMap
GLenum cube_map_target[6] =
{
  GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
  GL_TEXTURE_CUBE_MAP_POSITIVE_X,
  GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
  GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
  GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
  GL_TEXTURE_CUBE_MAP_POSITIVE_Z
};

  glEnable (GL_DEPTH_TEST);
  glEnable (GL_LIGHT0);
  glColor3f (1.0, 1.0, 1.0);
  glEnableClientState (GL_VERTEX_ARRAY);
  glEnableClientState (GL_TEXTURE_COORD_ARRAY);

  // Chargement des six textures
  Image * texture_image[6];
  texture_image[0] = image_load_png ("textures/XN.png");
  texture_image[1] = image_load_png ("textures/XP.png");
  texture_image[2] = image_load_png ("textures/YN.png");
  texture_image[3] = image_load_png ("textures/YP.png");
  texture_image[4] = image_load_png ("textures/ZN.png");
  texture_image[5] = image_load_png ("textures/ZP.png");

  // Génération d'une texture CubeMap
  glGenTextures (1, &cube_map_texture_ID);

  // Configuration de la texture
  glBindTexture (GL_TEXTURE_CUBE_MAP, cube_map_texture_ID);

  for (i = 0; i < 6; i++)
    {
      glTexImage2D (cube_map_target[i],
                    0, 3,
                    texture_image[i]->width, texture_image[i]->height,
                    0, GL_RGB, GL_UNSIGNED_BYTE, texture_image[i]->data);

      image_destroy (texture_image[i]);
    }
  glTexParameteri (GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri (GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri (GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP);
  glTexParameteri (GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP);
}

static void
display_callback (void)
{
  GLfloat  m[4][4];

  glClear (GL_DEPTH_BUFFER_BIT);

  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  gluPerspective (info.zoom, (double)info.width / info.height, 0.001, 100);
  add_quats (info.dquat, info.quat, info.quat);

  // Désactivation de l'écriture dans le DepthBuffer
  glDepthMask (GL_FALSE);
  glEnable (GL_TEXTURE_CUBE_MAP);
  glDisable (GL_LIGHTING);
  draw_sky_box ();

  glDepthMask(GL_TRUE);
  glDisable (GL_TEXTURE_CUBE_MAP);

  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();
  glRotatef (15, 1, 0, 0);
  glTranslatef (0,-1,-4);

  glRotatef (info.azimuth, 0, 1, 0);

  build_rotmatrix (m, info.quat);
  glMultMatrixf (&m[0][0]);

  glEnable (GL_LIGHTING);
  glutSolidTorus (0.7, 1, 30, 64);

  glutSwapBuffers ();
}

static void
draw_sky_box (void)
{
  // Cube definition
  GLfloat cube[8][3] =
    {
      { -1, -1, -1 }, { -1, -1,  1}, { -1,  1, -1 }, { -1,  1,  1},
      {  1, -1, -1 }, {  1, -1,  1}, {  1,  1, -1 }, {  1,  1,  1}
    };
  glVertexPointer (3, GL_FLOAT, 0, &cube[0][0]);
  glTexCoordPointer (3, GL_FLOAT, 0, &cube[0][0]);

  GLuint indexes[24] = { 0, 1, 3, 2,  4, 5, 7, 6,
                         0, 1, 5, 4,  2, 3, 7, 6,
                         0, 2, 6, 4,  1, 3, 7, 5 };

  // Réglage de l'orientation
  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();
  glRotatef (15, 1, 0, 0);
  glRotatef (info.azimuth, 0, 1, 0);

  glBindTexture (GL_TEXTURE_CUBE_MAP, cube_map_texture_ID);
  glDrawElements (GL_QUADS, 24, GL_UNSIGNED_INT, indexes);
}

static void
reshape_callback (int width, int height)
{
  info.width  = width;
  info.height = height;

  glViewport (0, 0, width, height);
  glutPostRedisplay ();
}

static void
mouse_callback (int button, int state, int x, int y)
{
  info.beginx = x;
  info.beginy = y;
  info.dquat[0] = info.dquat[1] = info.dquat[2] = 0.0f;
  info.dquat[3] = 1.0f;
  if (state == GLUT_DOWN)
    info.buttons |= (1 << button);
  else
    info.buttons &= ~(1 << button);

  glutPostRedisplay ();
}

static void
motion_callback (int x, int y)
{
  int width = info.width, height = info.height;

  if (info.buttons & (1 << 0))
    { /* drag in progress, simulate trackball */
      trackball (info.dquat,
                 (2.0 * info.beginx -             width) / width,
                 (           height - 2.0 * info.beginy) / height,
                 (          2.0 * x -             width) / width,
                 (           height -           2.0 * y) / height );
    }
  else if (info.buttons & (1 << 2))
    {
      /* zooming drag */
      info.zoom -= ( (y - info.beginy) / height) * 40.0;
      info.zoom = CLAMP (info.zoom, 5.0, 120.0);
    }

  info.beginx = x;
  info.beginy = y;

  glutPostRedisplay ();
}

static void
keyboard_callback (unsigned char key, int x, int y)
{
  if (key == 27)
    exit (0);
}

static void
special_callback (int key, int x, int y)
{
  switch (key)
    {
    case GLUT_KEY_LEFT:
        info.azimuth -= 5;
        break;
    case GLUT_KEY_RIGHT:
        info.azimuth += 5;
        break;
    }
  glutPostRedisplay ();
}

int
main (int argc, char** argv)
{
  glutInit (&argc, argv);
  glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowPosition (0, 0);
  glutInitWindowSize (info.width, info.height);
  glutCreateWindow ("Cube Map");

  init ();

  glutDisplayFunc (display_callback);
  glutReshapeFunc (reshape_callback);
  glutMouseFunc (mouse_callback);
  glutMotionFunc (motion_callback);
  glutKeyboardFunc (keyboard_callback);
  glutSpecialFunc (special_callback);

  glutMainLoop ();

  return 0;
}

