 // system includes
#include <iostream.h>
#include <stdlib.h>  // for int rand()
#include <assert.h>
#include <math.h>
#include <limits.h>

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

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

// local includes
#include "object.H"
#include "drawutils.H"
#include "visapp.H"
#include "log.h"

////////////////////////////////////////////////////////////
//
// Base Viewing Class, AppView
//
////////////////////////////////////////////////////////////
Log rclog;


//
// Constructors
//
AppView::AppView( void )
{
  interactionMode = GLCAN_INTERACT_MODE_3D;
  _app = NULL;
  _dirty = 1;  // force a redraw at beginning of time
  usr_msg = "hello";
}


// XXX is this constructor really necessary ?
AppView::AppView( App *app ) 
{ 
  _app = app; 
  _dirty = 1;  // force redraw of new view
  interactionMode = GLCAN_INTERACT_MODE_3D;
  _app->_currObject = NULL;
  usr_msg = "hello";
}


// XXX is this constructor really necessary ?
AppView::AppView( App *app, AppView *groupAppView ) :
  DisplayGLCanvas( ( DisplayGLCanvas *) groupAppView )
{
  _app = app; 
  _dirty = 1;  // force redraw of new view
  interactionMode = GLCAN_INTERACT_MODE_3D;
  usr_msg = "hello";
}  


// user supplies code to compute world-space axial bounds
void
AppView::UserBounds( Point3& min, Point3& max  )
{
  //  float xmin, float ymin, float zmin;
  //float xmax, float ymax, float zmax;
  
  // initialize bounding box to have min > max

  Point3 omin( FLT_MAX,   FLT_MAX,   FLT_MAX );
  Point3 omax( -FLT_MAX, -FLT_MAX, -FLT_MAX );

  // incorporate all objects
  if ( _app )
    for( int i=0; i < _app->_numObjects; i++ ) {
      _app->_objects[ i ]->bounds( omin, omax );
      
      if ( omin.x() < min.x() ) min[0] = omin[0];
      if ( omin.y() < min.y() ) min[1] = omin[1];
      if ( omin.z() < min.z() ) min[2] = omin[2];
      
      if ( max.x() < omax.x() ) max[0] = omax[0];
      if ( max.y() < omax.y() ) max[1] = omax[1];
      if ( max.z() < omax.z() ) max[2] = omax[2];
    }
  
  if ( min.x() > max.x() || min.y() > max.y() || min.z() > max.z() ) {
    min[0] = min[1] = min[2] = -1.0f; 
    max[0] = max[1] = max[2] =  1.0f;
  }

}




// draw arena and all existing objects
// call bounding box action of highlighted object
// typically this is NOT overloaded, unless you 
// wish to draw differently depending on whether
// the 2D or 3D canvas is being refreshed.

// NOTE: all draw methods should protect matrix stack with
// glPushMatrix(); ... glPopMatrix(); if they alter it

void
AppView::UserDraw( void ) {
  
  // clear framebuffer, depthbuffer
  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  glLineWidth(2.0);

  glBegin(GL_LINES);
  glColor3f( 1.0,1.0,1.0 );                         // set generic color information
  glVertex3f(0.0,0.0,0.0);
  glVertex3f(10.0,10.0,10.0);
  glEnd();

}

////////////////////////////////////////////////////////////
//
// Default lighting for all viewers
//
////////////////////////////////////////////////////////////

static void
DefineLighting()
{
  // define light
  float pos[4] = { 2.0, 2.0, 2.0, 0.0 }; // position
  glLightfv(GL_LIGHT0+0, GL_POSITION, pos);

  float amb[4] = { 0.4, 0.4, 0.4, 1.0 }; // ambient
  glLightfv(GL_LIGHT0+0, GL_AMBIENT, amb);

  float diff[4] = { 0.3, 0.3, 0.3, 1.0 }; // diffuse
  glLightfv(GL_LIGHT0+0, GL_DIFFUSE, diff );

  float spec[4] = { 0.1, 0.1, 0.1, 1.0 }; // specular
  glLightfv(GL_LIGHT0+0, GL_SPECULAR, spec );

  // enable light
  glEnable(GL_LIGHT0+0);

  // enable fog
  glEnable(GL_FOG);

  // enable lighting
  glEnable( GL_LIGHTING );
  glShadeModel (GL_SMOOTH);
  //glShadeModel (GL_FLAT);

  // cause material parameters to track current color (Color3f)
  glColorMaterial ( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE );
  glEnable ( GL_COLOR_MATERIAL );
}


// default GL initialization, once at beginning of time
// typically overridden by App3DView
void
AppView::UserGLInit( void ) {

  // set background color
  glClearColor( 0, 0, 0, 0 );

  // set up perspective
  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  gluPerspective(30.0, 1, 0.1, 100.0);
  glTranslatef(0.0, 0.0, -4.0);  

  // set up viewing
  glMatrixMode( GL_MODELVIEW );
  glLoadIdentity();

  // set up lighting
  DefineLighting();
}


// return 1 iff a redraw is requested
int
AppView::UserAction( const Point3& eye, const Point3& far,
		     PMOUSEINFO pMouseInfo,
		     PHANDLE currObj )
{

#if 0
  // verbose comment; change #if to 1 if you wish to see it
  static int na;
  cout << "Action " << na++
     << "; Mouse[" << pMouseInfo->mouseButton << "] "
	 << (pMouseInfo->event == MotionNotify ?  "Motion " : 
		 pMouseInfo->event == ButtonPress  ?  "Press  " :
		 pMouseInfo->event == ButtonRelease ? "Release" : "Unknown")
     << " d=" << pMouseInfo->doubleClick
	 << " currObj " << currObj 
	 << " e [" << e[0] << ", " << e[1] << ", " << e[2] << "] "
	 << " f [" << f[0] << ", " << f[1] << ", " << f[2] << "] "
	 << endl;
#endif

#if 1
  // Event:
  //     *  Mouse motion has occurred
  //     *  Object currently selected
  //     *  Left Mouse button is down
  // Action:
  //     *  Pass update action to user object
  if( ( pMouseInfo->doubleClick ) &&
      ( 0 == pMouseInfo->mouseButton ) ) {

	// capture selection ray for display
        _app->_capteye = eye;
        _app->_captfar = far;
	return 1;
  } // capture eye/far 
#endif

  // Event:
  //     *  Mouse motion has occurred
  //     *  Object currently selected
  //     *  Left Mouse button is down
  // Action:
  //     *  Pass update action to user object
  if( ( MotionNotify == pMouseInfo->event )  &&
	  ( 1 == pMouseInfo->buttonMotion ) &&
	  ( currObj != NULL ) &&
      ( 0 == pMouseInfo->mouseButton ) ) {

	// pass eye/far action to object, which returns 1 iff modified
	GeomObject *obj = ( GeomObject * ) currObj;
	int retval = obj->update( eye, far );

	// capture selection ray for display
        _app->_capteye = eye;
        _app->_captfar = far;
    
	return retval;
  } // motion


  // Event:
  //     *  ButtonPress ( button down )
  //     *  Left Mouse button
  // Action:
  //     *  Set _currObject to selected object, if any
  if( ( ButtonPress == pMouseInfo->event )  &&
      ( 0 == pMouseInfo->mouseButton ) ){

    // select first object whose intersectWithRay() method returns 1
    for( int i=0; i < _app->_numObjects; ++i ) {
      Point3 intersectionPt;
      if( _app->_objects[i]->intersectWithRay( eye, far, intersectionPt ) ) {
	_app->_currObject = ( PHANDLE ) _app->_objects[i]; 
	
	// can detect double click if you wish
	// if (pMouseInfo->doubleClick)
	return 1; // force redraw
      }
    } // for
    
    // user attempted selection, but nothing was selected
    if ( _app->_currObject != NULL ) {
      _app->_currObject = ( PHANDLE ) NULL;
      return 1;
    }
  } 
  
  
  // Event:
  //     *  Right Mouse button
  //     *  ButtonRelease ( button up )
  //     *  No button motion since ButtonDown
  // Action:
  //     *  Delete selected object, if any
  if( ( 2 == pMouseInfo->mouseButton ) &&
	  ( ButtonRelease == pMouseInfo->event ) &&
      ( 0 == pMouseInfo->buttonMotion ) ) {
    
    // delete first object whose intersectWithRay() method returns 1
    for( int i=0; i < _app->_numObjects; ++i ) {
      Point3 intersectionPt;
      if( _app->_objects[i]->intersectWithRay( eye, far, intersectionPt ) ) {
		cout << "Removing object " << i << endl;

		_app->deleteObject( _app->_objects[i]  );
		return 1; // force redraw
      }
    }
  }
  
    
  // Event:
  //     *  Middle Mouse button
  //     *  ButtonRelease ( button up )
  //     *  No button motion since ButtonDown
  // Action:
  //     *  select ????
  if( ( ButtonRelease == pMouseInfo->event ) &&
      ( 0 == pMouseInfo->buttonMotion ) &&
      ( 1 == pMouseInfo->mouseButton ) ) {
  }    

  // nothing modified; no redraw necessary
  return 0;
}


PHANDLE
AppView::UserHandle( void )
{
  return _app->_currObject;
}


//
// Refresh() is called whenever the application 
// is idle; it calls the render() action of both
// GLcanvases, if either has requested redrawing
//
int
AppView::UserRefresh( void )
{
  logStuff();
  // evolve any time-dependent state, and redraw as necessary
  if ( _app->app3DView->_dirty ) {
	  _app->_appForm->render(0);
	  assert ( _app->app3DView->_dirty == 0 ); // bit cleared by redraw action
	      }

  // return value apparently ignored by xforms
  return 0;
}

void
AppView::logStuff() {
  cout << "doing log stuff" << endl;
  fl_set_object_label(_app->_appForm->getFormStruct()->wassappenin_text,usr_msg);

  rclog.DrawUpToEntry(rclog.First());
  LogEntry *current;
  current = rclog.First();
  while (current) {
    rclog.DrawUpToEntry(current);
    current = current->_next;
  }
}

// this is a hack to allow the xforms C callback to 
// invoke AppView's C++ method; there is currently no
// way to give a non-static C++ method as a callback
// (CC complains, "error(3381): a pointer to a bound 
// function may only be used to call the function")
// so -- we deduce the method pointer from the object!

static int
idle_callback( XEvent *, void *data )
{
  AppView *av = (AppView *)data;
  int retval = av->UserRefresh();
  // return value apparently ignored by xforms
  return retval;
}



////////////////////////////////////////////////////////////
//
// 3D Perspective Viewer
//
////////////////////////////////////////////////////////////



//
// Constructor
//
App3DView::App3DView( App *app, AppView *appView ) :
  AppView( app, appView )
{ 
}

//
// Constructor
//
App3DView::App3DView( App *app ) :
  AppView( app )
{ 
}


void
App3DView::UserGLInit( void ) { 

  // background color
  glClearColor( 0, 0, 0, 0 );

  // set up perspective, depth buffering
  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  gluPerspective( 30.0, 1, 0.1, 100.0 );
  glTranslatef( 0.0, 0.0, -4.0 );
  glEnable( GL_DEPTH_TEST ); 

  // set up viewing
  glMatrixMode( GL_MODELVIEW );
  glLoadIdentity();

  // set up lights
  DefineLighting();

}




////////////////////////////////////////////////////////////
//
// Application Code
//
////////////////////////////////////////////////////////////

#define MAX_OBJECTS 100
//
// Application Constructor
//
App::App (void ):
  _numObjects( 0 )
{
  _capteye[0] = _capteye[1] = _capteye[2] = 0.f;
  _captfar[0] = _captfar[1] = _captfar[2] = 0.f;
}

// void App::initGLforms( void )
//
// Creates OpenGL contexts for XForms
// Initializes User data structures
// Invokes main interaction loop
// 
void
App::initGLforms( void )
{
  // create GLforms objects
  FirstXformsInit();

  DisplayGLCanvas* displayViews[1] = { 0 };
  //		  displayViews[0] = app2DView = new App2DView( this );
  displayViews[0] = app3DView = new App3DView( this );
  
  // create and initialize xforms canvas with two "appviews" from above
  _appForm = new XForms < FD_main >( create_form_main, 1, displayViews );   
  
  // initialize eric amram's glforms library
  extern char *stamp; // string from stamp.C
  _appForm->initFormsInteraction( stamp );

  //
  // BEGIN USER INITIALIZATION CODE
  //

  // allocate space for array of objects
  _objects = new GeomObject* [ MAX_OBJECTS ];

  //
  // END USER INITIALIZATION CODE
  //

  // reset viewing (requires axial bbox of data)
  _appForm->posReset( 0 ); // ugly hack; need real id of glCan
  app3DView->resetView();
 
  // now we are ready to draw, and interact
  // set up the idle callback to redraw when appropriate
  fl_set_idle_callback( (FL_APPEVENT_CB)idle_callback, (void *)app3DView );

  // start the main loop
  _appForm->mainLoopInteraction();
  
  // returns when form exits
  return;
}


//
// Adds an object at a particular location
//
void 
App::createObject( const Point3 &pos )
{
  
  cout<<"Creating object "<<_numObjects + 1 << 
    " at" << pos << " ...." << endl;
  
  Sphere *s = new Sphere( 0.01 );
  
  // constrain to arena
  s->setPosition( pos );
  _objects[ _numObjects++ ] = ( GeomObject * )s;

  cout<<"Done.  \n";

  // highlight object
  _currObject = ( PHANDLE )s;
}



//
// Remove an object at a particular location
//
void 
App::deleteObject( GeomObject *robj )
{

  // find object's index in array
  int index=-1;
  for( int i=0; i<_numObjects; ++i ) {
    if ( robj == _objects[ i ] ) { 
      index = i;
      break;
    }
  }
  
  cout << "Deleting object " <<index<< " ...", index;
  assert( index!=-1 );

  // deselect object, if it is selected
  if ( _currObject == ( PHANDLE ) robj )
	_currObject = ( PHANDLE ) NULL;

  // delete object
  delete _objects[index];

  // slide other objects down to fill gap
  for( i=index; i<_numObjects - 1; ++i )
    _objects[i] = _objects[i+1];

  // one less object
  --_numObjects;

  // success
  printf("Done\n");

}
