#include <stdlib.h>
#include <math.h>
#include <GL/glut.h>
#include "glcam.h"
#include "vecmath.h"
#include "stdio.h"
#include "glhand.h"


int lookatCallback(void* c, int eventtype, int eventdata, int dx, int dy)
{
  glcam_t* camera = *((glcam_t**)c);

  glcam_yaw(camera, -dx*0.01);
  glcam_pitch(camera, dy*0.01);
  glcam_adjustup(camera);

  return 1;
}

int dollyCallback(void* c, int eventtype, int eventdata, int dx, int dy)
{
  glcam_t* camera = *((glcam_t**)c);

  glcam_translate(camera, 
		  -dy*camera->dir.x*0.04,
		  -dy*camera->dir.y*0.04,
		  -dy*camera->dir.z*0.04,
		  1);

  return 1;
}

int panCallback(void* c, int eventtype, int eventdata, int dx, int dy)
{
  static vector vect;
  glcam_t* camera = *((glcam_t**)c);
  
  glcam_translate(camera, 
		  -dy*camera->up.x*0.04,
		  -dy*camera->up.y*0.04,
		  -dy*camera->up.z*0.04,
		  1);
  
  
  vector_product (&vect, camera->up, camera->dir);
  vector_normalize(&vect);
  
  glcam_translate(camera,
		  -dx*vect.x*0.04,
		  -dx*vect.y*0.04,
		  -dx*vect.z*0.04,
		  1);	

  return 1;
}

int keyboardactionCallback (void* c, int eventtype, int eventdata, int dx, int dy)
{
  glcam_t* camera = *((glcam_t**)c);
  switch (eventdata) 
    {
    case '1':
      if((camera->fov - 2) >= 0.0)
	camera->fov-=2;
      break;

    case '7':
      if((camera->fov + 2) <= 180.0)
	camera->fov+=2;
      break;

    case '3':
      glcam_translate(camera, 
		      camera->dir.x*-0.1,
		      camera->dir.y*-0.1,
		      camera->dir.z*-0.1,
		      0);
      break;

    case '9':
      glcam_translate(camera, 
		      camera->dir.x*0.1,
		      camera->dir.y*0.1,
		      camera->dir.z*0.1,
		      0);
      break;

    case '4':
	glcam_turnaround(camera, -0.1, 0.0);
	glcam_adjustup(camera);
      break;

    case '6':
	glcam_turnaround(camera, 0.1, 0.0);
	glcam_adjustup(camera);
      break;

    case '2':
	glcam_turnaround(camera, 0.0, -0.1);
	glcam_adjustup(camera);
      break;

    case '8':
	glcam_turnaround(camera, 0.0, 0.1);
	glcam_adjustup(camera);
      break;

    default:
      fprintf (stderr, "WARNING: unknown key in glcam kb handler (%c)\n",eventdata) ;
      return 0 ;
    }
  return 1 ;  
}

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

int mousebuttonCallback (void* dummy, int eventtype, int eventdata, int x, int y) {
  if (eventtype==EVENT_MOUSEUP) {
    zone_glcam.mappings[0].action = &action_glcam_none ;
    return 1;
  }
  if (eventtype==EVENT_MOUSEDOWN) {
    switch (eventdata) {
    case GLUT_LEFT_BUTTON:
      zone_glcam.mappings[0].action = &action_glcam_lookat ;
      return 1;
    case GLUT_MIDDLE_BUTTON:
      zone_glcam.mappings[0].action = &action_glcam_dolly ;
      return 1;
    case GLUT_RIGHT_BUTTON:
      zone_glcam.mappings[0].action = &action_glcam_pan ;
      return 1;
    default:
      fprintf (stderr, "WARNING: unknown mouse button !\n") ;
      return 0;
    }
    return 1;
  }
  fprintf (stderr, "WARNING: unknown mousebuttoncallback!\n") ;
  return 0 ;
}



action_t action_glcam_lookat = { "lookat", lookatCallback, &zone_glcam.data };
action_t action_glcam_dolly = { "dolly", dollyCallback, &zone_glcam.data };
action_t action_glcam_pan = { "pan", panCallback, &zone_glcam.data };
action_t action_glcam_mousebutton = { "button", mousebuttonCallback, NULL };
action_t action_glcam_none = { "none", noneCallback, NULL };
action_t action_glcam_keyboard = { "keyboard", keyboardactionCallback, &zone_glcam.data } ;

mapping_t mappings_glcam []= { 
  { EVENT_MOUSEMOVE, 0, &action_glcam_none },
  { EVENT_MOUSEDOWN, GLUT_LEFT_BUTTON, &action_glcam_mousebutton }, 
  { EVENT_MOUSEDOWN, GLUT_MIDDLE_BUTTON, &action_glcam_mousebutton },
  { EVENT_MOUSEDOWN, GLUT_RIGHT_BUTTON, &action_glcam_mousebutton },
  { EVENT_MOUSEUP, GLUT_LEFT_BUTTON, &action_glcam_mousebutton },
  { EVENT_MOUSEUP, GLUT_MIDDLE_BUTTON, &action_glcam_mousebutton },
  { EVENT_MOUSEUP, GLUT_RIGHT_BUTTON, &action_glcam_mousebutton },
  { EVENT_KEY, '1', &action_glcam_keyboard },
  { EVENT_KEY, '2', &action_glcam_keyboard },
  { EVENT_KEY, '3', &action_glcam_keyboard },
  { EVENT_KEY, '4', &action_glcam_keyboard },
  { EVENT_KEY, '6', &action_glcam_keyboard },
  { EVENT_KEY, '7', &action_glcam_keyboard },
  { EVENT_KEY, '8', &action_glcam_keyboard },
  { EVENT_KEY, '9', &action_glcam_keyboard }
} ;

zone_t zone_glcam = { 0, NULL, sizeof(mappings_glcam)/sizeof(mapping_t), 
		      mappings_glcam, NULL };

glcam_t* glcam_new (vector eye, vector target, coord fov, int width, int height)
{
  glcam_t* cam = malloc (sizeof(glcam_t)) ;
  glcam_init (cam, eye, target, fov, width, height) ;
  zone_glcam.data = cam ; /* FIXME */
  return cam ;
}

void glcam_delete (glcam_t* cam) 
{
  free (cam) ;
}

void glcam_reshape(glcam_t* cam,int w, int h)
{
  cam->height=h;
  cam->width=w;
  glViewport(0, 0, (GLsizei) w, (GLsizei) h);
  glcam_update (cam) ;
}

void glcam_update (glcam_t* cam)
{
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(cam->fov, (GLfloat)cam->width / (GLfloat)cam->height, 
		 0.01, 80.0);
  gluLookAt(cam->eye.x,    cam->eye.y,    cam->eye.z,
	    cam->target.x, cam->target.y, cam->target.z,
	    cam->up.x,     cam->up.y,     cam->up.z );
  glMatrixMode(GL_MODELVIEW);
}

void glcam_init (glcam_t* cam, vector eye, vector target, coord fov, int width, int height)
{
  cam->eye = eye;
  cam->target = target;
  cam->fov = fov;
  cam->width = width;
  cam->height = height;

  cam->up.x = 0.0;
  cam->up.y = 1.0;
  cam->up.z = 0.0;

  cam->dir.x = cam->target.x - cam->eye.x;
  cam->dir.y = cam->target.y - cam->eye.y;
  cam->dir.z = cam->target.z - cam->eye.z;

  vector_normalize(&cam->dir);

}

void glcam_translate (glcam_t* cam, coord x, coord y, coord z, char target)
{
  static matrix transfo;
  matrix_translation (&transfo, x, y, z) ;
  vector_applymatrix (&cam->eye, transfo, 1.0) ; 
  if(target)
    vector_applymatrix (&cam->target, transfo, 1.0) ;
}

void glcam_roll (glcam_t* cam, coord angle)
{
  static matrix transfo;
  matrix_rotation (&transfo, angle, cam->dir.x, cam->dir.y, cam->dir.z) ;
  vector_applymatrix (&cam->up, transfo, 0.0) ;
  vector_normalize (&cam->up) ;
}

void glcam_yaw (glcam_t* cam, coord angle)
{
  static matrix transfo;
  cam->target.x-=cam->eye.x;
  cam->target.y-=cam->eye.y;
  cam->target.z-=cam->eye.z;
  matrix_rotation (&transfo, angle, cam->up.x, cam->up.y, cam->up.z) ;
  vector_applymatrix (&cam->target, transfo, 0.0) ;
  vector_applymatrix (&cam->dir, transfo, 0.0) ;
  vector_normalize (&cam->dir) ;
  cam->target.x+=cam->eye.x;
  cam->target.y+=cam->eye.y;
  cam->target.z+=cam->eye.z;
}

void glcam_pitch (glcam_t* cam, coord angle)
{
  static matrix transfo;
  static vector vect;
  vector_product (&vect, cam->up, cam->dir) ;
  vector_normalize (&vect) ;  
  cam->target.x-=cam->eye.x;
  cam->target.y-=cam->eye.y;
  cam->target.z-=cam->eye.z;
  matrix_rotation (&transfo, angle, vect.x, vect.y, vect.z);
  vector_applymatrix (&cam->target, transfo, 0.0) ;
  vector_applymatrix (&cam->dir, transfo, 0.0) ;
  vector_normalize (&cam->dir) ;
  vector_applymatrix (&cam->up, transfo, 0.0) ;
  vector_normalize (&cam->up) ;
  cam->target.x+=cam->eye.x;
  cam->target.y+=cam->eye.y;
  cam->target.z+=cam->eye.z;
}

void glcam_turnaround (glcam_t* cam, coord x, coord y)
{
  static matrix transfo;
  vector right;
  cam->eye.x-=cam->target.x;
  cam->eye.y-=cam->target.y;
  cam->eye.z-=cam->target.z;
  matrix_rotation (&transfo, x, cam->up.x, cam->up.y, cam->up.z) ;
  vector_applymatrix (&cam->eye, transfo, 0.0) ;
  vector_applymatrix (&cam->dir, transfo, 0.0) ;
  vector_normalize (&cam->dir) ;
  /***/
  vector_product (&right, cam->dir, cam->up) ;
  vector_normalize (&right) ;  
  matrix_rotation (&transfo, y, right.x, right.y, right.z) ;
  vector_applymatrix (&cam->eye, transfo, 0.0) ;
  vector_applymatrix (&cam->dir, transfo, 0.0) ;
  vector_normalize (&cam->dir) ;
  vector_applymatrix (&cam->up, transfo, 0.0) ;
  vector_normalize (&cam->up) ;
  cam->eye.x+=cam->target.x;
  cam->eye.y+=cam->target.y;
  cam->eye.z+=cam->target.z;
}

void glcam_adjustup (glcam_t* cam)
{
  vector up, right;
  
  up.x = 0.0; up.z = 0.0;
  up.y = (cam->up.y > 0.0) ? 1.0 : -1.0,
  vector_product(&right, cam->dir, up);
  vector_normalize(&right);
  vector_product(&cam->up, right, cam->dir);
  vector_normalize(&cam->up);
}

