/* Auteur: Nicolas JANEY                  */
/* nicolas.janey@univ-fcomte.fr           */
/* Septembre 2005                         */
/* Un manipulateur souris et clavier      */

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

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

#include "ModuleManipulateur.h"
#include "ModuleMatriceVecteur.h"

#define UNREFERENCED_PARAMETER(P) (P)

static int mouvement = 0 ;
static int button = 0 ;
static int modifier = 0 ;
static float transx = 0;
static float transy = 0;
static float angle = 0.0F;
static float ax = 1.0F;
static float ay = 0.0F;
static float az = 0.0F;
static float zoom = 1.0F;
static int px ;
static int py ;
static int tx ;
static float mt[16];
static float manipulatorDistance = 1.0F;
static int anim = 0;
static float fact = 1.0F;
static int idleWindow = -1;
static float *param0 = NULL ;
static float *param1 = NULL ;

static float fct0;
static float min0;
static float max0;
static float fct1;
static float min1;
static float max1;

static fonctionVoid cleanUp = NULL;
static fonctionVoid refresh = NULL;
static fonctionVoid fonction1 = NULL;
static fonctionIntInt fonction2 = NULL;
static int tracking = 1;

static int rotateActive = 1;
static int translateActive = 1;
static int scaleActive = 1;

float manipulateurGetZoom(void) {
  return(zoom);
}

void manipulateurSetZoom(float zm) {
  zoom = zm;
}

void manipulateurGetTranslation(float *tx,float *ty) {
  *tx = transx;
  *ty = transy;
}

void manipulateurSetTranslation(float tx,float ty) {
  transx = tx;
  transy = ty;
}

void manipulateurGetRotation(float *nangle,float *nax,float *nay,float *naz) {
  *nangle = angle;
  *nax = ax;
  *nay = ay;
  *naz = az;
}

void manipulateurSetRotation(float nangle,float nax,float nay,float naz) {
  angle = nangle;
  ax = nax;
  ay = nay;
  az = naz;
}

#ifndef M_PI
#define M_PI 3.1415926535897932384626433832795
#endif

void compositionRotation(matrice m,float rx,float ry,float rz) {
  toRotationZ(m,rz);
  matrice mrx;
  toRotationX(mrx,rx);
  matrice mry;
  toRotationY(mry,ry);
  produitMatriceMatrice(mry,m,m);
  produitMatriceMatrice(mrx,m,m);
}

static double abs(double a) {
  return((a<0.0) ? -a :a);
}

void manipulateurGetRotations(float *rx,float *ry,float *rz) {
  matrice m;
  toRotation(m,angle,ax,ay,az);
  affichageMatrice(m);
  { double v = m[2][1];
    if ( v > 1.0 )
      v = 1.0;
    if ( v < -1.0 )
      v = -1.0;
    *rx = asin(v); }
  double csx = cos(*rx);
  if ( abs(csx) > 0.0001 ) {
    { double v = m[2][0]/csx;
      if ( v > 1.0 )
        v = 1.0;
      if ( v < -1.0 )
        v = -1.0;
      *ry = -asin(v); }
    { double v = m[1][1]/csx;
      if ( v > 1.0 )
        v = 1.0;
      if ( v < -1.0 )
        v = -1.0;
      *rz = -acos(v); } }
    else {
    }
  *rx = *rx * 180.0/M_PI;
  *ry = *ry * 180.0/M_PI;
  *rz = *rz * 180.0/M_PI;
  printf("%8.3f - %8.3f %8.3f %8.3f\n",angle,ax,ay,az);
  printf("         - %8.3f %8.3f %8.3f\n",*rx,*ry,*rz);
  matrice m2;
  compositionRotation(m2,*rx,*ry,*rz);
  affichageMatrice(m2);
}

static int getModifier() {
  int modif = 0;
  if ( ( (glutGetModifiers() & GLUT_ACTIVE_CTRL) != 0 ) && ( (glutGetModifiers() & GLUT_ACTIVE_ALT) != 0 ) )
    return(5);
  if ( (glutGetModifiers() & GLUT_ACTIVE_SHIFT) != 0 ) {
    if ( (glutGetModifiers() & GLUT_ACTIVE_CTRL) != 0 )
      modif = 2;
      else
      modif = 1; }
    else
    if ( (glutGetModifiers() & GLUT_ACTIVE_CTRL) != 0 )
      modif = 3;
      else
      if ( (glutGetModifiers() & GLUT_ACTIVE_ALT) != 0 )
        modif = 4;
  return(modif);
}

void setTracking(int tr) {
  tracking = tr;
}

int sourisManipulateur(int bouton,int etat,int x,int y) {
  glGetFloatv(GL_PROJECTION_MATRIX,mt);
  tx = glutGet(GLUT_WINDOW_WIDTH);
  button = bouton;
  if ( bouton == GLUT_LEFT_BUTTON ) {
    modifier = getModifier();
    if ( modifier ) {
      px = x ;
      py = y ; 
      if ( etat == GLUT_DOWN ) {
        mouvement = 1 ;}
      if ( etat == GLUT_UP ) {
        mouvement = 0 ; }
      switch (modifier) {
        case 5 :
          if ( etat == GLUT_UP ) {
            if ( fonction1 )
              fonction1();
            if ( fonction2 )
              fonction2(x,y); }
          break; }
      return(1); } }
  return(0);
}

void modificationRotation(float deltax,float deltay,float *angle,float *ax,float *ay,float *az) {
  if ( deltax || deltay ) {
    //printf("%f %f\n",deltax,deltay);
    //printf("%f - %f %f %f\n",*angle,*ax,*ay,*az);
    matrice m;
    toRotation(m,*angle,*ax,*ay,*az);
    matrice rx;
    toRotationX(rx,deltax);
    matrice ry;
    toRotationY(ry,deltay);
    produitMatriceMatrice(ry,m,m);
    produitMatriceMatrice(rx,m,m);
    rotationEquivalente(m,angle,ax,ay,az);
    /*printf("%f - %f %f %f\n\n",*angle,*ax,*ay,*az);*/ }
}

int motionManipulateur(int x,int y) {
  switch ( button ) {
    case GLUT_LEFT_BUTTON : 
      if ( mouvement == 1 ) {
        switch ( modifier ) {
          case 1 :
            if ( rotateActive ) {
              int deltax = y-py;
              int deltay = x-px;
              modificationRotation(deltax,deltay,&angle,&ax,&ay,&az);
              px = x; 
              py = y; }
            break;
          case 3 :
            if ( scaleActive ) {
              if ( ( y-py ) > 0 ) {
                float fact = pow(1.02F,(y-py)/5.0F);
                zoom *= fact ;
                transx *= fact;
                transy *= fact;
                }
              if ( ( y-py ) < 0 ) {
                float fact = pow(1.02F,(py-y)/5.0F);
                zoom /= fact ;
                transx /= fact;
                transy /= fact;
                }
              py = y; }
            break;
          case 4 :
            if ( translateActive ) {
              transx += (x-px)*2.0F/tx/mt[0]*manipulatorDistance ;
              transy -= (y-py)*2.0F/tx/mt[0]*manipulatorDistance ;
              px = x; 
              py = y; }
            break;
          case 5 :
            if ( param0 ) {
              int dx = x-px ;
              *param0 += dx*fct0;
              if ( *param0 < min0 )
                *param0 = min0;
              if ( *param0 > max0 )
                *param0 = max0; }
            if ( param1 ) {
              int dy = y-py ;
              *param1 += dy*fct1;
              if ( *param1 < min1 )
                *param1 = min1;
              if ( *param1 > max1 )
                *param1 = max1; }
            px = x; 
            py = y;
            if ( tracking ) {
              if ( fonction1 )
                fonction1();
              if ( fonction2 )
                fonction2(x,y); }
            break; }
        return(1); }
      break; }
  return(0);
}

void manipulateur() {
  glTranslatef(transx,transy,0.0F);
  glRotatef(angle,ax,ay,az);
  glScalef(zoom,zoom,zoom);
}

void setManipulateurDistance(float d) {
  manipulatorDistance = d;
}

int keyManipulateur(unsigned char key,int x,int y) {
  UNREFERENCED_PARAMETER(x);
  UNREFERENCED_PARAMETER(y);
  modifier = getModifier();
  if ( modifier == 1 ) {
    switch ( key ) {
      case 0x0D :
        anim = (anim+1)%2;
        return(1);
        break; } }
  switch ( key ) {
    case '#'   : { static int fullScreen = 0;
                   static int tx;
                   static int ty;
                   static int px;
                   static int py;
                   fullScreen = !fullScreen;
                   if ( fullScreen ) {
                     px = glutGet(GLUT_WINDOW_X);
                     py = glutGet(GLUT_WINDOW_Y);
                     tx = glutGet(GLUT_WINDOW_WIDTH);
                     ty = glutGet(GLUT_WINDOW_HEIGHT);
                     glutFullScreen(); }
                     else {
                     glutPositionWindow(px,py);
                     glutReshapeWindow(tx,ty); } }
                 break;
   case '\033' : if ( cleanUp )
                    cleanUp();
                  exit(0);
                  break ; }
  return(0);
}

int specialManipulateur(int k,int x,int y) {
  UNREFERENCED_PARAMETER(x);
  UNREFERENCED_PARAMETER(y);
  modifier = getModifier();
  if ( modifier == 1 ) {
    switch (k) {
      case GLUT_KEY_LEFT      : 
      case GLUT_KEY_PAGE_UP   : 
      case GLUT_KEY_UP        :
        fact *= 1.1F;
        return(1);
        break;
      case GLUT_KEY_RIGHT     : 
      case GLUT_KEY_PAGE_DOWN : 
      case GLUT_KEY_DOWN      :
        fact /= 1.1F;
        return(1);
        break; } }
  if ( modifier == 3 ) {
    switch (k) {
      case GLUT_KEY_UP :
        if ( rotateActive ) {
          angle = 0.0F;
          ax = 1.0F;
          ay = 0.0F;
          az = 0.0F;
          return(1); }
        break;
      case GLUT_KEY_DOWN : 
        if ( rotateActive ) {
          angle = 0.0F;
          ax = 1.0F;
          ay = 0.0F;
          az = 0.0F;
          return(1); }
        break;
      case GLUT_KEY_LEFT : 
        if ( rotateActive ) {
          angle = 0.0F;
          ax = 1.0F;
          ay = 0.0F;
          az = 0.0F;
          return(1); }
        break;
      case GLUT_KEY_RIGHT :
        if ( rotateActive ) {
          angle = 0.0F;
          ax = 1.0F;
          ay = 0.0F;
          az = 0.0F;
          return(1); }
        break;
      case GLUT_KEY_PAGE_DOWN :
        if ( rotateActive ) {
          angle = 0.0F;
          ax = 1.0F;
          ay = 0.0F;
          az = 0.0F;
          return(1); }
        break;
      case GLUT_KEY_PAGE_UP : 
        if ( rotateActive ) {
          angle = 0.0F;
          ax = 1.0F;
          ay = 0.0F;
          az = 0.0F;
          return(1); }
        break; }
    return(0); }
  float dif = 0.0F;
  switch(modifier) {
    case 0 : dif = 1.0F;
             break;
    case 4 : dif = 10.0F;
             break; }
  if ( rotateActive )
    switch (k) {
      case GLUT_KEY_UP : 
        if ( rotateActive ) {
          modificationRotation(-dif,0,&angle,&ax,&ay,&az);
          return(1); }
        break;
      case GLUT_KEY_DOWN : 
        if ( rotateActive ) {
          modificationRotation(dif,0,&angle,&ax,&ay,&az);
          return(1); }
        break;
      case GLUT_KEY_LEFT :
        if ( rotateActive ) {
          modificationRotation(0,-dif,&angle,&ax,&ay,&az);
          return(1); }
        break;
      case GLUT_KEY_RIGHT :
        if ( rotateActive ) {
          modificationRotation(0,dif,&angle,&ax,&ay,&az);
          return(1); }
        break; }
  return(0);
}

void mouseBasique(int bouton,int etat,int x,int y) {
  if ( sourisManipulateur(bouton,etat,x,y) )
    if ( refresh )
      refresh();
      else
      glutPostRedisplay();
}

void motionBasique(int x,int y) {
  if ( motionManipulateur(x,y) )
    if ( refresh )
      refresh();
      else
      glutPostRedisplay();
}

void keyBasique(unsigned char key,int x,int y) {
  if ( keyManipulateur(key,x,y) )
    if ( refresh )
      refresh();
      else
      glutPostRedisplay();
}

void specialBasique(int k,int x,int y) {
  if ( specialManipulateur(k,x,y) )
    if ( refresh )
      refresh();
      else
      glutPostRedisplay();
}

void setIdleWindow(int w) {
  idleWindow = w;
}

void idleBasique(void) {
  static int told;
  GLint t = glutGet(GLUT_ELAPSED_TIME);
  if ( anim ) {
    angle += (0.01F*(t-told)*fact);
    if ( idleWindow == -1 )
      if ( refresh )
        refresh();
        else
        glutPostRedisplay();
      else
      glutPostWindowRedisplay(idleWindow); }
  told = t;
}

void setManipulateurCleanUp(void (*func)(void)) {
  cleanUp = func;
}

void setManipulateurParametre0(float *param,float min,float max,float fct) {
  param0 = param;
  min0 = min;
  max0 = max;
  fct0 = fct;
}

void setManipulateurParametre1(float *param,float min,float max,float fct) {
  param1 = param;
  min1 = min;
  max1 = max;
  fct1 = fct;
}

void setManipulateurParametreFonction(fonctionVoid fonct) {
  fonction1 = fonct;
}

void setManipulateurParametreFonctionIntInt(fonctionIntInt fonct) {
  fonction2 = fonct;
}

void setManipulateurRefresh(fonctionVoid fonct) {
  refresh = fonct;
}

static float angleOld;
static float axOld;
static float ayOld;
static float azOld;

void manipulateurPush(void) {
  angleOld = angle;
  axOld = ax;
  ayOld = ay;
  azOld = az;
}

void manipulateurPop(void) {
  angle = angleOld;
  ax = axOld;
  ay = ayOld;
  az = azOld;
}

void manipulateurReset(void) {
  angle = 0.0F;
  ax = 1.0F;
  ay = 0.0F;
  az = 0.0F;
}

void manipulateurActivation(typeTransformation operation,int flag) {
  switch (operation) {
    case ROTATE :
      rotateActive = flag;
      break;
    case TRANSLATE :
      translateActive = flag;
      break;
    case SCALE :
      scaleActive = flag;
      break; }
}

