#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <GL/glut.h>
#include <sys/resource.h>
#include <sys/time.h>

/*
   Crappy little demonstration OpenGL application for testing purposes.
*/

typedef struct {
   double x,y,z;
} XYZ;
typedef struct {
   double r,g,b;
} COLOUR;
typedef struct {
   XYZ vp;              /* View position           */
   XYZ vd;              /* View direction vector   */
   XYZ vu;              /* View up direction       */
   double focallength;  /* Focal Length along vd   */
   double aperture;     /* Camera aperture         */
} CAMERA;

void Display(void);
void MakeGeometry(void);
void MakeEnvir(void);
void HandleKeyboard(unsigned char key,int x, int y);
void HandleSpecialKeyboard(int key,int x, int y);
void HandleMouse(int,int,int,int);
void HandleVisibility(int vis);
void HandleReshape(int,int);
void HandleMouseMotion(int,int);
void HandleIdle(void);
void Normalise(XYZ *);
double GetRunTime(void);

#define TRUE  1
#define FALSE 0
#define PI 3.141592653589793238462643
#define TWOPI 6.283185307179586476925287
#define DTOR 0.0174532925
#define RTOD 57.2957795
#define CROSSPROD(p1,p2,p3) \
   p3.x = p1.y*p2.z - p1.z*p2.y; \
   p3.y = p1.z*p2.x - p1.x*p2.z; \
   p3.z = p1.x*p2.y - p1.y*p2.x

CAMERA camera;
int screenwidth=600,screenheight=400;

int main(int argc,char **argv)
{
   int i;
   int fullscreen = FALSE;

   /* Parse the command line arguments */
   for (i=1;i<argc;i++) {
      if (strstr(argv[i],"-f") != NULL)
         fullscreen = TRUE;
   }

   /* Set things up and go */
   glutInit(&argc,argv);
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

   glutCreateWindow("Demo");
   glutReshapeWindow(screenwidth,screenheight);
   if (fullscreen)
      glutFullScreen();
   glutDisplayFunc(Display);
   glutReshapeFunc(HandleReshape);
   glutVisibilityFunc(HandleVisibility);
   glutKeyboardFunc(HandleKeyboard);
   glutSpecialFunc(HandleSpecialKeyboard);
   glutMouseFunc(HandleMouse);
   glutMotionFunc(HandleMouseMotion);

   /* Ready to go! */
   MakeEnvir();
   glutMainLoop();
   return(0);
}

/*
   This is the basic display callback routine
*/
void Display(void)
{
   double ratio,radians,wd2,ndfl;
   double left,right,top,bottom,near=0.1,far=1000;

   ratio   = screenwidth / (double)screenheight;
   radians = DTOR * camera.aperture / 2;
   wd2     = near * tan(radians);
   ndfl    = near / camera.focallength;

   glDrawBuffer(GL_BACK_LEFT);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   left  = - ratio * wd2;
   right =   ratio * wd2;
   top    =   wd2;
   bottom = - wd2;
   glFrustum(left,right,bottom,top,near,far);
   glMatrixMode(GL_MODELVIEW);
   glDrawBuffer(GL_BACK_LEFT);
   glLoadIdentity();
   gluLookAt(camera.vp.x,camera.vp.y,camera.vp.z,
             camera.vp.x + camera.vd.x,
             camera.vp.y + camera.vd.y,
             camera.vp.z + camera.vd.z,
             camera.vu.x,camera.vu.y,camera.vu.z);
     MakeGeometry();
   glutSwapBuffers();
}

/*
   Create the geometry 
*/
void MakeGeometry(void)
{
   static double theta = 0;

   glPushMatrix();
   glRotatef(theta,0.0,0.0,1.0);

   /* Axis */
   glBegin(GL_LINES);
   glColor3f(1.0,0.0,0.0);
   glVertex3f(0.0,0.0,0.0);
   glVertex3f(1.0,0.0,0.0);
   glColor3f(0.0,1.0,0.0);
   glVertex3f(0.0,0.0,0.0);
   glVertex3f(0.0,1.0,0.0);
   glColor3f(0.0,0.0,1.0);
   glVertex3f(0.0,0.0,0.0);
   glVertex3f(0.0,0.0,1.0);
   glEnd();

   /* Coincident lines */
   glColor3f(1.0,1.0,1.0);  
   glBegin(GL_LINE_LOOP); 
   glVertex3f(0.0,0.0,0.5);
   glVertex3f(1.0,0.0,0.5);
   glVertex3f(1.0,1.0,0.5); 
   glVertex3f(0.0,1.0,0.5); 
   glVertex3f(0.0,0.0,0.5); 
   glEnd();

   glPopMatrix();
   theta += 1;
}

/*
   Set up the Opengl state
*/
void MakeEnvir(void)
{
   GLfloat position[4] = {0.0,0.0,0.0,0.0};
   GLfloat ambient[4]  = {0.2,0.2,0.2,1.0};
   GLfloat black[4] = {0.0,0.0,0.0,1.0};
   GLfloat white[4] = {1.0,1.0,1.0,1.0};
   GLfloat shiny[1] = {5.0};

   glEnable(GL_DEPTH_TEST);
   glDisable(GL_LINE_SMOOTH);
   glDisable(GL_POINT_SMOOTH);
   glDisable(GL_POLYGON_SMOOTH);
   glDisable(GL_DITHER);
   glDisable(GL_CULL_FACE);
   glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
   glFrontFace(GL_CW);
   glClearColor(0.0,0.0,0.0,0.0);
   glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
   glEnable(GL_COLOR_MATERIAL);
   glPixelStorei(GL_UNPACK_ALIGNMENT,1);
   glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_FALSE);
   glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
   glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,white);
   glMaterialfv(GL_FRONT_AND_BACK,GL_SHININESS,shiny);
   glLightModelfv(GL_LIGHT_MODEL_AMBIENT,white);
   glLightfv(GL_LIGHT0,GL_POSITION,position);
   glLightfv(GL_LIGHT0,GL_AMBIENT,ambient);
   glLightfv(GL_LIGHT0,GL_DIFFUSE,white);
   glLightfv(GL_LIGHT0,GL_SPECULAR,black);
   glEnable(GL_LIGHT0);

   camera.vp.x = -3; camera.vp.y = 0; camera.vp.z =  1;
   camera.vd.x =  3; camera.vd.y = 0; camera.vd.z = -1;
   camera.vu.x =  0; camera.vu.y = 0; camera.vu.z =  1;
   Normalise(&camera.vd);
   camera.focallength = 5;   
   camera.aperture = 60;
}

void HandleKeyboard(unsigned char key,int x, int y)
{
   switch (key) {
   case 27:                            /* Quit */
   case 'Q':
   case 'q': 
      exit(0); 
      break;
   }
}

void HandleSpecialKeyboard(int key,int x, int y)
{
   switch (key) {
   case GLUT_KEY_LEFT:
      break;
   case GLUT_KEY_RIGHT:
      break;
   case GLUT_KEY_UP:
      break;
   case GLUT_KEY_DOWN:
      break;
   }
}

void HandleMouse(int button,int state,int x,int y)
{
   if (state == GLUT_DOWN) {
      if (button == GLUT_LEFT_BUTTON) {
         ;
      } else if (button == GLUT_MIDDLE_BUTTON) {
         ;
      } 
   }
}

void HandleVisibility(int visible)
{
   if (visible == GLUT_VISIBLE)
      glutIdleFunc(HandleIdle);
   else
      glutIdleFunc(NULL);
}

/*
   What to do on an idle event
*/
void HandleIdle(void)
{
   /*
   static double tstart = -1; 
   double tstop;
   double target = 0.04;

   if (tstart < 0)
      tstart = GetRunTime();
   tstop = GetRunTime();
   if (tstop - tstart > target) {
      glutPostRedisplay();
      tstart = tstop;
   }
   */
   glutPostRedisplay();
}

/*
   Handle a window reshape/resize
*/
void HandleReshape(int w,int h)
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glViewport(0,0,(GLsizei)w,(GLsizei)h);
   screenwidth = w;
   screenheight = h;
}

void Normalise(XYZ *p)
{
   double length;

   length = sqrt(p->x * p->x + p->y * p->y + p->z * p->z);
   if (length != 0) {
      p->x /= length;
      p->y /= length;
      p->z /= length;
   } else {
      p->x = 0;
      p->y = 0;
      p->z = 0;
   }
}

void HandleMouseMotion(int x,int y)
{

}

double GetRunTime(void)
{
   double sec = 0;
   struct timeval tp;
 
   gettimeofday(&tp,NULL);
   sec = tp.tv_sec + tp.tv_usec / 1000000.0;
 
   return(sec);
}


