// readfb.cpp
// Glenn G. Chappell
// 18 Sep 2012
//
// For CS 381 Fall 2012
// Advanced Picking Demo #1: Reading the Framebuffer
// Uses glReadPixels

// 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 <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)

// Color from framebuffer
GLubyte thecolor[3] = { 100, 100, 100 };
                               // Pixel color read from framebuffer
                               // Defaults to dark-ish gray
// Above is set by function readPixel. We use unsigned bytes to avoid
// problems with floating-point equality check.


// readPixel
// Reads pixel at mouse position (given, GLUT format)
// Puts color in thecolor (global GLfloat [3]).
// Assumes global winh holds height of window/viewport, in pixels.
void readPixel(int x, int y)
{
    glReadPixels(x, winh-y,         // Read pixel at mouse position
                 1, 1,              // 1 x 1 block (single pixel)
                 GL_RGB,            // Get R, G, B
                 GL_UNSIGNED_BYTE,  // Return GLubyte, value 0..255
                 thecolor);         // Place to put the result
}


// myDisplay
// The GLUT display function
void myDisplay()
{
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw a random-ish scene with lots of colors
    //  in the left half of the viewport
    glBegin(GL_TRIANGLES);
        glColor3ub(100, 50, 100);
        glVertex2d(-1.0,  0.0);
        glVertex2d(-0.05,-0.2);
        glVertex2d(-0.8, -0.7);

        glColor3ub(120, 160, 240);
        glVertex2d(-0.6,  0.8);
        glVertex2d(-0.7, -0.6);
        glVertex2d(-0.2, -1.0);

        glColor3ub(240, 50, 70);
        glVertex2d(-1.0,  0.4);
        glVertex2d(-0.2,  0.3);
        glVertex2d(-0.1,  0.6);

        glColor3ub(250, 200, 100);
        glVertex2d(-1.0, -0.9);
        glVertex2d(-0.9, -1.0);
        glVertex2d(-0.05, 0.1);

        glColor3ub(100, 200, 100);
        glVertex2d(-0.5, -1.0);
        glVertex2d(-0.1, -0.1);
        glVertex2d(-0.2, -0.9);

        glColor3ub(200, 120, 100);
        glVertex2d(-0.8,  0.3);
        glVertex2d(-0.9, -0.1);
        glVertex2d(-0.05, 0.2);

        glColor3ub(250, 160, 200);
        glVertex2d(-0.05, 0.5);
        glVertex2d(-0.4,  0.2);
        glVertex2d(-0.2, -0.5);
    glEnd();

    // Draw a solid color in the right half of the viewport
    glColor3ubv(thecolor);
    glBegin(GL_QUADS);
        glVertex2d(   0., -1000.);
        glVertex2d(1000., -1000.);
        glVertex2d(1000.,  1000.);
        glVertex2d(   0.,  1000.);
    glEnd();

    // 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.);
    ostringstream ostr;
    ostr << "Color: "
         << int(thecolor[0]) << " "
         << int(thecolor[1]) << " "
         << int(thecolor[2]);
    p.print(ostr.str());
    p.print("Click somewhere!");
    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
}


// myMouse
// The GLUT mouse function
void myMouse(int button, int state, int x, int y)
{
    // Read & store pixel color
    readPixel(x, y);
    glutPostRedisplay();
}


// myMotion
// The GLUT motion function
void myMotion(int x, int y)
{
    // Read & store pixel color
    readPixel(x, y);
    glutPostRedisplay();
}


// 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 - Picking Demo #2: Reading the Framebuffer");

    // Initialize GL states & register GLUT callbacks
    init();
    glutDisplayFunc(myDisplay);
    glutIdleFunc(myIdle);
    glutKeyboardFunc(myKeyboard);
    glutReshapeFunc(myReshape);
    glutMouseFunc(myMouse);
    glutMotionFunc(myMotion);

    // Do something
    glutMainLoop();

    return 0;
}

