// Common procedures for all GL operations 
// performed on an X orm window
//
// Eric Amram, Oct 1997
// with mods by Neel Master and Seth Teller, Feb 1998
//

// system includes
#include <iostream.h>
#include <assert.h>
#include <sys/signal.h>
#include <sys/time.h>
#include <math.h>

// X includes
#include <X11/X.h>
#include <Xm/Xm.h>

// openGL includes
#include <GL/gl.h>
#include <GL/glx.h>
#include <GL/glu.h>


// library includes
#include "gprims/vector.H"

// local includes
#include "glcanvas.H"


// ======================================================================
// ============= First XForms Init ======================================
// ======================================================================

static int Tempargc = 1;
static char *Tempargv[] = {"MIT Computer Graphics Group"};


// =======================================================

const int GLF_default_attrib[] = {
  GLX_RGBA, GLX_DEPTH_SIZE,1, GLX_DOUBLEBUFFER,
  GLX_RED_SIZE,1, GLX_GREEN_SIZE,1, GLX_BLUE_SIZE,1, None};


// Call this from the first main() function, and never after
void
FirstXformsInit()
{
  fprintf(stderr, "\n---Mouse buttons:\n\n");
  fprintf(stderr, "\t Left   button : Trackball & Picking\n");
  fprintf(stderr, "\t Middle button : Translation\n");
  fprintf(stderr, "\t Right  button : [move horizontally to] Zoom\n");
  fprintf(stderr, "\t Lf+Mid button : [move horizontally to] Rotate in image plane\n\n");
  
  // set the defaults attributes of GL canvases
  fl_set_glcanvas_defaults( GLF_default_attrib );
  fl_initialize(&Tempargc, Tempargv, "MIT Graphics Group", 0, 0); 
  return;
}


// ======================================================================
// ============= Intermediate functions for C interface =================
// ======================================================================

int    // substitute for GLCan::handleEvent
TempHandleEvent(FL_OBJECT*, Window, int wwin, int hwin, XEvent *ev, void *tmpglcan)
{
  ((GLCan *)tmpglcan)->handleEvent(ev, wwin, hwin);
  return 0;
} 



// ======================================================================
// ======================== GLCan class methods =========================
// ======================================================================

void
GLCan::initializeGLState( void )
{
  fl_activate_glcanvas(id);
  displC->UserGLInit();
  return;
}

void
GLCan::initGL(DisplayGLCanvas *disp, const int& nGL, const int& idn, FL_OBJECT *ob )
{
  fl_activate_glcanvas(ob);

  // Class variable initializations
  displC = disp;  // associated display canvas
  numGLcan = nGL;
  idnum = idn;
  id = ob;        // GL canvas FL_OBJECT 
  interact = displC->interactionMode;
  wind = fl_get_canvas_id(id);  // window of this canvas
  fl_get_winsize(wind, &width, &height);  // canvas size

  aspect = (float)width / (float)height;
  // Mouse buttons (initially not depressed)
  MouseDown[0] = MouseDown[1] = MouseDown[2] = 0;
  cur_x = cur_y = prev_x = prev_y = 0; // Mouse position

  // request MotionNotify events only when some mouse button is pressed
  fl_remove_selected_xevent( wind, PointerMotionMask | PointerMotionHintMask );
  fl_remove_selected_xevent( wind, ButtonMotionMask );
  
  // GL initializations
  initializeGLState();

  // reset view matrices; invokes UserBounds() to get axial bounding box
  posReset();

  // add GL canvas to Display objects
  disp->id = ob;
 
  // XXX force a deferred redraw 
  displC->_dirty = 1; 
  return;
}


// =====================================================
//   HandleEvent()
//   -----------------
//   XEvent dispatcher
// ===================================================== 


// tolerance in change between current and previous 
// mouse positions to warrant a double click 
#define DOUBLE_CLICK_DIFFERENTIAL 3

// !!!!!! must depend on the mode !!!!
void
GLCan::handleEvent(XEvent *ev, const int& wwin, const int& hwin)  
{
  // to allow external routines to handle the right GL canvas
  fl_activate_glcanvas(id);  
  
  switch (ev->type) {
    
  case ResizeRequest: { // window has been resized
    XResizeRequestEvent *resizeEv = (XResizeRequestEvent *)ev;
    printf("Trying to resize ..\n");
    printf("Width %d Height %d ..\n", resizeEv->width, resizeEv->height );
    width = resizeEv->width;
    height = resizeEv->height;
    aspect = (float)width / (float)height;
	displC->_dirty = 1; // force a redraw
  }
  break;

  case Expose: { // window has been exposed
    // attempt to resize the active form
    if (wwin != width || hwin != height) {   
      width = wwin; height = hwin;      
      
	  // XXX -- this should simply update aspect ratio, then call
	  // a single routine which re-specificies gluPerspective() !
      glViewport( 0, 0, width, height ); 
      aspect = (float)width / (float)height;
      glMatrixMode( GL_PROJECTION );
      glLoadIdentity();
      gluPerspective(30.0, aspect, 0.1, 100.0);
    }
	displC->_dirty = 1; // force a redraw
  } // Expose
  break;
    
  case ButtonPress: { // mouse has been pressed
    MouseDown[ev->xbutton.button - 1] = 1;
    
    // check double click
    //
    // make sure last button pressed is the same as
    // the current button, and that very little movement
    // occurred 
    //

    MOUSEINFO mi;

    // check for double click
    if( (fabsf ( cur_x - ev->xbutton.x ) 
		 < DOUBLE_CLICK_DIFFERENTIAL ) &&
		(fabsf ( cur_y - (height - ev->xbutton.y) ) 
		 < DOUBLE_CLICK_DIFFERENTIAL ) &&
		( _lastMouse == ev->xbutton.button - 1 ) )
      mi.doubleClick = 1;
    else
      mi.doubleClick = 0;
    
    // update persistent state
    _lastMouse = ev->xbutton.button - 1;
    prev_x = cur_x = ev->xbutton.x;
    prev_y = cur_y = height - ev->xbutton.y;
    _mxD = cur_x;
    _myD = cur_y;
    _buttonMotion = 0;

    // update mouse info
    mi.curX = cur_x;
    mi.curY = cur_y;
    mi.prevX = prev_x;
    mi.prevY = prev_y;
    mi.mouseButton = ev->xbutton.button - 1;
    mi.buttonMotion = _buttonMotion;
    mi.event = ev->type;
    
    // map mouse action to world-space ray, and pass event to user
    if ( displC->rayAction( &mi, NULL ) )
	  displC->_dirty = 1; // force a redraw
  }
  break;

  case MotionNotify : { // mouse has moved
    _buttonMotion = 1;
    prev_x = cur_x;
    prev_y = cur_y;
    cur_x = ev->xmotion.x;
	cur_y = height - ev->xmotion.y;  

	// UserHandle() returns NULL, or h if something selected
	// this allows user code to decide to "deselect" objects
	// if object currently selected, and left mouse is down,
	// inform selected user object of this mouse motion
	PHANDLE pHandle = displC->UserHandle();
	if( ( NULL != pHandle ) && ( 1 == MouseDown[0] ) ) {
	  MOUSEINFO mi;
	  mi.curX = cur_x;
	  mi.curY = cur_y;
	  mi.prevX = prev_x;
	  mi.prevX = prev_x;
	  // HACK -- motion events don't include a useful button id
	  // thus we must fill it in ourselves !  hardcoded to L mouse.
	  mi.mouseButton = 0;  // left mouse
	  mi.buttonMotion = _buttonMotion;
      mi.doubleClick = 0;
	  mi.event = ev->type;

	  // map mouse action to world-space ray, and pass event to user
	  if ( displC->rayAction( &mi, pHandle ) )
		displC->_dirty = 1; // force a redraw
	  }
    else if ( MouseDown[0] && 
			  (MouseDown[1] || interact==GLCAN_INTERACT_MODE_2D) ) {
	  // nothing selected, left mouse && ortho, or left && middle mouse
      updateViewRot();
	  displC->_viewChange = 1;
	  displC->_dirty = 1; // force a redraw
	  }
    else if ( MouseDown[0] && interact == GLCAN_INTERACT_MODE_3D ) {
	  // nothing selected, left mouse down; rotate 3D
      updateBallRot((float)cur_x-prev_x, (float)cur_y-prev_y);
	  displC->_viewChange = 1;
	  displC->_dirty = 1; // force a redraw
	  }
    else if ( MouseDown[1] ) {
	  // nothing selected, middle mouse down; translate
      updateViewTranslate();
	  displC->_viewChange = 1;
	  displC->_dirty = 1; // force a redraw
	  }
    else if ( MouseDown[2] ) {
	  // nothing selected, right mouse down; zoom
      updateBallZoom();
	  displC->_viewChange = 1;
	  displC->_dirty = 1; // force a redraw
	  }
    }
    break;
    
  case ButtonRelease: { // mouse button released
    
    cur_x = ev->xmotion.x;  
    cur_y = height - ev->xmotion.y;  
    
    MouseDown[ev->xbutton.button - 1] = 0;
    
    // action on ray
    MOUSEINFO mi;
    mi.curX = cur_x;
    mi.curY = cur_y;
    mi.prevX = prev_x;
    mi.prevX = prev_x;
    mi.mouseButton = ev->xbutton.button - 1;
    mi.buttonMotion = _buttonMotion;
    mi.doubleClick = 0;
    mi.event = ev->type;
    
    // map mouse action to world-space ray, and pass event to user
    if ( displC->rayAction( &mi, NULL ) )
	  displC->_dirty = 1; // force a redraw
  }
  break;

  default:
    break;
  }
  
  return;
}


// =====================================================
//   View Updates
//   -----------------
//   Inventor-like mouse processing
// ===================================================== 

void
GLCan::render()
{
  fl_activate_glcanvas(id);

  // set up viewing matrix
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glMultMatrixf( displC->matr );
  
  // call user refresh method
  displC->UserDraw();

  // swap rendered framebuffer forward
  glXSwapBuffers(fl_get_display(), wind);

  // clear the dirty bit of this canvas
  displC->_dirty = 0;
  return;
}


void 
GLCan::updateBallRot(float dx, float dy)
{
  float theta;

  if ((!dx) && (!dy)) return;
 
  dx *= 180.0 / (float)width;  
  dy *= 180.0 / (float)height;

  theta = dx*dx + dy*dy;
  theta = sqrtf(theta);
  dx /= theta;
  dy /= theta;

  fl_activate_glcanvas( id );
  glMatrixMode( GL_MODELVIEW );
  glLoadIdentity();
  glRotatef( theta, -dy, dx, 0.0 );        
  glMultMatrixf( displC->matr );  
  glGetFloatv( GL_MODELVIEW_MATRIX, displC->matr );

  displC->_dirty = 1; // XXX

  return;
}

void 
GLCan::updateBallZoom()
{
  float dx = (float)cur_x - (float)prev_x;
  dx *= 3.0 * zoomS / (float)width;

  // if motion is to the right, zoom in by decreasing
  // the ball radius, otherwise zoom out
  if (dx > 0) dx= 1.0 / (dx + 1.0);
  else dx = -dx + 1.0;

  fl_activate_glcanvas(id);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glScalef(dx, dx, dx);
  glMultMatrixf(displC->matr);
  glGetFloatv(GL_MODELVIEW_MATRIX, displC->matr);

  displC->_dirty = 1; // XXX

  return;
}

void 
GLCan::updateViewRot()
{
  float dx = (float)cur_x - (float)prev_x;
  dx *= -540.0 / (float)width;

  fl_activate_glcanvas(id); // XXX shouldn't be needed
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glRotatef(dx, 0, 0, 1);
  glMultMatrixf(displC->matr);
  glGetFloatv(GL_MODELVIEW_MATRIX, displC->matr);

  displC->_dirty = 1; // XXX

  return;
}

void 
GLCan::updateViewTranslate()
{
  float tx = ((float)cur_x - prev_x) / (float)width * 2.0 * trScale;
  float ty = ((float)cur_y - prev_y) / (float)height * 2.0 * trScale;
    
  fl_activate_glcanvas(id);
  glMatrixMode( GL_MODELVIEW );
  glLoadIdentity();
  glTranslatef(tx, ty, 0);
  glMultMatrixf(displC->matr);
  glGetFloatv(GL_MODELVIEW_MATRIX, displC->matr);

  displC->_dirty = 1; // XXX

  return;
}

void
GLCan::posReset()
{
  trScale = zoomS = 1.0;
  fl_activate_glcanvas(id);

  // get data bounding box from user
  // float xmin, ymin, zmin, xmax, ymax, zmax, dx, dy, dz;
  // displC->UserBounds( xmin, ymin, zmin, xmax, ymax, zmax );
  
  Point3 min, max;
  displC->UserBounds( min, max );


  //cout << "user reports bounds of [" << xmin << ", " << ymin << ", " << zmin << "], ";
  //cout << "[" << xmax << ", " << ymax << ", " << zmax << "]" << endl;

  cout << "user reports bounds of [" << min << "], ";
  cout << "[" << max << "]" << endl;

   // XXX set zoom factor 
  // dx = xmax - xmin;
  // dy = ymax - ymin;
  // dz = zmax - zmin;
  Point3 diff = max - min;

  float maxdim = diff.x();
  if ( diff.y() > maxdim ) maxdim = diff.y();
  if ( diff.z() > maxdim ) maxdim = diff.z();

  // XXX set zoom factor
  zoomS = sqrtf(3.0) / maxdim;

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  // XXX should translate here to deal with user bbox not centered at origin !
  glScalef( zoomS, zoomS, zoomS );
  glGetFloatv( GL_MODELVIEW_MATRIX, displC->matr );

  displC->_dirty = 1; // force deferred redraw
  return;
}


// ======================================================================
// ================= DisplayGLCanvas class methods ======================
// ======================================================================


DisplayGLCanvas      **DisplayGLCanvas::_canvasGroups = 0;
int                    DisplayGLCanvas::_numGroups = 0;


//
// Constructors
//
DisplayGLCanvas::DisplayGLCanvas( void ) : 
  interactionMode( 0 ), 
  modifyGLlist( 1 ), 
  _viewChange( 1 )
{
  _canvasGroups = new DisplayGLCanvas *[ GLCAN_MAX_GROUP_MEMBERS ] ;
  _numGroups = 0;
}


DisplayGLCanvas::DisplayGLCanvas( DisplayGLCanvas *glCanvas ) :
  interactionMode( 0 ), 
  modifyGLlist( 1 ), 
  _viewChange( 1 )
{  
  addCanvasToGroup ( this );
  addCanvasToGroup ( glCanvas );
  printf( "Number of canvases in the group: %d \n", _numGroups );
}

void
DisplayGLCanvas::addCanvasToGroup( DisplayGLCanvas *glCanvas ) {
  _canvasGroups[ _numGroups++ ]  = glCanvas;
}


//
// Default OpenGL Initializations
// 
void
DisplayGLCanvas::UserGLInit( void )
{
  printf( "Using default initialization..\n" );
  glPixelStorei(GL_UNPACK_ALIGNMENT,1);
  glClearColor(0,0,0,0);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_LINE_SMOOTH);
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 
  
  glLightfv(GL_LIGHT0, GL_AMBIENT, materlight_dgrey);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, materlight_white);  
  glLightfv(GL_LIGHT0, GL_SPECULAR, materlight_white);
  // ([3]==0 => directional light)
  glLightfv(GL_LIGHT0, GL_POSITION, light_direction);
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, materlight_dgrey);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);

  // initialize GL matrices
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(30.0, 1, 0.1, 100.0);   // XXX aspect should match viewport
  glTranslatef(0.0, 0.0, -4.0);  

  // force renormalization of normals
  glEnable(GL_NORMALIZE);
}



// ======================================================================
// ================= DisplayGLCan class methods =========================
// ======================================================================


void 
DisplayGLCanvas::resetView( void ) {  

  fl_activate_glcanvas(id);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  
  matr[0]  = 1; matr[1]  = 0; matr[2]  = 0; matr[3]  = 0;
  matr[4]  = 0; matr[5]  = 1; matr[6]  = 0; matr[7]  = 0;
  matr[8]  = 0; matr[9]  = 0; matr[10] = 1; matr[11] = 0;
  matr[12] = 0; matr[13] = 0; matr[14] = 0; matr[15] = 1;

  // call GLCan's reset method, somehow
  // posReset();

  _dirty = 1; // force redraw
}
