// $Id: Opengl_Window.cpp,v 1.73 2006-10-31 20:20:21 geuzaine Exp $
//
// Copyright (C) 1997-2006 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 "Draw.h"
#include "SelectBuffer.h"
#include "GUI.h"
#include "Opengl_Window.h"

extern GUI *WID;
extern Context_T CTX;

void MousePosition::set()
{
  for(int i = 0; i < 3; i++){
    s[i] = CTX.s[i];
    t[i] = CTX.t[i];
  }

  win[0] = (double)Fl::event_x();
  win[1] = (double)Fl::event_y();
  win[2] = 0.;

  wnr[0] = 
    (CTX.vxmin + win[0] / (double)CTX.viewport[2] * (CTX.vxmax - CTX.vxmin)) 
    / CTX.s[0] - CTX.t[0] + CTX.t_init[0] / CTX.s[0];
  wnr[1] = 
    (CTX.vymax - win[1] / (double)CTX.viewport[3] * (CTX.vymax - CTX.vymin))
    / CTX.s[1] - CTX.t[1] + CTX.t_init[1] / CTX.s[1];
  wnr[2] = 0.;
}

void MousePosition::recenter()
{
  // compute the equivalent translation to apply *after* the scaling
  // so that the scaling is done around the point which was clicked:
  CTX.t[0] = t[0] * (s[0] / CTX.s[0]) - wnr[0] * (1. - (s[0] / CTX.s[0]));
  CTX.t[1] = t[1] * (s[1] / CTX.s[1]) - wnr[1] * (1. - (s[1] / CTX.s[1]));
}
  
void lasso_zoom(MousePosition &click1, MousePosition &click2)
{
  if(click1.win[0] == click2.win[0] || click1.win[1] == click2.win[1])
    return;

  CTX.s[0] *= (double)CTX.viewport[2] / (click2.win[0] - click1.win[0]);
  CTX.s[1] *= (double)CTX.viewport[3] / (click2.win[1] - click1.win[1]);
  CTX.s[2] = MIN(CTX.s[0], CTX.s[1]); // bof...
  
  // recenter around the center of the lasso rectangle
  MousePosition tmp(click1);
  tmp.wnr[0] = 0.5 * (click1.wnr[0] + click2.wnr[0]);
  tmp.wnr[1] = 0.5 * (click1.wnr[1] + click2.wnr[1]);
  tmp.recenter();

  InitPosition();
  Draw();
  WID->update_manip_window();
}

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(LassoMode) { 
    // draw the zoom or selection lasso on top of the current scene
    // (without using overlays!)
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho((double)CTX.viewport[0], (double)CTX.viewport[2],
	    (double)CTX.viewport[1], (double)CTX.viewport[3], -1., 1.);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glColor3d(1., 1., 1.);
    glDisable(GL_DEPTH_TEST);
    if(SelectionMode && CTX.enable_mouse_selection){
      glEnable(GL_LINE_STIPPLE);
      glLineStipple(1, 0x0F0F);
    }
    // glBlendEquation(GL_FUNC_ADD);
    glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
    glEnable(GL_BLEND);
    glLineWidth(0.2);
    for(int i = 0; i < 2; i++){
      glBegin(GL_LINE_STRIP);
      glVertex2d(click.win[0], CTX.viewport[3] - click.win[1]);
      glVertex2d(lasso.win[0], CTX.viewport[3] - click.win[1]);
      glVertex2d(lasso.win[0], CTX.viewport[3] - lasso.win[1]);
      glVertex2d(click.win[0], CTX.viewport[3] - lasso.win[1]);
      glVertex2d(click.win[0], CTX.viewport[3] - click.win[1]);
      glEnd();
      if(!i) lasso.set();
    }
    glDisable(GL_BLEND);
    if(SelectionMode && CTX.enable_mouse_selection)
      glDisable(GL_LINE_STIPPLE);
    glEnable(GL_DEPTH_TEST);
  }
  else if(AddPointMode) { 
    // draw the whole scene and the point to add
    if(CTX.fast_redraw) {
      CTX.mesh.draw = 0;
      CTX.post.draw = 0;
    }
    ClearOpengl();
    Draw3d();
    glColor4ubv((GLubyte *) & CTX.color.fg);
    glPointSize(CTX.geom.point_size);
    glBegin(GL_POINTS);
    glVertex3d(point[0], point[1], point[2]);
    glEnd();
    Draw2d();
    CTX.mesh.draw = 1;
    CTX.post.draw = 1;
  }
  else{
    // draw the whole scene
    ClearOpengl();
    Draw3d();
    Draw2d();
  }

  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)
{
  switch (event) {

  case FL_FOCUS: // accept the focus when I'm asked if I want it
  case FL_UNFOCUS:
    return 1;

  case FL_SHORTCUT:
  case FL_KEYBOARD:
    // override the default widget arrow-key-navigation
    if(WID->arrow_shortcuts())
      return 1;
    return Fl_Gl_Window::handle(event);
    
  case FL_PUSH:
    take_focus(); // force keyboard focus when we click in the window
    curr.set();
    if(Fl::event_button() == 1 && 
       !Fl::event_state(FL_SHIFT) && !Fl::event_state(FL_ALT)) {
      if(!LassoMode && Fl::event_state(FL_CTRL)) {
        LassoMode = true;
	lasso.set();
      }
      else if(LassoMode) {
        LassoMode = false;
	if(SelectionMode && CTX.enable_mouse_selection){
	  WID->try_selection = 2; // will try to select multiple entities
	  WID->try_selection_xywh[0] = (int)(click.win[0] + curr.win[0])/2;
	  WID->try_selection_xywh[1] = (int)(click.win[1] + curr.win[1])/2;
	  WID->try_selection_xywh[2] = (int)fabs(click.win[0] - curr.win[0]);
	  WID->try_selection_xywh[3] = (int)fabs(click.win[1] - curr.win[1]);
	}
	else{
	  lasso_zoom(click, curr);
	}
      }
      else if(CTX.enable_mouse_selection){
        WID->try_selection = 1; // will try to select clicked entity
	WID->try_selection_xywh[0] = (int)curr.win[0];
	WID->try_selection_xywh[1] = (int)curr.win[1];
	WID->try_selection_xywh[2] = 5;
	WID->try_selection_xywh[3] = 5;
      }
    }
    else if(Fl::event_button() == 2 || 
	    (Fl::event_button() == 1 && Fl::event_state(FL_SHIFT))) {
      if(!LassoMode && Fl::event_state(FL_CTRL)) {
	// make zoom isotropic
        CTX.s[1] = CTX.s[0];
        CTX.s[2] = CTX.s[0];
        redraw();
      }
      else if(LassoMode) {
	LassoMode = false;
	if(SelectionMode && CTX.enable_mouse_selection){
	  WID->try_selection = -2; // will try to unselect multiple entities
	  WID->try_selection_xywh[0] = (int)(click.win[0] + curr.win[0])/2;
	  WID->try_selection_xywh[1] = (int)(click.win[1] + curr.win[1])/2;
	  WID->try_selection_xywh[2] = (int)fabs(click.win[0] - curr.win[0]);
	  WID->try_selection_xywh[3] = (int)fabs(click.win[1] - curr.win[1]);
	}
	else{
	  lasso_zoom(click, curr);
	}
      }
      else if(CTX.enable_mouse_selection){
        WID->try_selection = -1; // will try to unselect clicked entity
	WID->try_selection_xywh[0] = (int)curr.win[0];
	WID->try_selection_xywh[1] = (int)curr.win[1];
	WID->try_selection_xywh[2] = 5;
	WID->try_selection_xywh[3] = 5;
      }
    }
    else {
      if(Fl::event_state(FL_CTRL) && !LassoMode) {
        if(CTX.useTrackball)
          CTX.setQuaternion(0., 0., 0., 1.);
        else
          CTX.r[0] = CTX.r[1] = CTX.r[2] = 0.;
	CTX.t[0] = CTX.t[1] = CTX.t[2] = 0.;
	CTX.s[0] = CTX.s[1] = CTX.s[2] = 1.;
        redraw();
      }
      else {
        LassoMode = false;
      }
    }
    click.set();
    prev.set();
    WID->update_manip_window();
    return 1;

  case FL_RELEASE:
    curr.set();
    CTX.draw_rotation_center = 0;
    if(!LassoMode) {
      CTX.mesh.draw = 1;
      CTX.post.draw = 1;
      redraw();
    }
    prev.set();
    return 1;

  case FL_MOUSEWHEEL:
    {
      double dy = Fl::event_dy();
      double fact = (5. * CTX.zoom_factor * fabs(dy) + h()) / (double)h();
      CTX.s[0] *= ((dy > 0) ? fact : 1./fact);
      CTX.s[1] = CTX.s[0];
      CTX.s[2] = CTX.s[0];
      redraw();
    }
    WID->update_manip_window();
    return 1;

  case FL_DRAG:
    curr.set();
    {
      double dx = curr.win[0] - prev.win[0];
      double dy = curr.win[1] - prev.win[1];
      if(LassoMode) {
	redraw();
      }
      else {
	if(Fl::event_button() == 1 && 
	   !Fl::event_state(FL_SHIFT) && !Fl::event_state(FL_ALT)) {
	  if(CTX.useTrackball)
	    CTX.addQuaternion((2. * prev.win[0] - w()) / w(),
			      (h() - 2. * prev.win[1]) / h(),
			      (2. * curr.win[0] - w()) / w(),
			      (h() - 2. * curr.win[1]) / h());
	  else {
	    CTX.r[1] += ((fabs(dx) > fabs(dy)) ? 180. * dx / (double)w() : 0.);
	    CTX.r[0] += ((fabs(dx) > fabs(dy)) ? 0. : 180. * dy / (double)h());
	  }
	}
	else if(Fl::event_button() == 2 ||
		(Fl::event_button() == 1 && Fl::event_state(FL_SHIFT))) {
	  if(fabs(dy) > fabs(dx)) {
	    double fact = (CTX.zoom_factor * fabs(dy) + h()) / (double)h();
	    CTX.s[0] *= ((dy > 0) ? fact : 1./fact);
	    CTX.s[1] = CTX.s[0];
	    CTX.s[2] = CTX.s[0];
	    click.recenter();
	  }
	  else if(!CTX.useTrackball)
	    CTX.r[2] += -180. * dx / (double)w();
	}
	else {
	  CTX.t[0] += (curr.wnr[0] - click.wnr[0]);
	  CTX.t[1] += (curr.wnr[1] - click.wnr[1]);
	  CTX.t[2] = 0.;
	}
	CTX.draw_rotation_center = 1;
	if(CTX.fast_redraw) {
	  CTX.mesh.draw = 0;
	  CTX.post.draw = 0;
	}
	redraw();
      }
    }
    prev.set();
    WID->update_manip_window();
    return 1;

  case FL_MOVE:
    curr.set();
    if(LassoMode) {
      redraw();
    }
    else if(AddPointMode && !Fl::event_state(FL_SHIFT)){
      WID->g_opengl_window->cursor(FL_CURSOR_CROSS, FL_BLACK, FL_WHITE);
      // find line in real space corresponding to current cursor position
      double p[3],d[3];
      Unproject(curr.win[0], curr.win[1], p, d);
      // fin closest point to the center of gravity
      double r[3] = {CTX.cg[0] - p[0], CTX.cg[1] - p[1], CTX.cg[2] - p[2]}, t;
      prosca(r, d, &t);
      for(int i = 0; i < 3; i++){
	point[i] = p[i] + t * d[i];
	if(CTX.geom.snap[i]){
	  double d = point[i]/CTX.geom.snap[i];
	  double f = floor(d);
	  double c = ceil(d);
	  double n = (d - f < c - d) ? f : c;
	  point[i] = n * CTX.geom.snap[i];
	}
      }
      char str[32];
      sprintf(str, "%g", point[0]); WID->context_geometry_input[2]->value(str);
      sprintf(str, "%g", point[1]); WID->context_geometry_input[3]->value(str);
      sprintf(str, "%g", point[2]); WID->context_geometry_input[4]->value(str);
      redraw();
    }
    else{ // hover mode
      if(curr.win[0] != prev.win[0] || curr.win[1] != prev.win[1]){
	WID->make_opengl_current();
	std::vector<GVertex*> vertices;
	std::vector<GEdge*> edges;
	std::vector<GFace*> faces;
	std::vector<GRegion*> regions;
	std::vector<MElement*> elements;
	int meshSelection = CTX.enable_mouse_selection > 1 ? 1 : 0;
	bool something = ProcessSelectionBuffer(WID->selection, false,
						(int)curr.win[0], (int)curr.win[1], 5, 5, 
						vertices, edges, faces, regions,
						elements, meshSelection);
	if((WID->selection == ENT_ALL && something) ||
	   (WID->selection == ENT_POINT && vertices.size()) ||
	   (WID->selection == ENT_LINE && edges.size()) || 
	   (WID->selection == ENT_SURFACE && faces.size()) ||
	   (WID->selection == ENT_VOLUME && regions.size()))
	  WID->g_window->cursor(FL_CURSOR_CROSS, FL_BLACK, FL_WHITE);
	else
	  WID->g_window->cursor(FL_CURSOR_DEFAULT, FL_BLACK, FL_WHITE);
	HighlightEntity(vertices.empty() ? 0 : vertices[0], 
			edges.empty() ? 0 : edges[0],
			faces.empty() ? 0 : faces[0],
			regions.empty() ? 0 : regions[0]);
      }
    }
    prev.set();
    return 1;

  default:
    return Fl_Gl_Window::handle(event);
  }
}