// anim.cpp
// Glenn G. Chappell
// 11 Sep 2012
//
// For CS 381 Fall 2012
// Animation Demo

// OpenGL/GLUT includes - DO THESE FIRST
#include <cstdlib>       // Do this before GL/GLUT includes
using std::exit;
#ifndef __APPLE__
# include <GL/glut.h>    // GLUT stuff, includes OpenGL headers as well
#else
# include <GLUT/glut.h>  // Apple puts glut.h in a different place
#endif

// Other includes
#include "lib381/bitmapprinter.h"
                         // For class BitmapPrinter
#include <iostream>
using std::cout;
using std::cerr;
using std::endl;


// Global variables
// Keyboard
const int ESCKEY = 27;         // ASCII value of Escape

// Window/viewport
const int startwinsize = 600;  // Start window width & height (pixels)

// Objects
double savetime;               // Time of previous movement (sec)

// Square #1
const double s1halfside = 0.1; // Square 1 half side len (camunits)
double s1xpos, s1ypos;         // Square 1 x & y pos (camunits)
double s1xvel, s1yvel;         // Square 1 x & y velocity (camunits/sec)
const double gravity = 2.5;    // Down accel of gravity (camunits/sec^2)
const double damp = 0.9;       // Velocity damping factor for bounces

// Square #2
const double s2halfside = 0.2; // Square 2 half side len (camunits)
double s2xpos, s2ypos;         // Square 2 x & y pos (camunits)
double s2ang;                  // Square 2 rotation angle (deg)
const double s2angspeed = 200.;
                               // Square 2 rotation speed (deg/sec)
bool s2rotateflag;             // True if square 2 is rotating


// drawSquare
// Draws a filled square, using current GL states,
//  in the x,y plane, centered at the origin, aligned w/ x & y axes,
//  with side 2.
void drawSquare()
{
    glBegin(GL_QUADS);
        glVertex2d(-1., -1.);
        glVertex2d( 1., -1.);
        glVertex2d( 1.,  1.);
        glVertex2d(-1.,  1.);
    glEnd();
}


// myDisplay
// The GLUT display function
void myDisplay()
{
    glClearColor(0.7f, 0.7f, 0.7f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw objects

    // Set up initial transformation
    glLoadIdentity();

    // Square #1 (bouncing)
    glPushMatrix();
    glTranslated(s1xpos, s1ypos, 0.);
    glScaled(s1halfside, s1halfside, s1halfside);
    glColor3d(0.9, 0.1, 0.1);
    drawSquare();
    glPopMatrix();

    // Square #2 (rotating)
    glPushMatrix();
    glTranslated(s2xpos, s2ypos, 0.);
    glRotated(s2ang, 0.,0.,1.);
    glScaled(s2halfside, s2halfside, s2halfside);
    glColor3d(0.1, 0.1, 0.9);
    drawSquare();
    glPopMatrix();

    // Draw documentation
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);  // Set up simple ortho projection
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(-1., 1., -1., 1.);
    glColor3d(0., 0., 0.);        // Black text
    BitmapPrinter p(-0.9, 0.9, 0.1);
    p.print("Space    Restart bouncing");
    p.print("R        Toggle rotation");
    p.print("Esc      Quit");
    glPopMatrix();                // Restore prev projection
    glMatrixMode(GL_MODELVIEW);

    glutSwapBuffers();
        // ***** NOTE: Double buffering *****
}


// myIdle
// The GLUT idle function
void myIdle()
{
    // Compute elapsed time since last movement
    double currtime = glutGet(GLUT_ELAPSED_TIME)/1000.;
    double elapsedtime = currtime - savetime;
    savetime = currtime;
    if (elapsedtime > 0.1)
        elapsedtime = 0.1;

    // Move objects

    // Square #1: gravity
    s1yvel -= gravity * elapsedtime;

    // Square #1: velocity
    s1xpos += s1xvel * elapsedtime;
    s1ypos += s1yvel * elapsedtime;
    glutPostRedisplay();

    // Square #1: bouncing
    if (s1xpos + s1halfside > 1.)
    {
        double outamount = (s1xpos + s1halfside) - 1.;
        s1xpos -= 2 * outamount;
        s1xvel = damp * s1xvel * -1.;
        s1yvel = damp * s1yvel;
    }

    if (s1xpos - s1halfside < -1.)
    {
        double outamount = (s1xpos - s1halfside) - (-1.);
        s1xpos -= 2 * outamount;
        s1xvel = damp * s1xvel * -1.;
        s1yvel = damp * s1yvel;
    }

    if (s1ypos + s1halfside > 1.)
    {
        double outamount = (s1ypos + s1halfside) - 1.;
        s1ypos -= 2 * outamount;
        s1xvel = damp * s1xvel;
        s1yvel = damp * s1yvel * -1.;
    }

    if (s1ypos - s1halfside < -1.)
    {
        double outamount = (s1ypos - s1halfside) - (-1.);
        s1ypos -= 2 * outamount;
        s1xvel = damp * s1xvel;
        s1yvel = damp * s1yvel * -1.;
    }

    // Square #2: rotation
    if (s2rotateflag)
    {
        s2ang += s2angspeed * elapsedtime;
        glutPostRedisplay();
    }

    // Print OpenGL errors, if there are any (for debugging)
    static int error_count = 0;
    if (GLenum err = glGetError())
    {
        ++error_count;
        cerr << "OpenGL ERROR " << error_count << ": "
             << gluErrorString(err) << endl;
    }
}


// resetSquare1Velocity
// Set initial velocity of bouncing square
void resetSquare1Velocity()
{
    s1xvel = 3.0;
    s1yvel = 4.0;
}


// myKeyboard
// The GLUT keyboard function
void myKeyboard(unsigned char key, int x, int y)
{
    switch (key)
    {
    case ESCKEY:  // Esc: quit
        exit(0);
        break;
    case ' ':     // Space: restart bouncing
        resetSquare1Velocity();
        // No redisplay post is necessary here;
        //  Velocity does not directly affect the scene
        break;
    case 'r':
    case 'R':
        s2rotateflag = !s2rotateflag;
        // No redisplay post is necessary here;
        //  s2rotateflag does not directly affect the scene
        break;
    }
}


// init
// Initialize GL states & global data
// Called by main after window creation
void init()
{
    // Objects
    savetime = glutGet(GLUT_ELAPSED_TIME)/1000.;

    // Square #1
    resetSquare1Velocity();
    s1xpos = -0.5;
    s1ypos = 0.2;

    // Square #2
    s2xpos = 0.;
    s2ypos = 0.;
    s2ang = 0.;
    s2rotateflag = true;

    // OpenGL Stuff
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(-1., 1., -1., 1.);

    glMatrixMode(GL_MODELVIEW);  // Always go back to model/view mode
}


int main(int argc, char ** argv)
{
    // Initialize OpenGL/GLUT
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
        // ***** NOTE: Double buffering *****

    // Make a window
    glutInitWindowSize(startwinsize, startwinsize);
    glutInitWindowPosition(50, 50);
    glutCreateWindow("CS 381 - Animation Demo");

    // Initialize GL states & register GLUT callbacks
    init();
    glutDisplayFunc(myDisplay);
    glutIdleFunc(myIdle);
    glutKeyboardFunc(myKeyboard);

    // Do something
    glutMainLoop();

    return 0;
}

