////////////////////////////////////////////////////////////////
//
// graphics.cc
// Copyright (c) 2002 by Wesley H. Huang.  All rights reserved.
// 
////////////////////////////////////////////////////////////////
#include "graphics.h"
#include <list>
#include <map>
#include <string>
#include <iostream>
#include <math.h>

// this is here so that if someone ever tried to get this to run under
// Windows, it would compile OK.  (Currently pertains only to error
// callback function...)
#ifndef CALLBACK
#define CALLBACK
#endif

namespace Graphics {

  bool initialized = false;
  bool verbose = false;

  // window parameters
  double _minX, _maxX, _minY, _maxY;
  double _winMinX, _winMaxX, _winMinY, _winMaxY;
  int _winWidth, _winHeight;
  // user keyboard handler
  void (*userKeyboard)(unsigned char);
  // storage for color definitions
  map<string, float*> colors;

  // list of lists of objects that are to be drawn on the screen
  list< list<drawableObject*> * > objectLists;

  // a quadrics object for drawing ellipses
  GLUquadricObj* Q = false;

  // prototypes for functions that should only be used in this file
  void drawPoints(int numPoints, const drawableObject* O);
  void setColor(const string& name );
  void display();
  void reshape(int w, int h);
  void keyboard(unsigned char key, int x, int y);
  void setTransform();
  //void CALLBACK errorCallback(GLenum errCode);
  void CALLBACK errorCallback();
}

// initialize graphics system
//
// this function prepares for a graphics window which will be
// displayed when the runEventLoop() function is called.
//
// the default value for the "name" argument means to use argv[0] as
// the name of the window.  You can specify a c-style string if you
// want something else.
//
// the default value for "winHeight" means to make the window the same
// aspect ratio as the region of interest specified by the
// {min,max}{X,Y} arguments.  The longer dimension of the windown will
// be "winWidth" pixels.  If the "winHeight" argument is nonzero, then
// "winWidth" and "winHeight" are taken to be the literal dimensions
// of the window.
// 
void Graphics::init(int *argc, char* argv[], // command line arguments
		    double minX, double maxX,  
		    double minY, double maxY,
		    char* name, 
		    int winWidth, int winHeight, 
		    int winPositionX, int winPositionY)
{
  if (initialized) {
    cerr << "Hey, you can only create one Graphics object!!!" << endl;
    exit(1);
  }

  // store the ROI (region of interest) geometry parameters
  _minX = minX;
  _maxX = maxX;
  _minY = minY;
  _maxY = maxY;
  if (winHeight != 0) {
    _winWidth = winWidth;
    _winHeight = winHeight;
  }
  else { 
    float roiWidth = maxX - minX;
    float roiHeight = maxY - minY;
    if (roiWidth >= roiHeight) {
      _winWidth = winWidth;
      _winHeight = (int) (winWidth*roiHeight/roiWidth);
    }
    else {
      _winHeight = winWidth;
      _winWidth = (int) (_winHeight*roiWidth/roiHeight);
    }
  }
  // initialize GLUT and prepare for window creation
  glutInit(argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB); // double buffered, RGB mode
  glutInitWindowSize(_winWidth, _winHeight); 
  glutInitWindowPosition(winPositionX, winPositionY);
  if (name == 0)
    glutCreateWindow(argv[0]); 
  else
    glutCreateWindow(name);

  // register event handlers
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  glutKeyboardFunc(keyboard);

  // set up some other stuff
  glClearColor(0.5,0.5,0.5,0);	// backgroud is 50% gray
  setTransform();
  glEnable(GL_POINT_SMOOTH);    // antialias points

  // no user keyboard handler right now
  userKeyboard = 0;

  initialized = true;
}


void Graphics::addBasicColors()
{
  // initialize some colors
  addColor("black",   0.0, 0.0, 0.0);
  addColor("red",     1.0, 0.0, 0.0);
  addColor("green",   0.0, 1.0, 0.0);
  addColor("blue",    0.0, 0.0, 1.0);
  addColor("yellow",  1.0, 1.0, 0.0);
  addColor("magenta", 1.0, 0.0, 1.0);
  addColor("cyan",    0.0, 1.0, 1.0);
  addColor("white",   1.0, 1.0, 1.0);
  addColor("gray50",  0.5, 0.5, 0.5);
}

void Graphics::reshape(int w, int h)
{
  _winWidth = w;
  _winHeight = h;
  glViewport (0, 0, (GLsizei) w, (GLsizei) h); 
  setTransform();
}

// set the transform so that the region of interest (ROI) specified by
// (_minX, _maxX, _minY, _maxY) is always visible and we scale the X
// and Y axes equally
void Graphics::setTransform()
{
  float winAspect = _winHeight*1.0/_winWidth;
  float roiAspect = (_maxY-_minY)/(_maxX-_minX);

  if (winAspect > roiAspect) {
    // ROI is shorter than the window; center it vertically
    _winMinX=_minX; _winMaxX=_maxX;
    float yHeight = (_maxX-_minX)*winAspect;
    float offset = (yHeight - (_maxY-_minY))/2.0;
    _winMinY=_minY-offset; _winMaxY=_maxY+offset;
  }
  else if (roiAspect > winAspect) {
    // ROI is narrower than the window; center it horizontally
    _winMinY=_minY; _winMaxY=_maxY;
    float xWidth = (_maxY-_minY)/winAspect;
    float offset = (xWidth - (_maxX-_minX))/2.0;
    _winMinX=_minX-offset; _winMaxX=_maxX+offset;
  }
  else {
    _winMinX=_minX; _winMaxX=_maxX;
    _winMinY=_minY; _winMaxY=_maxY;
  }

  glMatrixMode (GL_PROJECTION);               
  glLoadIdentity();
  gluOrtho2D((GLdouble) _winMinX, (GLdouble) _winMaxX,
	     (GLdouble) _winMinY, (GLdouble) _winMaxY);
}

void Graphics::display()
{
  glClear(GL_COLOR_BUFFER_BIT);

  // draw all the objects
  for (list< list<drawableObject*> * >::iterator m = objectLists.begin();
       m != objectLists.end(); m++) {
    if (Graphics::verbose)
      cout << "This list has " << (*m)->size() << " objects." << endl;
    for (list<drawableObject*>::iterator k = (**m).begin();
	 k != (**m).end(); k++) {
      int pts = (*k)->numPoints();

      if ((*k)->type() == drawableObject::polygon) {
	if (Graphics::verbose)
	  cout << "drawing a " << pts << " sided polygon." << endl;
	if (pts == 1) { // must be a point
	  glPointSize((GLfloat) (*k)->width());
	  glBegin(GL_POINTS);
	  setColor((*k)->lineColor());
	  drawPoints(pts, *k);
	}
	else if (pts == 2) { // a line segment
	  glLineWidth((GLfloat) (*k)->width());
	  glBegin(GL_LINE_STRIP);
	  setColor((*k)->lineColor());
	  drawPoints(pts, *k);
	}
	else if (pts >= 3) { // a polygon or a chain of line segments
	  if ((*k)->fill()) {
	    // draw the body
	    glBegin(GL_POLYGON);
	    setColor((*k)->bodyColor());
	    drawPoints(pts,*k);
	  }
	  if ((*k)->drawBorder()) {
	    // draw the border (or chain of line segments)
	    glLineWidth((GLfloat) (*k)->width());
	    if ((*k)->closed()) 
	      glBegin(GL_LINE_LOOP); 
	    else
	      glBegin(GL_LINE_STRIP);
	    setColor((*k)->lineColor());
	    drawPoints(pts,*k);
	  }
	}
      }
      else if ((*k)->type() == drawableObject::ellipse) {
	if (Graphics::verbose)
	  cout << "Drawing an ellipse." << endl;
	if (pts != 1) 
	  continue;
	if (!((*k)->fill() ^ (*k)->drawBorder())) 
	  continue; // can only handle (filled XOR outlined) ellipses
	if ((*k)->ellipseMajorRadius() == 0.0)
	  continue;
	// 4/3/04 for some reason this doesn't compile on the suns
// 	if (Q == 0) {
// 	  Q = gluNewQuadric();
// 	  gluQuadricCallback(Q, GLU_ERROR, errorCallback);
// 	}	
	if ((*k)->fill()) 
	  gluQuadricDrawStyle(Q, GLU_FILL);
	if ((*k)->drawBorder()) {
	  gluQuadricDrawStyle(Q, GLU_SILHOUETTE);
	  glLineWidth((GLfloat) (*k)->width());
	}
	// set the coordinate transform so that the circle turns into
	// an ellipse of the correct shape, orientation, and position
	double xc,yc;
	(*k)->getPoint(0, &xc, &yc);
	glTranslated(xc, yc, 0.0);
	glRotated((*k)->ellipseOrientation()*180/M_PI, 0, 0, 1);
	glScaled(1.0, (*k)->ellipseAspectRatio(), 1.0);
	// make a circle
	double minDim = ((_maxX - _minX) > (_maxY - _minY)) ?
	  (_maxX - _minX) : (_maxY - _minY);
	int segments = (int) (4 * (*k)->ellipseMajorRadius() / (minDim/50.0));
	if (segments < 16)
	  segments = 16;
	if (Graphics::verbose)
	  cout << "using " << segments << " segments." << endl;
	gluDisk(Q, 0.0, (*k)->ellipseMajorRadius(), segments, 1);
	glLoadIdentity();
	gluOrtho2D((GLdouble) _winMinX, (GLdouble) _winMaxX,
		   (GLdouble) _winMinY, (GLdouble) _winMaxY);
      }
      else {
	if (Graphics::verbose)
	  cout << "Unrecognized object in a drawable object list." << endl;
      }
    }
  }
  glFlush();
  glutSwapBuffers();
}

void Graphics::addColor(const char* name, float red, float green, float blue)
{
  float *p = new float[3];
  p[0]=red;
  p[1]=green;
  p[2]=blue;
  colors[string(name)] = p;
}

void Graphics::setColor(const string& name)
{
  glColor3fv( colors[name]);
}


void Graphics::drawPoints(int numPoints, const drawableObject* O)
{
  double x,y;
  for (int k=0; k<numPoints; k++) {
    O->getPoint(k, &x, &y);
    glVertex2d(x,y);
  }
  glEnd();
}
    
void Graphics::keyboard(unsigned char key, int x, int y)
{
  if (userKeyboard == 0) {
    if (key == 'q')
      exit(0);
    else
      return;
  }
  else {
    userKeyboard(key);
    glutPostRedisplay(); // assume that the window must be redrawn
  }
}

void Graphics::runEventLoop()
{
  glutMainLoop();
}

void Graphics::setKeyboardHandler(void (*kb)(unsigned char key))
{ 
  userKeyboard=kb;
}

//void CALLBACK Graphics::errorCallback(GLenum errCode)
void CALLBACK Graphics::errorCallback()
{
  //  const GLubyte *message;

  //  message = gluErrorString(errCode);
  //  cerr << "Quadric error: " << message << endl;
  exit(1);
}

Ellipse::Ellipse(double X, double Y, double aspect, double majRad, 
		 double angle) 
  : xc(X), yc(Y), orientation(angle), majorRadius(majRad),
    aspectRatio(aspect) 
{
  _type = drawableObject::ellipse;

  _drawBorder = true;
  _width = 2;
  _lineColor = string("black");
  _fill = false;
}
