#include <stdlib.h>
#include <math.h>
#include <GL/glut.h>
#include "camera.h"
#include "vecmath.h"

camera cam;
camera savecam;

int oldx = -1, oldy = -1;
int leftbutton=0;

int main(int argc, char** argv)
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
  glutInitWindowSize(500, 500);
  glutInitWindowPosition(100, 100);
  glutCreateWindow(argv[0]);
  init();
  initCamera();
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  glutKeyboardFunc(keyboard);
  glutMouseFunc(mouse);
  glutMotionFunc(motion);
  glutMainLoop();
}

void init(void)
{
  glClearColor(0.0, 0.0, 0.0, 0.0);
  glShadeModel(GL_FLAT);
}

void display(void)
{
  glClear(GL_COLOR_BUFFER_BIT);

  glMatrixMode(GL_MODELVIEW);

  glColor3f(1.0, 0.0, 0.0);
  glLoadIdentity();
  glTranslatef(5.0, 0.0, 0.0);
  glutWireSphere(1.0, 20, 16);

  glColor3f(0.0, 1.0, 0.0);
  glLoadIdentity();
  glTranslatef(-5.0, 0.0, 0.0);
  glutWireSphere(1.0, 20, 16);

  glColor3f(0.0, 0.0, 1.0);
  glLoadIdentity();
  glTranslatef(0.0, 5.0, 0.0);
  glutWireSphere(1.0, 20, 16);

  glColor3f(1.0, 1.0, 0.0);
  glLoadIdentity();
  glTranslatef(0.0, -5.0, 0.0);
  glutWireSphere(1.0, 20, 16);

  glutSwapBuffers();
}

void reshape(int w, int h)
{
  cam.height=h;
  cam.width=w;

  glViewport(0, 0, (GLsizei) w, (GLsizei) h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(cam.fov, (GLfloat) cam.width/(GLfloat) cam.height, 1.0, 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);
  glLoadIdentity();
}


void motion(int x, int y)
{
  static vector vect;
  static int dx;
  static int dy;

  if(leftbutton == 1) {

    dx = oldx-x;
    dy = oldy-y;
    
    if(oldx != -1) {
      switch(cam.transformation) {
	
      case PAN:
	translateCamera(dy*cam.up.x*0.04,
			dy*cam.up.y*0.04,
			dy*cam.up.z*0.04);
	vect.h=0.0;
	vectorialProd(cam.up, cam.dir, &vect);
	normalize(&vect);

	translateCamera(dx*vect.x*0.04,
			dx*vect.y*0.04,
			dx*vect.z*0.04);	
	break;
	
      case DOLLY:
	translateCamera(dy*cam.dir.x*0.04,
			dy*cam.dir.y*0.04,
			dy*cam.dir.z*0.04);
	break;
	
      case ROLL:
	cam.rollAngle+=dy*0.01;
	rollCamera(dy*0.01);
	break;
	
      case PITCH:
	pitchCamera(-dy*0.01);
	break;
	
      case YAW:
	yawCamera(dx*0.01);
	break;
	
      case LOOK:
	yawCamera(dx*0.01);
	pitchCamera(-dy*0.01);
	adjustCamUp();
	break;
	
      case AROUND:
	rotateAroundTarget(dx*0.01, dy*0.01);
	adjustCamUp();
	break;
	
      case ZOOM:
	break;
	
      case FOV:
	if(((cam.fov + dy) >= 0.0) && ((cam.fov + dy) <= 180.0)) {
	  cam.fov+=dy;
	  updateCamera(cam.width, cam.height); 
	}
	break;

      default:
	break;
      }
    }
    oldx=x;
    oldy=y;

    glutPostRedisplay();
  }
}
  
void mouse(int button, int state, int x, int y)
{
  switch(button) {
    
  case GLUT_LEFT_BUTTON:
    if(state == GLUT_DOWN) {
      leftbutton=1;
      setCamera(cam, &savecam);
      oldx=x;
      oldy=y;
    }
    else {
      leftbutton=0;
      oldx=-1;
      oldy=-1;
    }
    break;

  case GLUT_RIGHT_BUTTON:
    if((state == GLUT_DOWN)&&(leftbutton==1)) {
      leftbutton=0;
      oldx=-1;
      oldy=-1;
      setCamera(savecam, &cam);
      updateCamera(cam.width, cam.height);
      glutPostRedisplay();
    }
    break;
    
  default:
    break;
  }
}

void keyboard(unsigned char key, int x, int y)
{
  switch(key) {
    
  case 'm':
    cam.transformation=PAN;
    break;
    
  case 'd':
    cam.transformation=DOLLY;
    break;
    
  case 'r':
    cam.transformation=ROLL;
    break;
    
  case 'p':
    cam.transformation=PITCH;
    break;
    
  case 'y':
    cam.transformation=YAW;
    break;
    
  case 'l':
    cam.transformation=LOOK;
    break;
    
  case 'a':
    cam.transformation=AROUND;
    break;

  case 'z':
    cam.transformation=ZOOM;
    break;

  case 'f':
    cam.transformation=FOV;
    break;

  case '0':
    initCamera();
    updateCamera(cam.width, cam.height);
    glutPostRedisplay();
    break;

  default:
    break;
  }
}

void updateCamera(int w, int h)
{
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(cam.fov, (GLfloat) cam.width/(GLfloat) cam.height, 1.0, 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 );
}

void setCamera(camera source, camera* dest)
{
  dest->eye.x=source.eye.x;
  dest->eye.y=source.eye.y;
  dest->eye.z=source.eye.z;
  dest->eye.h=source.eye.h;

  dest->target.x=source.target.x;
  dest->target.y=source.target.y;
  dest->target.z=source.target.z;
  dest->target.h=source.target.h;

  dest->up.x=source.up.x;
  dest->up.y=source.up.y;
  dest->up.z=source.up.z;
  dest->up.h=source.up.z;

  dest->dir.x=source.dir.x;
  dest->dir.y=source.dir.y;
  dest->dir.z=source.dir.z;
  dest->dir.h=source.dir.h;
  
  dest->height=source.height;
  dest->width=source.width;

  dest->transformation=source.transformation;
}

void initCamera()
{
  cam.eye.x=0.0;
  cam.eye.y=0.0;
  cam.eye.z=-10.0;
  cam.eye.h=1.0;

  cam.target.x=0.0;
  cam.target.y=0.0;
  cam.target.z=0.0;
  cam.target.h=1.0;

  cam.up.x=0.0;
  cam.up.y=1.0;
  cam.up.z=0.0;
  cam.up.h=0.0;

  cam.dir.x=0.0;
  cam.dir.y=0.0;
  cam.dir.z=1.0;
  cam.dir.h=0.0;

  cam.fov=60.0;
  
  cam.height=500;
  cam.width=500;

  cam.transformation=0;
}

void translateCamera(coord x, coord y, coord z)
{
  static matrix transfo;

  // creer mat de translation (une fonction)
  translationMatrix(x, y, z,&transfo);
  
  // appliquer la transfo a l'oeil et au centre :
  // multiplier oeil et centre par la matrice
  multMatPoint(transfo, &cam.eye);
  multMatPoint(transfo, &cam.target);

  // mettre a jour la camera
  updateCamera(cam.width, cam.height);
}

void rollCamera(coord angle)
{
  static matrix transfo;

  rotationMatrix(angle, cam.dir.x, cam.dir.y, cam.dir.z, &transfo);

  multMatVector(transfo, &cam.up);
  normalize(&cam.up);

  updateCamera(cam.width, cam.height);
}

void yawCamera(coord angle)
{
  static matrix transfo;

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

  rotationMatrix(angle, cam.up.x, cam.up.y, cam.up.z, &transfo);
  
  multMatPoint(transfo, &cam.target);
  multMatVector(transfo, &cam.dir);
  normalize(&cam.dir);

  cam.target.x+=cam.eye.x;
  cam.target.y+=cam.eye.y;
  cam.target.z+=cam.eye.z;

  updateCamera(cam.width, cam.height);
}

void pitchCamera(coord angle)
{
  static matrix transfo;
  vector vect;
  vect.h=0.0;
    
  vectorialProd(cam.up, cam.dir, &vect);
  normalize(&vect);  

  cam.target.x-=cam.eye.x;
  cam.target.y-=cam.eye.y;
  cam.target.z-=cam.eye.z;
  
  rotationMatrix(angle, vect.x, vect.y, vect.z, &transfo);

  multMatPoint(transfo, &cam.target);
  multMatVector(transfo, &cam.dir);
  normalize(&cam.dir);
  
  multMatVector(transfo, &cam.up);
  normalize(&cam.up);

  cam.target.x+=cam.eye.x;
  cam.target.y+=cam.eye.y;
  cam.target.z+=cam.eye.z;
  
  updateCamera(cam.width, cam.height);
}

void rotateAroundTarget(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;
  
  rotationMatrix(x, cam.up.x, cam.up.y, cam.up.z, &transfo);
  
  multMatPoint(transfo, &cam.eye);
  multMatVector(transfo, &cam.dir);
  normalize(&cam.dir);
  
  /***/

  vectorialProd(cam.dir, cam.up, &right);
  normalize(&right); 

  rotationMatrix(y, right.x, right.y, right.z, &transfo);

  multMatPoint(transfo, &cam.eye);
  multMatVector(transfo, &cam.dir);
  normalize(&cam.dir);
  multMatVector(transfo, &cam.up);
  normalize(&cam.up);

  cam.eye.x+=cam.target.x;
  cam.eye.y+=cam.target.y;
  cam.eye.z+=cam.target.z;

  updateCamera(cam.width, cam.height);

}

void adjustCamUp()
{
  vector up, right;
  
  up.x = 0.0; up.z = 0.0;
  up.y = (cam.up.y > 0.0) ? 1.0 : -1.0,
  vectorialProd(cam.dir, up, &right);
  normalize(&right);  

  vectorialProd(right, cam.dir, &cam.up);
  normalize(&cam.up);

  if(cam.rollAngle) {
    rollCamera(cam.rollAngle);
  }
  else {
    updateCamera(cam.width, cam.height);
  }
}

void drawFunc () {} /* gleye compat */
