#include <string.h>
#include <math.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <GL/glut.h>
#include "sphere.h"
#include "globj.h"
#include "icosa.h"
#include "carpet.h"
#include "glhand.h"
#include "gloutils.h"
#include "text.h"
#include "fps.h"
#include "vecmath.h"

glcam_t** cameras;
int current_camera;
int nb_cameras;
int selectionmode = 0, selectionx, selectiony, selectiontriangle=-1 ;

fps_t fps ;

char* fpssource (text_t* dummy1, void* dummy2) { 
    char text [] = "%.1f/%.1f/%.1f FPS" ;
    int len=strlen(text)+10 ;
    char buffer [len] ;
    snprintf (buffer, len, text, 
	      1000.0/fps.realframetime, 
	      fps.fpslimit, 
	      1000.0/fps.possibleframetime) ;
    return strdup (buffer) ;
}

void (*fpsbehf[])(text_t*,void*) =   { 
  behaviour_gl_color_white, 
  behaviour_gl_font,
  behaviour_gl_position, 
  behaviour_gl_display 
} ;
void* fpsbehd[] = { NULL,GLUT_BITMAP_TIMES_ROMAN_24,TEXT_GL_BOTTOMRIGHT, NULL};
text_t fpstext = { 
  fpssource, NULL, 
  fpsbehf, fpsbehd, 4
} ;

char* decosource (text_t* text, void* data) {
  return strdup(data) ;
}

void (*decobehf[])(text_t*,void*) = { 
  behaviour_gl_fadeinout, 
  behaviour_gl_font, 
  behaviour_gl_position, 
  behaviour_gl_display 
} ;
void* decobehd[] = { NULL, GLUT_BITMAP_TIMES_ROMAN_24, TEXT_GL_CENTER, NULL } ;
text_t decotext = { decosource, "", decobehf, decobehd, 4 } ;

int fpslimitCallback (void* data, int etype, int edata, int x, int y)
{
  switch (edata) {
  case 'f': fps_setlimit (&fps, fps_getlimit (&fps) - 1) ; return 1;
  case 'F': fps_setlimit (&fps, fps_getlimit (&fps) + 1) ; return 1;
  default: return 0;
  }
}

int dumpzoneCallback (void* data, int etype, int edata, int x, int y)
{
  glhand_dump () ;
  return 1 ;
}

int glmodeCallback (void* dummy, int eventtype, int eventdata, int x, int y)
{
  if (eventdata == 'm') {
    GLint shademodel ;
    glGetIntegerv (GL_SHADE_MODEL, &shademodel) ;
    if (shademodel == GL_FLAT) {
      fprintf (stderr, "Switching from FLAT to SMOOTH\n") ;
      glShadeModel (GL_SMOOTH) ;
      return 1 ;
    }
    if (shademodel == GL_SMOOTH) {
      fprintf (stderr, "Switching from SMOOTH to FLAT\n") ;
      glShadeModel (GL_FLAT) ;
      return 1 ;
    }
    fprintf (stderr, "WARNING: Unknown SHADE_MODEL, I don't switch\n") ;
    return 0 ;
  }
  if (eventdata == 'M') {
    if (glIsEnabled (GL_LIGHTING)) {
      fprintf (stderr, "Turning GL_LIGHTING OFF\n") ;
      glDisable (GL_LIGHTING) ;
      return 1 ;
    }
    else {
      fprintf (stderr, "Tunring GL_LIGHTING ON\n") ;
      glEnable (GL_LIGHTING) ;
      return 1;
    }
  }
  fprintf (stderr, "glmode: unknown keysym\n") ;
  return 0; 
}

int selectionCallback (void* dummy, int eventtype, int eventdata, int x, int y)
{
  selectionmode=1;
  selectionx=x; selectiony=y;
  return 1 ;
}

int exitCallback (void* dummy, int eventtype, int eventdata, int x, int y) {
  exit (0) ;
  return 1 ;
}

int bozoCallback (void* data, int eventtype, int eventdata, int x, int y) {
  int* bozo = data ;
  (*bozo)++ ;
  fprintf (stderr, "bozo count is now %d!\n", *bozo) ;
  return 1 ;
}

int switchcamCallback (void* c, int eventtype, int eventdata, int dx, int dy)
{
  current_camera = (current_camera + 1) % nb_cameras;
  zone_glcam.data = cameras[current_camera];

  return 1;
}

zone_t zone_eyetest;
globj_p sphere, carpet, icosa ;
int bozocount = 0 ;
action_t action_eyetest_quit = { "quit", exitCallback, NULL } ;
action_t action_eyetest_bozo = { "bozo", bozoCallback, &bozocount } ;
action_t action_eyetest_switchcam = { "switch cam", switchcamCallback, NULL } ;
action_t action_eyetest_selection = { "selection", selectionCallback, NULL } ;
action_t action_eyetest_glmode = { "glmode", glmodeCallback, NULL};
action_t action_eyetest_dumpzone = { "dumpzone", dumpzoneCallback, NULL } ;
action_t action_eyetest_fpslimit = { "fpslimit", fpslimitCallback, NULL };

mapping_t mappings_eyetest [] = { 
  { EVENT_KEY, 'x', &action_eyetest_quit },
  { EVENT_KEY, 'b', &action_eyetest_bozo },
  { EVENT_KEY, 'c', &action_eyetest_switchcam },
  { EVENT_KEY, 's', &action_eyetest_selection },
  { EVENT_KEY, 'm', &action_eyetest_glmode },
  { EVENT_KEY, 'M', &action_eyetest_glmode },
  { EVENT_KEY, 'z', &action_eyetest_dumpzone },
  { EVENT_KEY, 'f', &action_eyetest_fpslimit },
  { EVENT_KEY, 'F', &action_eyetest_fpslimit }
} ;
zone_t zone_eyetest = { 0, NULL, sizeof(mappings_eyetest)/sizeof(mapping_t), 
			mappings_eyetest, NULL } ;

GLint id_teapot,id_plan,id_fleche,id_grid;
GLfloat cg[4] =  { 0.5f, 0.5f, 0.5f, 0.5f};

void idleFunc(void)
{
  fps_oneframe (&fps) ;
  glutPostRedisplay();
}

/*********************************************************/
/* fonction de dessin de la scène à l'écran              */
void drawFunc(void)
{ /* initialisation des buffers : couleur et ZBuffer */
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  /* modification de la matrice de la scène */
  glMatrixMode(GL_MODELVIEW);

  glPushMatrix();
  glTranslatef (1,1,1) ;
  glRotatef (0.01*glutGet(GLUT_ELAPSED_TIME), 0,0,1) ;
  //globj_draw (icosa,NULL) ;
  glPopMatrix();
 
  {
    GLuint hits[1024];
    int i;
    
    if (selectionmode) {
      glSelectBuffer( 1024, hits );
      glRenderMode( GL_SELECT );
    }
    
    glPushMatrix() ;
    //glTranslatef (3,3,3) ;
    //glRotatef (glutGet(GLUT_ELAPSED_TIME)/10, 0,1,1) ;
    glRotatef (0.05*glutGet(GLUT_ELAPSED_TIME), -1,0,0) ;
    globj_draw (sphere, cameras[current_camera]) ;    
    if (selectiontriangle!=-1) {
      sphere_t* s = sphere->data ;
      glPushAttrib (GL_LIGHTING_BIT) ;
      glDisable (GL_DEPTH_TEST) ;
      glDisable (GL_LIGHTING) ;
      glColor4f (1,1,1,1) ;
      glBegin (GL_TRIANGLES) ;
      sphere_drawtriangle_gl_highlight (s,s->triangles+selectiontriangle);
      glEnd () ;
      glPopAttrib () ;
      glEnable (GL_DEPTH_TEST) ;
    }
    
    glPopMatrix () ;

    if (selectionmode) {
      GLuint  num_hits = glRenderMode( GL_RENDER );
      fprintf (stderr,"%d hits\n", num_hits) ;
      for (i=0; i<num_hits; i++) {
	if (hits[4*i]!=1) 
	  fprintf (stderr, "WARNING: multiple names for one hit!\n") ;
	if (hits[4*i+3]!=-1) {
	  fprintf (stderr, "HIT: %d (%d %d)\n", 
		   hits[4*i+3], hits[4*i+1], hits[4*i+2]) ;
	  selectiontriangle = hits[4*i+3] ;
	}
      }
    }
  }

  /* petit test de debug pour afficher la normale du triangle selectionne */
  if (selectionmode && selectiontriangle != -1) {
    sphere_t* s = sphere->data ;
    fprintf (stderr,"normal: %f %f %f (%f)\n",
	     s->triangles[selectiontriangle].normal.x,
	     s->triangles[selectiontriangle].normal.y,
	     s->triangles[selectiontriangle].normal.z,
	     vector_norm (&s->triangles[selectiontriangle].normal)) ;
  }

  {
    GLfloat pos[4] = {-20.0f, 20.0f,-20.0f, 1.0f};
    glLightfv(GL_LIGHT0, GL_POSITION, pos );
  }

  glPushAttrib (GL_LIGHTING_BIT) ;
  glDisable (GL_LIGHTING) ;
  //  text_display (&fpstext) ;
  //  text_display (&decotext) ;
  glPopAttrib () ;

  glcam_update(cameras[current_camera]);

  glFlush();
  if (!selectionmode)
    glutSwapBuffers();
  selectionmode=0;
}

/*********************************************************/
/* fonction de changement de dimension de la fenetre     */
/* paramètres :                                          */
/* - width : largeur (x) de la zone de visualisation     */
/* - height : hauteur (y) de la zone de visualisation    */
static void reshapeFunc(int width,int height)
{
  int i;

  glViewport(0, 0, (GLsizei) width, (GLsizei) height);

  for(i=0; i<nb_cameras; i++) {
    cameras[i]->width = width;
    cameras[i]->height = height;
    glcam_update(cameras[i]);
  }
}

/*********************************************************/
/* fonction d'initialisation des paramètres d rendu et  */
/* des objets de la scènes.                              */
static void init()
{
  vector eye1 = { 0.0, 1.2, -0.5 };
  vector target1 = { 0.0, 1.0, 0.0 };
  vector eye2 = { -0.5, 1.2, 0.0 };
  vector eye3 = { 0.0, 0.0, -5.0 };
  vector target3 = { 0.0, 0.0, 0.0 };
  coord fov = 60;
  int width = 200;
  int height = 200;

  icosa  = globj_newinstance (&globj_class_icosa) ;
  sphere = globj_newinstance (&globj_class_sphere) ;

  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  /* couleur de fond */
  glClearColor(0.1,0.1,0.4,1.0);
  /* activation du ZBuffer */
  glEnable( GL_DEPTH_TEST);
  /* ZBuffer actif pour les objets non convexes */
  glDisable(GL_CULL_FACE);

  /* activation de la transparence */
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  /* lissage des couleurs sur les facettes */
  //glShadeModel(GL_SMOOTH);
  glShadeModel(GL_FLAT) ;

  /* création des cameras */
  nb_cameras = 3;
  cameras = (glcam_t**)malloc(nb_cameras*sizeof(glcam_t*));


  cameras[0] = glcam_new(eye1, target1, fov, width, height);
  
  cameras[1] = glcam_new(eye2, target1, fov, width, height);

  cameras[2] = glcam_new(eye3, target3, fov, width, height);

  current_camera = 2;

  /* initialisation de glhand */
  glhand_installcallbacks () ;
  glhand_addzone (&zone_eyetest) ;
  glhand_addzone (&zone_glcam) ;
  glhand_addzone (&zone_sphere) ;

  fps_init (&fps) ;
}

int main(int argc, char** argv)
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA|GLUT_DEPTH|GLUT_DOUBLE);
  glutInitWindowPosition(64, 0);
  glutInitWindowSize(200, 200);
  if (glutCreateWindow("Gleu gleu gleu") == GL_FALSE)
    return 1;

  init();
  
  glutReshapeFunc(reshapeFunc);
  glutDisplayFunc(drawFunc);
  glutIdleFunc(idleFunc);
  glutMainLoop();

  return 0;
}

