// $Id: Opengl_Window.cpp,v 1.38 2004-08-28 00:48:37 geuzaine Exp $
//
// Copyright (C) 1997-2004 C. Geuzaine, J.-F. Remacle
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA.
// 
// Please report all bugs and problems to <gmsh@geuz.org>.

#include "Gmsh.h"
#include "Numeric.h"
#include "GmshUI.h"
#include "Context.h"
#include "Geo.h"
#include "Mesh.h"
#include "Draw.h"
#include "GUI.h"
#include "Opengl_Window.h"

// This file defines the Opengl_Window class (subclass of Fl_GL_Window)

extern GUI *WID;
extern Mesh M;
extern Context_T CTX;

void Process_SelectionBuffer(int x, int y, int *n, GLuint * ii, GLuint * jj);
void Filter_SelectionBuffer(int n, GLuint * typ, GLuint * ient,
                            Vertex ** thev, Curve ** thec, Surface ** thes,
                            Mesh * m);
void myZoom(GLdouble X1, GLdouble X2, GLdouble Y1, GLdouble Y2, GLdouble Xc1,
            GLdouble Xc2, GLdouble Yc1, GLdouble Yc2);
int check_type(int type, Vertex * v, Curve * c, Surface * s);

static int ZOOM = 0;
static double ZOOM_X0, ZOOM_Y0, ZOOM_X1, ZOOM_Y1;

void Opengl_Window::draw()
{
  static int locked = 0;
  if(locked)
    return;
  else
    locked = 1;
  Msg(DEBUG, "Opengl_Window->draw()");
  if(!valid()) {
    valid(1);
    CTX.viewport[0] = 0;
    CTX.viewport[1] = 0;
    CTX.viewport[2] = w();
    CTX.viewport[3] = h();
    glViewport(CTX.viewport[0], CTX.viewport[1],
               CTX.viewport[2], CTX.viewport[3]);
  }
  else {
    if((w() != CTX.viewport[2] - CTX.viewport[0]) ||
       (h() != CTX.viewport[3] - CTX.viewport[1])) {
      WID->set_size(CTX.viewport[2] - CTX.viewport[0],
                    CTX.viewport[3] - CTX.viewport[1]);
      glViewport(CTX.viewport[0], CTX.viewport[1],
                 CTX.viewport[2], CTX.viewport[3]);
    }
  }

  if(!ZOOM) {
    Orthogonalize(0, 0);
    ClearOpengl();
    Draw3d();
    Draw2d();
  }
  else {
    glPopMatrix();
    glDisable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(CTX.vxmin, CTX.vxmax, CTX.vymin, CTX.vymax);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glDisable(GL_DEPTH_TEST);
    glColor3f(1., 1., 1.);
    glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
    glBlendEquation(GL_FUNC_ADD);
    glEnable(GL_BLEND);
    glLineWidth(0.2);
    glBegin(GL_LINE_STRIP);
    glVertex2d(ZOOM_X0, ZOOM_Y0);
    glVertex2d(ZOOM_X1, ZOOM_Y0);
    glVertex2d(ZOOM_X1, ZOOM_Y1);
    glVertex2d(ZOOM_X0, ZOOM_Y1);
    glVertex2d(ZOOM_X0, ZOOM_Y0);
    glEnd();
    ZOOM_X1 =
      CTX.vxmin + ((double)Fl::event_x() / (double)w()) * (CTX.vxmax -
                                                           CTX.vxmin);
    ZOOM_Y1 =
      CTX.vymax - ((double)Fl::event_y() / (double)h()) * (CTX.vymax -
                                                           CTX.vymin);
    glBegin(GL_LINE_STRIP);
    glVertex2d(ZOOM_X0, ZOOM_Y0);
    glVertex2d(ZOOM_X1, ZOOM_Y0);
    glVertex2d(ZOOM_X1, ZOOM_Y1);
    glVertex2d(ZOOM_X0, ZOOM_Y1);
    glVertex2d(ZOOM_X0, ZOOM_Y0);
    glEnd();
    glDisable(GL_BLEND);
    glEnable(GL_DEPTH_TEST);
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    ZOOM = 0;
  }
  locked = 0;
}

// The event model in FLTK is pretty different from other toolkits:
// the events are passed to the widget handle of the widget that has
// the focus. If this handle returns 1, then the event is considered
// as treated, and is suppressed. If the handle returns 0, the event
// is passed to the parent.

int Opengl_Window::handle(int event)
{
  static int xpos, ypos, xmov, ymov, ibut, hits;
  static int ZoomClick = 0, FirstClick = 0;
  static GLdouble xc, yc, xc1, yc1, xc2, yc2, xt1, yt1, xscale1, yscale1;
  static Vertex *v = NULL, *ov;
  static Curve *c = NULL, *oc;
  static Surface *s = NULL, *os;

  GLuint ii[SELECTION_BUFFER_SIZE], jj[SELECTION_BUFFER_SIZE];

  switch (event) {

  case FL_ENTER:
    take_focus();       //force keyboard focus on the Opengl_Window
    return 1;

  case FL_LEAVE:
    return 1;

  case FL_FOCUS:
    return 1;

  case FL_UNFOCUS:
    return 1;

  case FL_SHORTCUT:
  case FL_KEYBOARD:
    // this overrides the default navigation
    if(WID->arrow_shortcuts()) {
      return 1;
    }
    return Fl_Gl_Window::handle(event);

  case FL_PUSH:
    FirstClick = 1;
    ibut = Fl::event_button();
    xpos = Fl::event_x();
    ypos = Fl::event_y();

    if(ibut == 1 && !Fl::event_state(FL_SHIFT) && !Fl::event_state(FL_ALT)) {
      if(!ZoomClick && Fl::event_state(FL_CTRL)) {
        ZOOM_X0 = ZOOM_X1 =
          CTX.vxmin + ((double)xpos / (double)w()) * (CTX.vxmax - CTX.vxmin);
        ZOOM_Y0 = ZOOM_Y1 =
          CTX.vymax - ((double)ypos / (double)h()) * (CTX.vymax - CTX.vymin);
        xc1 = ZOOM_X0 / CTX.s[0] - CTX.t[0];
        yc1 = ZOOM_Y0 / CTX.s[1] - CTX.t[1];
        ZoomClick = 1;
      }
      else if(ZoomClick) {
        xc2 = ZOOM_X1 / CTX.s[0] - CTX.t[0];
        yc2 = ZOOM_Y1 / CTX.s[1] - CTX.t[1];
        ZoomClick = 0;
        if(ZOOM_X0 != ZOOM_X1 && ZOOM_Y0 != ZOOM_Y1)
          myZoom(ZOOM_X0, ZOOM_X1, ZOOM_Y0, ZOOM_Y1, xc1, xc2, yc1, yc2);
      }
      else {
        WID->try_selection = 1;
      }
    }
    else if(ibut == 2 || (ibut == 1 && Fl::event_state(FL_SHIFT))) {
      if(Fl::event_state(FL_CTRL) && !ZoomClick) {
        set_s(1, CTX.s[0]);
        set_s(2, CTX.s[0]);
        redraw();
      }
      else {
        ZoomClick = 0;
      }
    }
    else {
      if(Fl::event_state(FL_CTRL) && !ZoomClick) {
        if(CTX.useTrackball)
          CTX.setQuaternion(0., 0., 0., 1.);
        else {
          set_r(0, 0.);
          set_r(1, 0.);
          set_r(2, 0.);
        }
        set_t(0, 0.);
        set_t(1, 0.);
        set_t(2, 0.);
        set_s(0, 1.);
        set_s(1, 1.);
        set_s(2, 1.);
        redraw();
      }
      else {
        ZoomClick = 0;
      }
    }
    return 1;

  case FL_RELEASE:
    ibut = Fl::event_button();
    xpos = Fl::event_x();
    ypos = Fl::event_y();
    if(!ZoomClick) {
      CTX.mesh.draw = 1;
      CTX.post.draw = 1;
      redraw();
    }
    return 1;

  case FL_DRAG:
    xmov = Fl::event_x() - xpos;
    ymov = Fl::event_y() - ypos;

    if(ZoomClick) {
      ZOOM = 1;
      redraw();
    }
    else {
      if(FirstClick) {
        xc1 = (((double)xpos / (double)w()) * (CTX.vxmax - CTX.vxmin) + CTX.vxmin)
          / CTX.s[0] - CTX.t[0];
        yc1 = (CTX.vymax - ((double)ypos / (double)h()) * (CTX.vymax - CTX.vymin))
          / CTX.s[1] - CTX.t[1];
        xt1 = CTX.t[0];
        yt1 = CTX.t[1];
        xscale1 = CTX.s[0];
        yscale1 = CTX.s[1];
        FirstClick = 0;
      }

      if(ibut == 1 && !Fl::event_state(FL_SHIFT) && !Fl::event_state(FL_ALT)) {
        if(CTX.useTrackball)
          CTX.addQuaternion((2.0 * xpos - w()) / w(),
                            (h() - 2.0 * ypos) / h(),
                            (2.0 * Fl::event_x() - w()) / w(),
                            (h() - 2.0 * Fl::event_y()) / h());
        else {
          set_r(1, CTX.r[1] + ((abs(xmov) > abs(ymov)) ?
			       180 * (double)xmov / (double)w() : 0));
          set_r(0, CTX.r[0] + ((abs(xmov) > abs(ymov)) ? 
			       0 : 180 * (double)ymov / (double)h()));
        }
      }
      else if(ibut == 2 || (ibut == 1 && Fl::event_state(FL_SHIFT))) {
        if(!CTX.useTrackball)
          set_r(2, CTX.r[2] + ((abs(ymov) > abs(xmov)) ? 
			       0 : -180 * (double)xmov / (double)w()));
        set_s(0, CTX.s[0] * ((abs(ymov) > abs(xmov)) ? 
			     ((ymov > 0) ? (double)(CTX.zoom_factor * (abs(ymov) + h())) /
                              (double)h() : 
			      (double)(h()) / (double)(CTX.zoom_factor * 
						       (abs(ymov) + h()))) : 1.));
        set_s(1, CTX.s[0]);
        set_s(2, CTX.s[0]);
        if(abs(ymov) > abs(xmov)) {
          set_t(0, xt1 * (xscale1 / CTX.s[0]) - xc1 * (1. - (xscale1 / CTX.s[0])));
          set_t(1, yt1 * (yscale1 / CTX.s[1]) - yc1 * (1. - (yscale1 / CTX.s[1])));
        }
      }
      else {
        xc = (((double)xpos / (double)w()) * (CTX.vxmax - CTX.vxmin) + CTX.vxmin)
          / CTX.s[0];
        yc = (CTX.vymax - ((double)ypos / (double)h()) * (CTX.vymax - CTX.vymin))
          / CTX.s[1];
        set_t(0, xc - xc1);
        set_t(1, yc - yc1);
        set_t(2, 0.);
      }

      if(CTX.fast_redraw) {
        CTX.mesh.draw = 0;
        CTX.post.draw = 0;
      }

      redraw();
    }

    xpos += xmov;
    ypos += ymov;
    return 1;


  case FL_MOVE:
    xmov = Fl::event_x() - xpos;
    ymov = Fl::event_y() - ypos;

    if(ZoomClick) {
      ZOOM = 1;
      redraw();
    }
    else {
      WID->make_opengl_current();
      Process_SelectionBuffer(Fl::event_x(), Fl::event_y(), &hits, ii, jj);
      ov = v;
      oc = c;
      os = s;
      v = NULL;
      c = NULL;
      s = NULL;
      Filter_SelectionBuffer(hits, ii, jj, &v, &c, &s, &M);
      if(ov != v || oc != c || os != s) {
        if(check_type(WID->selection, v, c, s))
          WID->g_window->cursor(FL_CURSOR_CROSS, FL_BLACK, FL_WHITE);
        else
          WID->g_window->cursor(FL_CURSOR_DEFAULT, FL_BLACK, FL_WHITE);
        HighlightEntity(v, c, s, 0);
      }
    }

    xpos += xmov;
    ypos += ymov;
    return 1;

  default:
    return Fl_Gl_Window::handle(event);

  }

}