// simplejitter.cpp
// by Glenn G. Chappell
// February 2004
//
// For CS 481/681
// Demonstrates jittering
// SINGLE-BUFFERED, so we can see what's going on.

#include <iostream>
using std::cerr;
using std::endl;
#include <string>
using std::string;
#include <sstream>
#include <stdlib.h>  // Some versions of MS-Vis C++ have broken <cstdlib>
//using std::exit;
#include <GL/glut.h> // GLUT stuff, includes OpenGL headers as well
#include "jitter.h"  // SGI jittering data; include this AFTER glut.h


// Global variables
// Window/viewport
const int startwinsize = 400; // Starting window width & height, in pixels
const double neardist = 1.;   // Near & far clipping distances
const double fardist = 10.;

// Keyboard
const int ESCKEY = 27;        // ASCII value of escape character

// Jittering data
jitter_point j1[] = { { 0., 0. } };
                              // Our own "no jittering" array, in
                              // addition to SGI's arrays in jitter.h
const int NUM_JITTER_ARRAYS = 8;
                              // Number of jittering arrays
const jitter_point * jitter_arrays[NUM_JITTER_ARRAYS]
   = { j1, j2, j3, j4, j8, j15, j24, j66 };
                              // Ptrs to the jittering arrays
const int jitter_array_sizes[NUM_JITTER_ARRAYS]
   = { 1, 2, 3, 4, 8, 15, 24, 66 };
                              // Sizes of the jittering arrays

int jsubs = 3;                // subscript, selects WHICH jittering array
const int maxjsubs = NUM_JITTER_ARRAYS - 1;
const int minjsubs = 0;

// Globals affecting display
const double jitter_amount = 0.5;


// tostring
// Convert argument to string class using operator<<
// Must include <sstream>
template<typename T>
std::string tostring(const T & input)
{
   std::ostringstream os;
   os << input;
   return os.str();
}


// printbitmap
// Prints the given string at the given raster position
//  using GLUT bitmap fonts.
// You probably don't want any rotations in the model/view
//  transformation when calling this function.
void printbitmap(const string msg, double x, double y)
{
   glRasterPos2d(x, y);
   for (string::const_iterator ii = msg.begin();
        ii != msg.end();
        ++ii)
   {
      glutBitmapCharacter(GLUT_BITMAP_9_BY_15, *ii);
   }
}


// setmaterial_main
// Set material paramters for main material
void setmaterial_main()
{
   GLfloat diffuse[] = { 0.9, 0.2, 0.5, 1.0 };
   GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat shininess = 100.;
   glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, diffuse);
   glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
   glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess);
}


// display
// The GLUT display function
void display()
{
   const int jitters = jitter_array_sizes[jsubs];
   for (int i=0; i<jitters; ++i)
   {
      const GLfloat jx = jitter_arrays[jsubs][i].x;  // x & y offset
      const GLfloat jy = jitter_arrays[jsubs][i].y;  // for this jitter

      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      setmaterial_main();
      glPushMatrix();
         glLoadIdentity();
         glTranslated(0., 0., -4.);
         glTranslated(jx*jitter_amount, jy*jitter_amount, 0.);
         glutSolidTorus(0.5, 1., 20, 40);
      glPopMatrix();

      glAccum(((i > 0) ? GL_ACCUM : GL_LOAD), 1./jitters);
   }

   // Make final image
   glAccum(GL_RETURN, 1.);

   // Draw instructions
   glDisable(GL_LIGHTING);
   glDisable(GL_DEPTH_TEST);
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
      glLoadIdentity();
      gluOrtho2D(-1., 1., -1., 1.);
      glColor3d(1., 1., 1.);
      printbitmap("Jittering Demo", -0.9, 0.9);
      printbitmap("Large windows may be SLOW!", -0.9, 0.8);
      printbitmap("Number of jitters: "
                     + tostring(jitter_array_sizes[jsubs]),
                  -0.9, 0.7);
      printbitmap("<- ->  Change number of jitters", -0.9, 0.6);
      printbitmap("Esc    Quit", -0.9, 0.5);
   glPopMatrix();
   glMatrixMode(GL_MODELVIEW);
   glEnable(GL_DEPTH_TEST);
   glEnable(GL_LIGHTING);

   glFlush();  // We are SINGLE-BUFFERED!!
}


// idle
// The GLUT idle function
void idle()
{
   // Print OpenGL errors, if there are any (for debugging)
   if (GLenum err = glGetError())
   {
      cerr << "OpenGL ERROR: " << gluErrorString(err) << endl;
   }
}


// reshape
// The GLUT reshape function
void reshape(int w, int h)
{
   glViewport(0, 0, w, h);

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(60, double(w)/h, neardist, fardist);

   glMatrixMode(GL_MODELVIEW);  // Always go back to modelview mode
}


// keyboard
// The GLUT keyboard function
void keyboard(unsigned char key, int x, int y)
{
   switch (key)
   {
   case ESCKEY:  // ESC: Quit
      exit(0);
      break;
   }
}


// special
// The GLUT special function
void special(int key, int x, int y)
{
   switch (key)
   {
   case GLUT_KEY_RIGHT:  // -> more jitters
      if (jsubs < maxjsubs)
      {
         ++jsubs;
         glutPostRedisplay();
      }
      break;
   case GLUT_KEY_LEFT:  // <- fewer jitters
      if (jsubs > minjsubs)
      {
         --jsubs;
         glutPostRedisplay();
      }
      break;
   }
}


// init
// Initialization
// Called by main after window creation
void init()
{
   glEnable(GL_DEPTH_TEST);  // We're doing 3-D

   glClearColor(0., 0., 0., 0.);  // Background color

   // Set up a light
   GLfloat light_position[] = { -1.0, 1.3, 1.0, 0.0 };
   glLightfv(GL_LIGHT0, GL_POSITION, light_position);
   glEnable(GL_LIGHT0);

   // General lighting stuff
   glEnable(GL_LIGHTING);
}


int main(int argc, char ** argv)
{
   // Initialize OpenGL/GLUT
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH | GLUT_ACCUM);
      // SINGLE-BUFFERED!!

   // Make a window
   glutInitWindowSize(startwinsize, startwinsize);
   glutInitWindowPosition(50, 50);
   glutCreateWindow("CS 481/681 - Jittering Demo");

   // Initialize GL states & register callbacks
   init();
   glutDisplayFunc(display); 
   glutIdleFunc(idle);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutSpecialFunc(special);

   // Do something
   glutMainLoop();

   return 0;
}
