// check_opengl.cpp
// Glenn G. Chappell
// 5 Sep 2012
//
// For CS 381 Fall 2012
// Check that OpenGL & GLUT work
// Used in Assignment 1, Exercise A

// 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 <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)
const double vxmin = -1.;      // Viewport bdaries in world (camunits)
const double vxmax = 1.;
const double vymin = -1.;
const double vymax = 1.;

// Object
double savetime;               // Time of previous movement (sec)
double xpos, ypos;             // x, y pos of object (camunits)
double xvel = 2.0, yvel = 1.591;
                               // x, y velocity of object (camunits/sec)
const double objhalfsize = 0.25;
                               // Half object width & height (camunits)
double angle;                  // Rotation angle of object (deg)
const double anglespeed = 70.;
                               // Rotation speed (deg/sec)


// myDisplay
// The GLUT display function
void myDisplay()
{
    int i;  // loop counter
    int data[] = {
         1,  1,  2,  1,  2,  8,  1,  8,
         2,  4,  4,  4,  4,  5,  2,  5,
         4,  1,  5,  1,  5,  8,  4,  8,
         6,  1,  9,  1,  9,  2,  6,  2,
         7,  2,  8,  2,  8,  7,  7,  7,
         6,  7,  9,  7,  9,  8,  6,  8,
        10,  3, 11,  3, 11,  8, 10,  8,
        10,  1, 11,  1, 11,  2, 10,  2
    };
    const double xx = 1./12.;
    const double yy = 1./9.;

    // What is the above for? Explanatory comments are GOOD, of course,
    //  but maybe not in a program that is *supposed* to be a mystery.

    glClearColor(0.8f, 0.7f, 0.9f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw the moving object
    glPushMatrix();
    glLoadIdentity();
    glTranslated(xpos, ypos, 0.);
    glRotated(angle, 0.,0.,1.);
    glScaled(objhalfsize, objhalfsize, objhalfsize);
    glColor3d(0.9, 0.1, 0.1);
    glBegin(GL_QUADS);
        glVertex2d(-1., -1.);
        glVertex2d( 1., -1.);
        glVertex2d( 1.,  1.);
        glVertex2d(-1.,  1.);
    glEnd();
    glColor3d(0.9, 0.9, 0.1);
    glBegin(GL_LINES);
        glVertex2d(-0.6, 0.5);
        glVertex2d(-0.4, 0.5);
        glVertex2d( 0.4, 0.5);
        glVertex2d( 0.6, 0.5);
    glEnd();
    glBegin(GL_LINE_STRIP);
        glVertex2d(-0.6, -0.4);
        glVertex2d(-0.4, -0.6);
        glVertex2d( 0.4, -0.6);
        glVertex2d( 0.6, -0.4);
    glEnd();

    glPopMatrix();

    // Draw the mysterious other stuff
    glColor3d(0.1, 0.9, 0.1);
    glBegin(GL_QUADS);
        for (i = 0; i < 12; ++i)
            glVertex2d(xx*data[2*i]*2.-1., yy*data[2*i+1]*2.-1.);
    glEnd();

    glColor3d(0.1, 0.9, 0.9);
    glBegin(GL_QUADS);
        for (i = 12; i < 24; ++i)
            glVertex2d(xx*data[2*i]*2.-1., yy*data[2*i+1]*2.-1.);
    glEnd();

    glColor3d(0.1, 0.1, 0.9);
    glBegin(GL_QUADS);
        for (i = 24; i < 32; ++i)
            glVertex2d(xx*data[2*i]*2.-1., yy*data[2*i+1]*2.-1.);
    glEnd();

    glutSwapBuffers();
}


// 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
    // (The bouncing is not quite right -- so sue me)
    xpos += xvel * elapsedtime;
    if (xpos < vxmin+objhalfsize)
    {
        xvel = -xvel;
        xpos = 2.*(vxmin+objhalfsize)-xpos;
    }
    if (xpos > vxmax-objhalfsize)
    {
        xvel = -xvel;
        xpos = 2.*(vxmax-objhalfsize)-xpos;
    }

    ypos += yvel * elapsedtime;
    if (ypos > vymax-objhalfsize)
    {
        yvel = -yvel;
        ypos = 2.*(vymax-objhalfsize)-ypos;
    }
    if (ypos < vymin+objhalfsize)
    {
        yvel = -yvel;
        ypos = 2.*(vymin+objhalfsize)-ypos;
    }

    angle += anglespeed * 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;
    }
}


// myKeyboard
// The GLUT keyboard function
void myKeyboard(unsigned char key, int x, int y)
{
    switch (key)
    {
    case ESCKEY:  // Esc: quit
        exit(0);
        break;
    }
}


// init
// Initialize GL states & global data
// Called by main after window creation
void init()
{
    // Objects
    savetime = glutGet(GLUT_ELAPSED_TIME)/1000.;

    xpos = 0.;
    ypos = 0.;
    angle = 0.;

    // OpenGL Stuff
    glLineWidth(10.0);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(vxmin, vxmax, vymin, vymax);

    glMatrixMode(GL_MODELVIEW);  // Always go back to model/view mode

    // Documentation
    cout << "Check that OpenGL & GLUT Work" << endl;
    cout << "by Glenn G. Chappell" << endl;
    cout << "for CS 381 Fall 2012" << endl;
    cout << endl;
    cout << "Esc (in graphics window) to quit." << endl;
}


int main(int argc, char ** argv)
{
    // Initialize OpenGL/GLUT
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);

    // Make a window
    glutInitWindowSize(startwinsize, startwinsize);
    glutInitWindowPosition(50, 50);
    glutCreateWindow("CS 381 - Check That OpenGL & GLUT Work");

    // Initialize GL states & register GLUT callbacks
    init();
    glutDisplayFunc(myDisplay);
    glutIdleFunc(myIdle);
    glutKeyboardFunc(myKeyboard);

    // Do something
    glutMainLoop();

    return 0;
}

