#ifndef GRAPHMISC_H
#define GRAPHMISC_H

#include <cassert>
#include <string>
#include <sstream>
#include <iostream>
#include <vector>
using namespace std;

#include <GL/glut.h>

#include <commandline.h>
#include <mathlib.h>
#include <print.h>
#include <gobj.h>


/*!
\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();

};

/*!
\brief Rotate 
*/
class myRotate : public gobj
{
public:

  point3<double> axis;

  myRotate(point3<double> const & axis_)
    : axis(axis_) {}

  static point3<double> const zaxis;

  void draw()
  {
    GOBJDEBUGCODE
    if (axis!=zaxis)
    {
      assert(axis!=point3<double>(0,0,0));

      point3<double> N;
      crossproduct::evalxyz(N,zaxis,axis);

      double angle = acos( axis.dot(zaxis) / axis.distance() );
      angle *= 180.0/PI;
      glRotated(angle,N.x,N.y,N.z);
    }
  }

};
    


/*!
\brief Push and pop the current OpenGL matrix mode.

Use with named variables so when the object goes
  out of scope the destructor is called.
*/
class myglPushMatrixMode
{
public:

  /** The mode when the object was constructed. */
  GLint mode;

  /** Save the current matrix mode. */
  myglPushMatrixMode()
    { glGetIntegerv(GL_MATRIX_MODE, &mode); }

  /** Restore the previous matrix mode. */
  ~myglPushMatrixMode()
    { glMatrixMode(mode); }
};

/*!
\brief Push and pop the current OpenGL matrix.

Use with named variables so when the object goes
  out of scope the destructor is called.
*/
class myglPushMatrix
{
public:

  /** Save the current matrix. */
  myglPushMatrix()
    { glPushMatrix(); }

  /** Restore the previous matrix. */
  ~myglPushMatrix()
    { glPopMatrix(); }
};

/*! 
\brief Save and restore the attribute. 

Use with named variables so when the object goes
  out of scope the destructor is called.
*/
class myglPushAttrib
{
  myglPushAttrib() { assert(false); }
public:

  /** Push the attribute onto the attribute stack. */
  myglPushAttrib( GLbitfield mask )
    { glPushAttrib(mask); } 

  /** Pop the attribute from the attribute stack. */
  ~myglPushAttrib()
    { glPopAttrib(); }
};

/*!
\brief Enable and disable an attriub

Use with named variables so when the object goes
  out of scope the destructor is called.
*/
class myglCapability
{
  myglCapability() { assert(false); }
public:

  GLenum capability;

  /** Enable the given capability. */
  myglCapability(GLenum capability_)
    : capability(capability_) 
    { glEnable(capability); }

  /** Turn off the capability. */
  ~myglCapability()
    { glDisable(capability); }
};

class myglMode
{
  myglMode() { assert(false); }
public:

  myglMode(GLenum mode)
   { glBegin(mode); }

  ~myglMode()
    { glEnd(); }
};
  

  

/*!
\brief Turn the lighting off.

Use with named variables so when the object goes
  out of scope the destructor is called.
*/
class myLightingTurnOff
{
public:

  /** Push the current lighting state and disable lighting. */
  myLightingTurnOff()
  {     
    glPushAttrib(GL_CURRENT_BIT);
    glPushAttrib(GL_LIGHTING_BIT);
    glDisable(GL_LIGHTING);
  }

  /** Restore the previous lighting state. */
  ~myLightingTurnOff()
    { glPopAttrib(); glPopAttrib(); }
};


// Write a vector of triangles. 
//void writeTriangles(vector< point3<double> > const & v);

/** Write OpenGl errors to cout. */
void glerrordisplay();

/** Draw axes red(x), green(y), blue(z). */
void axes(doublec length);

/*!
\brief Draw axes at the origin.
*/
class myaxes : public gobj
{
public:

  /** The color in the x-axis. */
  point3<float> xaxiscolor;
  /** The color in the y-axis. */
  point3<float> yaxiscolor;
  /** The color in the z-axis. */
  point3<float> zaxiscolor;

  /** The length of each axis. */
  double length;

  /** Straight line axes. */
  myaxes(doublec length_);

  void draw();

};

/** Draw a wire box with the following dimensions.[scales] */
void wirerectangle(floatc x, floatc y, floatc z);

/*!
\brief View manipulator.
*/
class camera
{
public:

  /** Moves OpenGL camera.  Default is 
      [-2,-2,2,2] window looking at origin. 
   $ ./main camerax=2.0 ... */
  static void lookatxz
  (
    commandline & cmd,
    doublec camerax_=0.0, 
    doublec cameraz_=2.0 
  ); 

  static void lookat
  (
    commandline & cmd,
    doublec camerax_=0.0, 
    doublec cameray_=0.0, 
    doublec cameraz_=2.0 
  ); 
};





/*!
\brief  Display a Glut applications framerate. 

\par Usage  
Call framerate::display() from within the display function.
  Call framerate::update() from the idle function.
*/
template< int period = 500 >
class framerate
{
  int frame;
  int time;
  int timebase;
  float fps;
  string sr;
public:

  /** Position x: [0,1.0] */
  float xpos;
  /** Position y: [0,1.0] */
  float ypos;

  /** xpos and ypos position the framebuffer text.
   Without scaling a 2.0 by 2.0 square with origin at center. */
  framerate(float xpos_=0.1, float ypos_=0.05) :
    frame(0), time(0), timebase(0), fps(0.0), xpos(xpos_), ypos(ypos_) {}

  /** Call in idle function. */
  void update()
  {
    // glutGet(GLUT_ELAPSED_TIME) returns the time in millisecond units. 
    //   1unit=1ms
    time = glutGet(GLUT_ELAPSED_TIME);

    // Has a period passed? 
    if (time - timebase > period ) 
    {
      fps = frame*((GLfloat)period)/(time-timebase);
      timebase = time;		
      frame = 0;

      stringstream ss;
      ss << fps;
      sr = ss.str();
      sr += "fps";
    }
  }

  /** Call inside general display routine. 
    Keeps count off and displays the frame rate. */
  void display()
  {
    //assuming GL_MODELVIEW
    glPushMatrix(); //save
    glLoadIdentity(); //clear

    myLightingTurnOff temp2;

    glMatrixMode(GL_PROJECTION);
    glPushMatrix(); //save
    glLoadIdentity(); //clear
    //gluOrtho2D(-1.0, 1.0, -1.0, 1.0);
    gluOrtho2D(0.0, 1.0, 0.0, 1.0);
    glMatrixMode(GL_MODELVIEW);

    ++frame;

    glColor3f(0.0,1.0,0.0);

//cout << SHOW(xpos) << " " << SHOW(ypos) << endl;

    glRasterPos2f(xpos,ypos);
    //glRasterPos2f(0.8,0.2);

//cout << SHOW(sr) << endl;
    int len = sr.length();
    for (int i = 0; i < len; ++i)
      glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, sr[i]);

    glMatrixMode(GL_PROJECTION);
    glPopMatrix(); //restore
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix(); //restore
  }
};



class gltextmsg
{
  gltextmsg() { assert(false); }
public:
  
  string msg;
  float pos[2];
  float col[3];

  gltextmsg
  ( 
    string const & msg_,
    floatc x, 
    floatc y,
    floatc c0,
    floatc c1,
    floatc c2
  )
    : msg(msg_)
  {
    pos[0] = x;
    pos[1] = y;
    col[0] = c0;
    col[1] = c1;
    col[2] = c2;
  }

  template < typename T >
  void updatevalue
  ( 
    T const & v,
    string const & post = string("")
  )
  {  
    stringstream ss;
    ss << v;
    msg = ss.str();
    msg += post;
  }

  template < typename T >
  void updatevalue
  ( 
    string const & pre,
    T const & v,
    string const & post = string("")
  )
  {  
    stringstream ss;
    ss << v;
    msg = pre;
    msg += ss.str();
    msg += post;
  }
  

  void display() const
  {
    myglPushMatrix temp;

    myLightingTurnOff temp2;

    glLoadIdentity();
    gluOrtho2D(-1.0, 1.0, -1.0, 1.0);

    glColor3f(col[0],col[1],col[2]);

    glRasterPos2f(pos[0],pos[1]);

    int len = msg.length();
    for (int i = 0; i < len; ++i)
      glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, msg[i]);
  } 
};

/*!
\brief Save the coordinate system, translate, draw, restore. 
*/
class gobjMyTranslateDraw : public gobj
{
  gobjMyTranslateDraw() { assert(false); }
public:

  gobj* target;
  bool clean;

  point3<double> shift;

  /** Own the target if clean is set to true.*/
  gobjMyTranslateDraw
  (
    gobj * target_, 
    boolc clean_,
    point3<double> const & shift_
  )
    : target(target_), clean(clean_), shift(shift_) {}

  ~gobjMyTranslateDraw()
    { if (clean) delete target; }


  void draw()
  {
    GOBJDEBUGCODE
    assert(target!=0);

    glPushMatrix();
    glTranslatef(shift.x,shift.y,shift.z);
    target->draw();
    glPopMatrix();
  }
};

class gobjQuadric : public gobj
{
public:

  GLUquadric * quadric;

  // Possible states
  float radius;
  int slices;
  int loops;

  gobjQuadric()
    : quadric(gluNewQuadric()), radius(0.2), slices(10), loops(3) {}

  void detailincrease()
    { slices *= 2; loops *=2; }

  ~gobjQuadric()
    { gluDeleteQuadric(quadric); }

  void draw()
    { GOBJDEBUGCODE }
};

class gobjMyDiscDraw: public gobj
{
public:

  gobjQuadric * d;

  float x;
  float y;

  gobjMyDiscDraw()
    : d(0), x(0.0), y(0.0) {}

  gobjMyDiscDraw
  (
    floatc x_,
    floatc y_,
    gobjQuadric * d_
  )
    : d(d_), x(x_), y(y_) {}

  gobjMyDiscDraw
  (
    point2<double> const & x,
    gobjQuadric * d_
  )
    : d(d_), x(x.x), y(x.y) {}

  gobjMyDiscDraw
  (
    point2<float> const & x,
    gobjQuadric * d_
  )
    : d(d_), x(x.x), y(x.y) {}

  void draw()
  {
    GOBJDEBUGCODE
    assert(d!=0);

    glPushMatrix();
    glTranslatef(x,y,0.0);
    gluDisk(d->quadric, 0.0, d->radius, d->slices, d->loops);
    glPopMatrix();
  }
};
 
/*!
\brief Draw a translated sphere.

The client supplies the quadric which allows for reuse - multiple spheres
 can use the same quadric.

\par Example
\verbatim
  gobjpush( new gobjglColor3ub(0,255,0) );

  gobjQuadric * q2 = new gobjQuadric();
  q2->radius = 0.02;
  q2->slices=20;
  q2->loops=6;
  gobjpush(q2);
  for (uint i=0; i<greenpoints.size(); ++i)
    gobjpush( new gobjMySphereDraw(greenpoints[i],q2) );
\endverbatim
*/
class gobjMySphereDraw : public gobj
{
public:
  
  gobjQuadric * quadric;

  float x;
  float y;
  float z;

  gobjMySphereDraw()
    : quadric(0), x(0.0), y(0.0), z(0.0) {}

  gobjMySphereDraw
  (
    floatc x_,
    floatc y_,
    floatc z_,
    gobjQuadric * quadric_
  )
    : quadric(quadric_), x(x_), y(y_), z(z_) {}

  gobjMySphereDraw
  (
    doublec x_,
    doublec y_,
    doublec z_,
    gobjQuadric * quadric_
  )
    : quadric(quadric_), x(x_), y(y_), z(z_) {}

  gobjMySphereDraw
  (
    point3<double> const & p,
    gobjQuadric * quadric_
  )
    : quadric(quadric_), x(p.x), y(p.y), z(p.z) {}

  gobjMySphereDraw
  (
    point3<float> const & p,
    gobjQuadric * quadric_
  )
    : quadric(quadric_), x(p.x), y(p.y), z(p.z) {}

  gobjMySphereDraw
  (
    point2<double> const & x,
    gobjQuadric * quadric_
  )
    : quadric(quadric_), x(x.x), y(x.y), z(0.0) {}

  // Unhappy with reference pass.
  gobjMySphereDraw
  (
    point2<double> const & x,
    gobjQuadric& quadricref
  )
    : quadric(&quadricref), x(x.x), y(x.y), z(0.0) {}

  gobjMySphereDraw
  (
    point2<float> const & x,
    gobjQuadric * quadric_
  )
    : quadric(quadric_), x(x.x), y(x.y), z(0.0) {}

  void draw()
  {
    GOBJDEBUGCODE
    assert(quadric!=0);

    glPushMatrix();
    glTranslatef(x,y,z);
    gluSphere(quadric->quadric,quadric->radius,quadric->slices,quadric->loops);
    glPopMatrix();
  }

};

/*!
\brief Sampled circle or ellipse.

This can be used in drawing circles of different sizes
 by gobjMyCircleDraw .
*/
class gobjMyCircle : public gobj
{
public:

  /** The number of sampled points. */
  uintc N;

  /** The samples x values. */
  float * const ptx;
  /** The samples y values. */
  float * const pty;

  /** General arcs can be created by changing the angle
     interval [theta0,theta1].  Ellipses can be made
     by changing the axis lengths.
   */ 
  gobjMyCircle
  (
    doublec theta0, 
    doublec theta1, 
    doublec xaxislength,
    doublec yaxislength,
    uintc N_=360
  );

  /** Create an arc. */
  gobjMyCircle
  (
    doublec theta0, 
    doublec theta1, 
    doublec radius=1.0,
    uintc N_=360
  );

  /** Create a circle. */
  gobjMyCircle( uintc N_=360 );

  /** Dummy so that it can be placed on a gobj stack. */
  void draw() { GOBJDEBUGCODE }

  /** Cleanup. */
  ~gobjMyCircle();

};

/*!
\brief Draw a circle or ellipse in xy plane.
*/
class gobjMyCircleDraw : public gobj
{
  static point3<double> const zaxis;
public:

  /** Multiplier of the x component of the circle cir. */
  double scalex;
  /** Multiplier of the y component of the circle cir. */
  double scaley;

  /** Translate to center before drawing. */
  point3<double> center;

  /** The circles normal, by default is the z-axis. */
  point3<double> axis;

  /** Many drawings can share a common circle as a reference. */
  gobjMyCircle const & cir;

  void radiusset(doublec radius)
    { scalex=radius; scaley=radius; }
  
  /** Pass in a center point and a sampled circle. */
  template<typename T>
  gobjMyCircleDraw
  (
    point3<T> const & center_,
    gobjMyCircle const & cir_
  )
    : scalex(1.0), scaley(1.0), center(center_.x,center_.y,center_.z), 
      axis(zaxis), cir(cir_) {}

  /** Pass in a center point and a sampled circle. */
  template<typename T>
  gobjMyCircleDraw
  (
    point2<T> const & center_,
    gobjMyCircle const & cir_
  )
    : scalex(1.0), scaley(1.0), center(center_.x,center_.y,0.0), 
      axis(zaxis), cir(cir_) {}


  /** Pass in a center point and a sampled circle. */
  template<typename T>
  gobjMyCircleDraw
  (
    doublec radius,
    point3<T> const & center_,
    gobjMyCircle const & cir_
  )
    : scalex(radius), scaley(radius), center(center_.x,center_.y,center_.z), 
      axis(zaxis), cir(cir_) {}

  /** Pass in a center point, an axis which the circle is draw
      about and a sampled circle. */
  template<typename T>
  gobjMyCircleDraw
  (
    doublec radius,
    point3<T> const & center_,
    point3<T> const & axis_,
    gobjMyCircle const & cir_
  )
    : scalex(radius), scaley(radius), center(center_), 
      axis(axis_), cir(cir_) {}

  /** Draw a 2D circle in 3D space. */
  void draw();

};





/*!
\brief Display a character string in 3D space.  */
class gobjMyBitmapCharacter : public gobj
{
public:

  /** The string being displayed. */
  string name;

  /** The position in 3D space. */
  point3<float> x;

  /** A pointer to an OpenGL font that the string is written in. */
  void * font;

  /** Construct a character string in 3D space. 

  Possible values of the font are
     - GLUT_BITMAP_HELVETICA_10
     - GLUT_BITMAP_HELVETICA_12
     - GLUT_BITMAP_HELVETICA_18
     - GLUT_BITMAP_TIMES_ROMAN_10
     - GLUT_BITMAP_TIMES_ROMAN_24
     - GLUT_BITMAP_9_BY_15
     - GLUT_BITMAP_8_BY_13
  */
  gobjMyBitmapCharacter
  (
    string const & name_,
    point3<float> const & x_,  
    void* font_=GLUT_BITMAP_HELVETICA_10
  )
    : name(name_), x(x_), font(font_) {};

  /** Construct a character string in 3D space. */
  gobjMyBitmapCharacter
  (
    string const & name_,
    point3<double> const & x_,  
    void* font_=GLUT_BITMAP_HELVETICA_10
  )
    : name(name_), x( point3<float>(x_.x,x_.y,x_.z) ), font(font_) {};

  void draw();

};

/*!
\brief Draw a primitive flat arrow in 3D space.
*/
class gobjMyArrow : public gobj
{
public:

  /** The start of the arrow. */
  point3<double> p0;
  /** The end of the arrow. */
  point3<double> p1;
  /** The length of the head. */
  doublec headlength;
  /** The width or height of the arrow head. */
  doublec headwidth;
  double delta;

  gobjMyArrow
  (
    point3<double> const & p0_,
    point3<double> const & p1_,
    doublec headlength_,
    doublec headwidth_,
    doublec delta_
  );

/*
  gobjMyArrow
  (
    point2<double> const & _p0,
    point2<double> const & _p1,
    doublec _delta
  );
*/

  void draw();

};

class gobjMyDiskDraw : public gobj
{
public:
  
  gobjQuadric * d;

  float x;
  float y;
  float z;

  gobjMyDiskDraw()
    : d(0), x(0.0), y(0.0), z(0.0) {}

  gobjMyDiskDraw
  (
    floatc x_,
    floatc y_,
    floatc z_,
    gobjQuadric * d_
  )
    : d(d_), x(x_), y(y_), z(z_) {}

  gobjMyDiskDraw
  (
    point3<double> x,
    gobjQuadric * d_
  )
    : d(d_), x(x.x), y(x.y), z(x.z) {}

  gobjMyDiskDraw
  (
    point3<float> x,
    gobjQuadric * d_
  )
    : d(d_), x(x.x), y(x.y), z(x.z) {}

  void draw()
  {
    GOBJDEBUGCODE
    assert(d!=0);

    glPushMatrix();
    glTranslatef(x,y,z);
    gluDisk(d->quadric,0.0,d->radius,d->slices,d->loops);
    glPopMatrix();
  }

};


/*!
\brief Mapping a color from blue to red along a unit length.  

See "Computer Visualization" by R.Gallagher, page 91.
*/
class colorfunction
{
public:

  /** Map a value to a color. */
  template< class T >
  point3<T>& operator () (point3<T>& p, T const v) const;
  /** Map a value to a color. */
  template< class T >
  void operator () (T& x, T& y, T& z, T const v) const;

};


/*!
\brief Draw a square grid.

Essentially this is a ruler.

\par Example
\verbatim
  gridsquare * gs = new gridsquare(1,0xaaaa,5,5,1.0,1.0);
  gobjpush(gs);
\endverbatim
*/
class gridsquare : public gobjContainer
{
public:

  /** The lines are drawn using glLineStripple. */
  gridsquare
  (
    GLint factor,
    GLushort pattern,
    uintc xcolumns,
    uintc ycolumns,
    doublec xmax,
    doublec ymax
  );


};

/*!
\brief Miscellaneous graphics functions.
*/
class graphmisc
{
public:

  /** Normalize RGB color. */
  static void colornormalize(point3<double> & p1, point3<uint> const & p2);

  /** Read a point3<uint> from the command line and normalize. */
  static void colornormalize(point3<double> & p1, commandline & cmd, stringc tag );

};


//----------------------------------------------------------
// Implementation

template< class T >
point3<T>& colorfunction::operator () (point3<T>& p, T const v) const
{
  assert(v>=0.0);
  assert(v<=1.0);

  p.x =  p.z = 0.0;
  p.y = 1.0;

  if (v>=0.75)
    p.x = 1.0;  
  else
    if (v>0.5)
      p.x = (v-0.5)/0.25;

  if (v<=0.25)
    p.z = 1.0;
  else
    if (v<0.5)
      p.z = 1.0 - (v-0.25)/0.25;

  if (v>0.75)
    p.y = 1.0 - (v-0.75)/0.25;
  else
    if (v<0.25)
      p.y = v/0.25;

  return p;
}

template< class T >
void colorfunction::operator () (T& x, T& y, T& z, T const v) const
{
  assert(v>=0.0);
  assert(v<=1.0);

  x = z = 0.0;
  y = 1.0;

  if (v>=0.75)
    x = 1.0;  
  else
    if (v>0.5)
      x = (v-0.5)/0.25;

  if (v<=0.25)
    z = 1.0;
  else
    if (v<0.5)
      z = 1.0 - (v-0.25)/0.25;

  if (v>0.75)
    y = 1.0 - (v-0.75)/0.25;
  else
    if (v<0.25)
      y = v/0.25;
}



#endif




