/****************************************************************************/
/* This file is part of FreeFEM.                                            */
/*                                                                          */
/* FreeFEM is free software: you can redistribute it and/or modify          */
/* it under the terms of the GNU Lesser General Public License as           */
/* published by the Free Software Foundation, either version 3 of           */
/* the License, or (at your option) any later version.                      */
/*                                                                          */
/* FreeFEM is distributed in the hope that it will be useful,               */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of           */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            */
/* GNU Lesser General Public License for more details.                      */
/*                                                                          */
/* You should have received a copy of the GNU Lesser General Public License */
/* along with FreeFEM. If not, see <http://www.gnu.org/licenses/>.          */
/****************************************************************************/
/* SUMMARY : ...                                                            */
/* LICENSE : LGPLv3                                                         */
/* ORG     : LJLL Universite Pierre et Marie Curie, Paris, FRANCE           */
/* AUTHORS : Pascal Frey                                                    */
/* E-MAIL  : pascal.frey@sorbonne-universite.fr                             */

#ifdef __cplusplus
extern "C" {
#endif

#include "medit.h"
#include "extern.h"
#include "sproto.h"

#ifndef ON
#define ON 1
#define OFF 0
#endif

GLuint lasttime;
GLboolean tracking = GL_FALSE, ctracking = GL_FALSE;
GLboolean picking = GL_FALSE;
int cbutton = 0;
int startx, starty, curx, cury;
int rxi, ryi, rx1, rx2, ry1, ry2;

#define MinWH 0.1
#define MaxWH 0.9

#ifndef GLUT_BUTTON_3
#define GLUT_BUTTON_3 2
#define GLUT_BUTTON_4 3
#endif

/* project x,y, onto a hemi-sphere */
static void point2Vect(int x, int y, int w, int h, float *v) {
  double d, a, areax, areay;

  areax = (w - startx) / w;
  areay = (h - starty) / h;
  if (areax > MinWH && areax < MaxWH && areay > MinWH && areay < MaxWH) {
    v[0] = (2.0 * x - w) / w;
    v[1] = (h - 2.0 * y) / h;
    v[2] = 1.0f;
  } else {
    v[0] = 2.0f * (x - startx) / w;
    v[1] = -2.0f * (y - starty) / h;
    v[2] = 1.0f;
  }

  d = v[0] * v[0] + v[1] * v[1];
  if (d == 0.0f) return;

  d = sqrt(d);

  v[2] = cos(M_PI_2 * ((d < 1.0) ? d : 1.0));
  d = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
  a = 1.0f / sqrt(d);
  v[0] *= a;
  v[1] *= a;
  v[2] *= a;
}

void ortho2D(pScene sc, ubyte mode) {
  if (mode == ON) {
    glMatrixMode(GL_PROJECTION);
    glPushMatrix( );
    glLoadIdentity( );
    gluOrtho2D(0, sc->par.xs, 0., sc->par.ys);
    glDisable(GL_LIGHTING);
    glDisable(GL_DEPTH_TEST);
    glMatrixMode(GL_MODELVIEW);
  } else if (mode == OFF) {
    glMatrixMode(GL_PROJECTION);
    glPopMatrix( );
    glMatrixMode(GL_MODELVIEW);
    glEnable(GL_DEPTH_TEST);
    glLineWidth(1.);
  }
}

static void drawRubberBand(int xa, int ya, int xb, int yb) {
  glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
  glLineWidth(2.0);
  glColor3f(0.5, 0.5, 0.5);
  glRecti(xa, ya, xb, yb);
}

static void rubberMotion(int x, int y) {
  pScene sc;

  sc = cv.scene[currentScene( )];

  glEnable(GL_COLOR_LOGIC_OP);
  glLogicOp(GL_XOR);

  /* draw frame */
  drawRubberBand(rxi, ryi, rx1, ry1);
  rx2 = x;
  ry2 = sc->par.ys - y;
  drawRubberBand(rxi, ryi, rx2, ry2);
  glFlush( );

  glLogicOp(GL_COPY);
  glDisable(GL_COLOR_LOGIC_OP);

  /* keep old coords */
  rx1 = rx2;
  ry1 = ry2;
}

void zoomMotion(int x, int y) {
  pScene sc;
  pPersp p;
  int dy;

  sc = cv.scene[currentScene( )];
  p = sc->persp;

  dy = starty - y;
  if (dy > 0)
    if (p->fovy < 1.0e-02)
      return;
    else
      p->fovy = max(0.95 * p->fovy, 1e-05);

  else if (p->fovy > 160.0)
    return;
  else
    p->fovy = min(1.1 * p->fovy, 179.0);

  farclip(1);
  starty = y;
  glutPostRedisplay( );
}

void mouse(int button, int state, int x, int y) {
  pScene sc;
  pTransform tr;
  pPersp p;
  int keyact, idw = currentScene( );
  static int olds = -1;

  picking = GL_FALSE;

  /* default */
  if (ddebug) printf("control mouse %d\n", state);

  sc = cv.scene[idw];
  p = sc->persp;
  if (sc->cube->active & C_EDIT)
    tr = sc->cube->cubetr;
  else if (sc->clip->active & C_EDIT)
    tr = sc->clip->cliptr;
  else
    tr = sc->view;

  tr->mstate = state;
  tr->mbutton = button;

  /* check if ctrl-shift-alt pressed */
  keyact = glutGetModifiers( );

  if (state == GLUT_DOWN) {
    tracking = GL_TRUE;
    lasttime = glutGet(GLUT_ELAPSED_TIME);

    if (button == GLUT_LEFT_BUTTON) {
      if (keyact == GLUT_ACTIVE_SHIFT) {
        /* entity designation */
        picking = GL_TRUE;
        if (sc->picklist) glDeleteLists(sc->picklist, 1);

        sc->picklist = pickingScene(sc, x, y, 0);
        return;
      } else if (keyact == GLUT_ACTIVE_ALT) {
        /* zoom */
        starty = y;
        glutMotionFunc(zoomMotion);
        return;
      } else if (keyact == GLUT_ACTIVE_CTRL) {
        /* rubberband selection */
        glutSetCursor(GLUT_CURSOR_CROSSHAIR);
        p->rubix = p->rubfx = x;
        p->rubiy = p->rubfy = sc->par.ys - y;
        rxi = rx1 = x;
        ryi = ry1 = sc->par.ys - y;
        p->rubber = 1;
        glDrawBuffer(GL_BACK_LEFT);
        ortho2D(sc, ON);
        glutMotionFunc(rubberMotion);
        return;
      }
    } else if (button == GLUT_MIDDLE_BUTTON && keyact & GLUT_ACTIVE_SHIFT) {
      picking = GL_TRUE;
      if (sc->picklist) glDeleteLists(sc->picklist, 1);

      sc->picklist = pickingScene(sc, x, y, LPoint);
      return;
    }

    /* transformation */
    startx = x;
    starty = y;
    point2Vect(x, y, sc->par.xs, sc->par.ys, tr->pos);
    glutSetCursor(GLUT_CURSOR_INFO);
  } else if (state == GLUT_UP) {
    if (button == GLUT_LEFT_BUTTON) {
      if (keyact == GLUT_ACTIVE_CTRL) {
        /* rubberband selection */
        p->rubfx = x;
        p->rubfy = sc->par.ys - y;
        p->rubber = 2;
        glDrawBuffer(GL_BACK_LEFT);
        ortho2D(sc, OFF);
        glutMotionFunc(motion);
        return;
      } else if (keyact == GLUT_ACTIVE_ALT) {
        glutMotionFunc(motion);
        return;
      } else if (picking == GL_TRUE) {
        picking = GL_FALSE;
        reshapeScene(sc->par.xs, sc->par.ys);
        glutPostRedisplay( );
      }
    }

    glutMotionFunc(motion);
    if (sc->clip->active & C_EDIT) sc->clip->active |= C_REDO;

    /* transformation */
    glutSetCursor(GLUT_CURSOR_INHERIT);
    tracking = GL_FALSE;
    if (glutGet(GLUT_ELAPSED_TIME) >= lasttime) {
      if (tr->manim == GL_TRUE)
        glutIdleFunc(glutIdle);
      else
        tr->angle = 0.0;

      glutPostRedisplay( );
    } else if (tr->manim == GL_TRUE && olds == idw) {
      glutIdleFunc(NULL);
    }
  }

  olds = idw;
}

void motion(int x, int y) {
  pScene sc;
  pTransform tr;
  pPersp p;
  GLuint gtime;
  double deltax, deltay;
  float coeff, pos[3];
  int idw = currentScene( );

  /* default */
  if (picking) return;

  if (ddebug) fprintf(stdout, "motion\n");

  if (tracking == GL_FALSE) return;

  sc = cv.scene[idw];
  p = sc->persp;
  if (p->rubber == 1) return;

  /* what is transformed ? */
  if (sc->cube->active & C_EDIT)
    tr = sc->cube->cubetr;
  else if (sc->clip->active & C_EDIT)
    tr = sc->clip->cliptr;
  else
    tr = sc->view;

  if (tr->mstate != GLUT_DOWN) return;

  if (picking) tr->angle = 0.0f;

  gtime = glutGet(GLUT_ELAPSED_TIME);
  if ((animate || sc->type & S_FOLLOW) && gtime < lasttime + 40) return;

  if (tr->mbutton == GLUT_LEFT_BUTTON) {
    /* calculate axis of rotation: cross product */
    point2Vect(x, y, sc->par.xs, sc->par.ys, pos);
    tr->axis[0] = tr->pos[1] * pos[2] - tr->pos[2] * pos[1];
    tr->axis[1] = tr->pos[2] * pos[0] - tr->pos[0] * pos[2];
    tr->axis[2] = tr->pos[0] * pos[1] - tr->pos[1] * pos[0];

    /* calculate angle to rotate by */
    if (animate && saveimg) {
      tr->angle = 2.0f;
    } else {
      float dx, dy, dz;

      dx = pos[0] - tr->pos[0];
      dy = pos[1] - tr->pos[1];
      dz = pos[2] - tr->pos[2];
      tr->angle = 180.0 * sqrt(dx * dx + dy * dy + dz * dz);
    }

    /* reset for next time */
    tr->pos[0] = pos[0];
    tr->pos[1] = pos[1];
    tr->pos[2] = pos[2];
    lasttime = gtime;

    if (sc->cube->active & C_ON && sc->cube->active & C_EDIT)
      sc->cube->active |= C_UPDATE;
    else if (sc->clip->active & C_ON && (sc->clip->active & C_EDIT || sc->clip->active & C_FREEZE))
      sc->clip->active |= C_UPDATE;

    glutPostRedisplay( );
  } else if (tr->mbutton == GLUT_MIDDLE_BUTTON) {
    coeff = tr->manim == GL_TRUE ? 0.2 : 2.0;
    deltax = coeff * (x - startx) / (float)sc->par.xs;
    deltay = coeff * (starty - y) / (float)sc->par.ys;

    if (deltax != 0.0) tr->panx += -deltax * p->depth * tan(p->fovy / 360. * M_PI);

    if (deltay != 0.0) tr->pany += -deltay * p->depth * tan(p->fovy / 360. * M_PI);

    tr->angle = 0.0;
    startx = x;
    starty = y;

    lasttime = gtime;
    if (sc->cube->active & C_ON && sc->cube->active & C_EDIT)
      sc->cube->active |= C_UPDATE;
    else if (sc->clip->active & C_ON && (sc->clip->active & C_EDIT || sc->clip->active & C_FREEZE))
      sc->clip->active |= C_UPDATE;

    glutPostRedisplay( );
  }
}

void mouseCamera(int button, int state, int x, int y) {
  /* default */
  if (ddebug) printf("control mouse camera %d button %d\n", state, button);

  cbutton = button;
  if (state == GLUT_DOWN) {
    ctracking = GL_TRUE;
    startx = x;
    starty = y;
    curx = x;
    cury = y;
  } else {
    startx = x;
    starty = y;
    ctracking = GL_FALSE;
  }
}

void motionCamera(int x, int y) {
  pScene sc;
  pCamera c;
  double dazim, delev, azim, elev;
  float cfelev, cfazim;

  /* keep current pos */
  curx = x;
  cury = y;

  if (animate) return;

  sc = cv.scene[currentScene( )];
  c = sc->camera;
  azim = Azimuth(c);
  elev = Elevation(c);

  switch (cbutton) {
    case GLUT_LEFT_BUTTON:
      cfelev = 50.0;
      cfazim = 50.0;
      delev = cfelev * (y - starty) / (float)sc->par.ys;
      dazim = cfazim * (x - startx) / (float)sc->par.xs;
      startx = x;
      starty = y;
      elev += delev;
      azim -= dazim;
      break;
    case GLUT_MIDDLE_BUTTON:
      break;
    case GLUT_BUTTON_3:
      puts("button3");
      break;
    case GLUT_BUTTON_4:
      puts("button4");
      break;
  }

  updateCamera(sc, c, azim, elev);
  reshapeScene(sc->par.xs, sc->par.ys);
  glutPostRedisplay( );
}

void animateCamera( ) {
  pScene sc;
  pCamera c;
  double dazim, delev, azim, elev;
  float cfelev, cfazim;

  if (!animate || !ctracking) return;

  sc = cv.scene[currentScene( )];
  c = sc->camera;
  azim = Azimuth(c);
  elev = Elevation(c);

  switch (cbutton) {
    case GLUT_LEFT_BUTTON:
      cfelev = 3.0;
      cfazim = 3.0;
      delev = 2.0 * (cury - starty) / (float)sc->par.ys;
      dazim = 2.0 * (curx - startx) / (float)sc->par.xs;
      if (delev >= 0.0)
        delev *= delev;
      else
        delev = -delev * delev;

      if (dazim >= 0.0)
        dazim *= dazim;
      else
        dazim = -dazim * dazim;

      elev += cfelev * delev;
      azim -= cfazim * dazim;
      break;

    case GLUT_MIDDLE_BUTTON:
      break;
    case GLUT_BUTTON_3:
      puts("button3");
      break;
    case GLUT_BUTTON_4:
      puts("button4");
      break;
  }

  updateCamera(sc, c, azim, elev);
  reshapeScene(sc->par.xs, sc->par.ys);
  glutPostRedisplay( );
}

#ifdef __cplusplus
}
#endif
