// showmat.cpp
// Glenn G. Chappell
// 19 Sep 2012
//
// For CS 381 Fall 2012
// Display a Transformation Matrix

// 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 <iomanip>
using std::setw;
#include <sstream>
using std::ostringstream;
#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)
int winw = 1, winh = 1;        // Window width, height (pixels)
                               //  (Initialize to avoid spurious errors)


// 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();
}


// drawF
// Draws a letter "F" made of rectangles, using current GL states,
//  in the x,y plane, fitting in square going from -1 to 1 in both x, y.
void drawF()
{
    // Upper horizontal bar
    glPushMatrix();
    glTranslated(0.0, 0.8, 0.0);
    glScaled(1.0, 0.2, 1.0);
    drawSquare();
    glPopMatrix();

    // Lower horizontal bar
    glPushMatrix();
    glTranslated(-0.3, 0.0, 0.0);
    glScaled(0.7, 0.2, 1.0);
    drawSquare();
    glPopMatrix();

    // Vertical bar
    glPushMatrix();
    glTranslated(-0.8, 0.0, 0.0);
    glScaled(0.2, 1.0, 1.0);
    drawSquare();
    glPopMatrix();
}


// myDisplay
// The GLUT display function
void myDisplay()
{
    glClearColor(1.0f, 0.9f, 0.8f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Set model/view matrix
    glLoadIdentity();
    // *****************************************
    // PUT OTHER TRANSFORMATION COMMANDS HERE
    // E.G.
    //     glTranslated(0.5, -0.3, 0.2);
    // *****************************************

    // Draw a pink untransformed "F"
    glPushMatrix();
    glLoadIdentity();
    glColor3d(1.0, 0.8, 0.9);
    glScaled(0.3, 0.3, 0.3);
    drawF();
    glPopMatrix();

    // Draw a blue transformed "F", to illustrate effect of model/view
    glPushMatrix();
    glColor3d(0.6, 0.7, 1.0);
    glScaled(0.3, 0.3, 0.3);
    drawF();
    glPopMatrix();

    // Get value of model/view matrix, for later output
    GLdouble mvmat[16];  // For holding model/view matrix
    glGetDoublev(GL_MODELVIEW_MATRIX, mvmat);
    // Now mvmat holds the 4x4 model/view matrix, column-major order

    // Draw documentation
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);  // Set up simple ortho projection
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(0., double(winw), 0., double(winh));
    glColor3d(0., 0., 0.);        // Black text
    BitmapPrinter p(20., winh-20., 20.);
    p.print("Model/view matrix:");
    p.print("");

    // Output model/view matrix stored in mvmat
    for (int row = 0; row < 4; ++row)
    {
        ostringstream ostr;
        for (int col = 0; col < 4; ++col)
        {
            if (col > 0)
                ostr << "  ";
            ostr << setw(10) << mvmat[col*4 + row];
        }
        p.print(ostr.str());
    }

    p.print("");
    p.print("Try changing the model/view transformation in the code");
    p.print("(See function myDisplay)");
    p.print("");
    p.print("Esc      Quit");
    glPopMatrix();                // Restore prev projection
    glMatrixMode(GL_MODELVIEW);

    glutSwapBuffers();
}


// myIdle
// The GLUT idle function
void myIdle()
{
    // 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;
    }
}


// myReshape
// The GLUT reshape function
void myReshape(int w, int h)
{
    // Set viewport & save window dimensions in globals
    glViewport(0, 0, w, h);
    winw = w;
    winh = h;

    // Set up projection
    // Projection is orthographic. Aspect ratio is correct,
    // and region -1..1 in x & y always just fits in viewport.
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w > h)
    {
        gluOrtho2D(-double(w)/h, double(w)/h, -1., 1.);
    }
    else
    {
        gluOrtho2D(-1., 1., -double(h)/w, double(h)/w);
    }

    glMatrixMode(GL_MODELVIEW);  // Always go back to model/view mode
}


// init
// Initialize GL states & global data
// Called by main after window creation
void init()
{
    // OpenGL Stuff
    // (Nothing here)
}


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 - Display a Transformation Matrix");

    // Initialize GL states & register GLUT callbacks
    init();
    glutDisplayFunc(myDisplay);
    glutIdleFunc(myIdle);
    glutKeyboardFunc(myKeyboard);
    glutReshapeFunc(myReshape);

    // Do something
    glutMainLoop();

    return 0;
}

