#ifndef ZPR_H
#define ZPR_H

#include <cassert>
#include <cmath>
using namespace std;

#include <GL/gl.h>

typedef unsigned int uint;
typedef int const intc;


/*!  
\brief 3D Mouse support for OpenGL.
 
\par
  Mouse Functionality.  The left mouse button rotates about 
  the global origin.  The right mouse button pans.  The 
  third mouse button (hold down left and right for 2 button 
  mouse) zooms. 
 
\par
  Acknowledgements: Thanks to Nigel Stewart at nigels.com 
  for zpr.h and 
  zpr.cpp.  I requested permission to use the code as 
  mouse support is great. For the original version see 
  http://www.nigels.com/glt/gltzpr/ . 

  I have modified the code from C to C++.  Additional 
  functionality was added along with a minor optimization.

  zpr is currently a singleton class.  An instance initializes
  OpenGL with a reasonable default projection and model view.

\par Example
\verbatim
  int wx=800; 
  int wy=800;
  glutInitWindowSize(wx,wy);
  glutCreateWindow("");

  zpr zz;
\endverbatim

  You can make direct OpenGL calls yourself and then have
  this class read in the new state with zpr::update(). ie

  zpr works correctly when its state mirrors that of OpenGL.
  For example a call to glFrustrum or gluLookAt will change the viewing 
  frustrum and zpr's values will not be the same as OpenGL's,
  hence zpr will break. Call zpr::update() to update
  zpr after a change.

\par Example
\verbatim
  zpr zz;
  ...
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt
  (
    2.0,0.0,3.0, 
    2.0,0.0,0.0, 
    0.0,1.0,0.0
  );

  zz.update();
\endverbatim

*/
class zpr
{
  /** The motion handler. */
  void motionzpr(intc x, intc y);
  /** The mouse button handler. */
  void mousezpr(intc button, intc state, intc x, intc y);
  /** The reshape handler. */
  void reshapezpr(intc w, intc h);
public:

  /** zpr is a singleton with a global pointer. */
  static zpr * global;

  /** The minimum x screen coordinate. */
  GLdouble left;
  /** The maximum x screen coordinate. */
  GLdouble right;
  /** The minimum y screen coordinate. */
  GLdouble bottom;
  /** The maximum y screen coordinate. */
  GLdouble top;
  /** The closest z clipping plane. */
  GLdouble zNear;
  /** The farthest z clipping plane. */
  GLdouble zFar;

  /** Screen pixel width. */
  int width;
  /** Screen pixel height. */
  int height;

  /** Look at the origin from (0.0,0.0,2.0) with up as 
      (0.0,1.0,0.0) and the field of view 30 degrees. */
  zpr();

  /** Read the model view matrix from OpenGL. */
  void readModelView();
  /** Read the projection matrix from OpenGL and calculate 
      left, right, bottom, top, zNear and zFar. */
  void readProjection();
  /** Read the current screen size from OpenGL. */
  void readScreenDimensions();
  /** Read in the current OpenGL state. */
  void update()
  {
    readScreenDimensions();
    readModelView();
    readProjection();
  }

  /** Write the current projection from zpr's parameters. */
  void write();
  /** Write a default projection from zpr. */
  void writeDefault();
  /** Draw having the x and y axes with equal scales, zpr::top 
      needs to be re-calculated. This function is useful in 
      plotting maths graphs. */
  void writefromXaxis();

  /** The mouse X position in pixeles. */
  int  mouseX;
  /** The mouse Y position in pixeles. */
  int  mouseY;
  /** Was the left mouse button pressed? */
  bool mouseLeft;
  /** Was the middle mouse button pressed? */
  bool mouseMiddle;
  /** Was the right mouse button pressed? */
  bool mouseRight;

  /** The mouse X position in world coordinates. */
  GLdouble mouseXworld;
  /** The mouse Y position in world coordinates. */
  GLdouble mouseYworld;
  /** The mouse Z position in world coordinates. */
  GLdouble mouseZworld;

  /** The model view matrix. */
  GLdouble matrix[16];
  /** The inverse of the model view matrix. */
  GLdouble matrixI[16];


  /** If not zero callback after mousezpr(..) when a mouse click occures. 
      mousecallback pointer is not memory managed. */
  //fnobj0<void>* mousecallback;

  /** Glut motion call back function. */
  static void motion(int x, int y);
  /** Glut mouse call back function. */
  static void mouse(int button, int state, int x, int y);
  /** Glut reshape call back function. */
  static void reshape(int width_, int height_);

  /** Get the world mouse co-ordinates. */
  void readMouse
  (
    GLdouble *px,
    GLdouble *py,
    GLdouble *pz,
    intc x,
    intc y
  ) const;

  /** Distance function. */
  static double vlen(GLdouble x,GLdouble y,GLdouble z) 
    { return sqrt(x*x+y*y+z*z); }

  /** Print the frustrum values. */
  void printInfo() const;
};

/*!
\brief Support for basic operations on OpenGL matrix.

This is a helper class for zrp.

OpenGL stores a matrix as a transpose so its rows are
  stored as columns.  The access function adjusts for this.
  If you want to print meaningfully use printTranspose(). 
*/
class zprGLmatrix
{
public:

  /** Client must ensure that this is not null. */
  GLdouble * matrix;

  /** The class needs a pointer to an existing OpenGL matrix. */
  zprGLmatrix(GLdouble* matrix_)
    : matrix(matrix_) {}

  /** The OpenGL matrix is stored as a transpose. */
  GLdouble access( unsigned int const r, unsigned int const k) const
    { return matrix[4*k+r]; }

  /** Compute the inverse of a 4x4 matrix. */
  static void invertMatrix(GLdouble *out, GLdouble const *m );

  /** The smallest positive value used in singularity test. */
  static GLdouble zero;

  /** Print the matrix as stored to cout. */
  void print() const;
  /** Print the transpose matrix to cout. */
  void printTranspose() const;

};

void glerrordisplay();

/*!
\brief  A default OpenGL session.
*/  
class OpenGLinitialisation
{
public:

  static GLfloat light_ambient[];
  static GLfloat light_diffuse[];
  static GLfloat light_specular[];
  static GLfloat light_position[];

  static GLfloat mat_ambient[];
  static GLfloat mat_diffuse[];
  static GLfloat mat_specular[];
  static GLfloat mat_shininess[];

  /** Turns on lighting and depth test. */
  OpenGLinitialisation();

  /** Read the light parameters for LIGHT0 into OpenGL. */
  void light0Init();
  /** Read in the material parameters into OpenGL. */
  void materialInit();

};




#endif


