// particles.cpp
// by Glenn G. Chappell
// March 2004
//
// For CS 481/681
// Particle simulation demo

#include <iostream>
using std::cerr;
using std::endl;
#include <vector>
#include <string>
using std::string;
#include <stdlib.h>
//using std::exit;
#include <time.h>    // Some versions of MS-Vis C++ have broken <ctime>
//using std::clock;
#include <GL/glut.h> // GLUT stuff - includes OpenGL headers

#include "vecpos.h"
#include "tfogl.h"   // Include GLUT stuff first!!
using namespace tf;

// Global variables
// Window/viewport
const int startwinsize = 400; // Starting window width & height, in pixels
const double neardist = 1.;
const double fardist = 10.;

// Keyboard
const int ESCKEY = 27;        // ASCII value of escape character

// For particles
struct Particle {
   pos position;
   vec velocity;
   double mass;
};

std::vector<Particle> particles;
                              // List of particles


// 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);
   }
}


vec compute_accel(const pos & position,
                  const vec & velocity,
                  double mass)
{
   return vec(0., 0., 0.);;
}


void render_particle(const Particle & p)
{
   glMaterialAmbientAndDiffuse(GL_FRONT_AND_BACK, vec(0.9, 0.6, 0.4));
   glMaterialShininess(GL_FRONT_AND_BACK, 100.);

   glPushMatrix();
      glTranslate(vec(p.position));
      glutSolidSphere(0.1, 30, 15);
   glPopMatrix();
}


// display
// The GLUT display function
void display()
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   glPushMatrix();
      glTranslated(0., 0., -4.);
      std::vector<Particle>::iterator it;
      for (it = particles.begin();
         it != particles.end();
         ++it)
      {
         Particle & p = *it;
         render_particle(p);
      }
   glPopMatrix();

   // Text documentation
   glDisable(GL_DEPTH_TEST);  // No depth test for text
   glDisable(GL_LIGHTING);
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();  // Save old projection matrix
      glLoadIdentity();
      gluOrtho2D(-1., 1., -1., 1.);  // old faithful
      glColor3d(1., 1., 1.);
      printbitmap("Particles", -0.9, 0.9);
      printbitmap("Esc     Quit", -0.9, 0.8);
   glPopMatrix();  // Restore old projection matrix
   glMatrixMode(GL_MODELVIEW);  // Always go back to model/view mode
   glEnable(GL_LIGHTING);
   glEnable(GL_DEPTH_TEST);  // Back to "normal" for 3-D drawing

   glutSwapBuffers();
}


// 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;
   }
}


// 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;
   }

   // Move particles
   static double previous_time;
   static bool initialized = false;
   if (!initialized)
   {
      previous_time = double(clock())/CLOCKS_PER_SEC;
      initialized = true;
   }
   double current_time = double(clock())/CLOCKS_PER_SEC;
   double elapsed_time = current_time - previous_time;

   std::vector<Particle>::iterator it;
   for (it = particles.begin();
        it != particles.end();
        ++it)
   {
      Particle & p = *it;

      vec current_accel = compute_accel(p.position, p.velocity, p.mass);
      pos next_position_guess = p.position + p.velocity * elapsed_time;
      vec next_velocity_guess = p.velocity + current_accel * elapsed_time;
      vec next_accel_guess =
         compute_accel(next_position_guess, next_velocity_guess, p.mass);
      p.position += (p.velocity + next_velocity_guess)/2. * elapsed_time;
      p.velocity += (current_accel + next_accel_guess)/2. * elapsed_time;
   }

   previous_time = current_time;
   glutPostRedisplay();
}


// init
// Initializes GL states
// Called by main
void init()
{
   // General Graphics
   glClearColor(0.2, 0.2, 0.2, 0.0);
   glEnable(GL_DEPTH_TEST);

   // Lighting
   // Set up a light
   glLightPosition(GL_LIGHT0, pos(-1., 1.3, 1.));
   glEnable(GL_LIGHT0);
   glEnable(GL_LIGHTING);

   // Particles
   Particle p;

   p.position = pos(-1., 0., 0.);
   p.velocity = vec(0.1, 0.1, -0.2);
   p.mass = 1.;
   particles.push_back(p);

   p.position = pos(1., 0.1, 0.3);
   p.velocity = vec(-0.1, -0.1, 0.2);
   p.mass = 1.;
   particles.push_back(p);
}


int main(int argc, char ** argv)
{
   // Initialize OpenGL/GLUT
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

   // Make a window
   glutInitWindowSize(startwinsize, startwinsize);
   glutInitWindowPosition(50, 50);
   glutCreateWindow("CS 481/681 - Particles");

   // Initialize GL states & register callbacks
   init();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutIdleFunc(idle);

   // Do something
   glutMainLoop();

   return 0;
}
