// tryglsl.cpp
// Glenn G. Chappell
// 24 Sep 2012
//
// For CS 381 Fall 2012
// Check that GLSL works
// Used in Assignment 3, Exercise A

// *********************************************************************
// This is NOT recommended as a starting point for other programs
// *********************************************************************

// OpenGL/GLUT includes - DO THESE FIRST
#include <cstdlib>       // Do this before GL/GLUT includes
using std::exit;
#ifndef __APPLE__
# include <GL/glew.h>
# include <GL/glut.h>    // GLUT stuff, includes OpenGL headers as well
#else
# include <GLEW/glew.h>
# include <GLUT/glut.h>  // Apple puts glut.h in a different place
#endif

// Other includes
#include "lib381/bitmapprinter.h"
                         // For class BitmapPrinter
#include "lib381/glslprog.h"
                         // For GLSL code-handling functions
#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)

// Objects
double savetime;               // Time of previous movement (sec)
double rotangle;               // Rotation angle for object (deg)
const double rotspeed = 40.;   // Rotation speed (deg/sec)

// Shaders
GLhandleARB prog1;             // GLSL Program Object


// drawColoredCube
// Draw a cube, each face a different color,
//  centered at origin, vertices at x,y,z = +/- 1.
// Given location for attribute vec4 in shaders;
//  sends drawing color to this.
void drawColoredCube(GLint vloc)
{
    glBegin(GL_QUADS);

        glVertexAttrib4dARB(vloc, 1.,0.,0.,0.);
        glVertex3d( 1.,-1.,-1.);
        glVertex3d( 1.,-1., 1.);
        glVertex3d( 1., 1., 1.);
        glVertex3d( 1., 1.,-1.);

        glVertexAttrib4dARB(vloc, 0.,1.,1.,0.);
        glVertex3d(-1.,-1.,-1.);
        glVertex3d(-1., 1.,-1.);
        glVertex3d(-1., 1., 1.);
        glVertex3d(-1.,-1., 1.);

        glVertexAttrib4dARB(vloc, 0.,1.,0.,0.);
        glVertex3d(-1., 1.,-1.);
        glVertex3d( 1., 1.,-1.);
        glVertex3d( 1., 1., 1.);
        glVertex3d(-1., 1., 1.);

        glVertexAttrib4dARB(vloc, 1.,0.,1.,0.);
        glVertex3d(-1.,-1.,-1.);
        glVertex3d(-1.,-1., 1.);
        glVertex3d( 1.,-1., 1.);
        glVertex3d( 1.,-1.,-1.);

        glVertexAttrib4dARB(vloc, 0.,0.,1.,0.);
        glVertex3d(-1.,-1., 1.);
        glVertex3d( 1.,-1., 1.);
        glVertex3d( 1., 1., 1.);
        glVertex3d(-1., 1., 1.);

        glVertexAttrib4dARB(vloc, 1.,1.,0.,0.);
        glVertex3d(-1.,-1.,-1.);
        glVertex3d(-1., 1.,-1.);
        glVertex3d( 1., 1.,-1.);
        glVertex3d( 1.,-1.,-1.);

    glEnd();
}


// myDisplay
// The GLUT display function
void myDisplay()
{
    glClearColor(0.7f, 0.7f, 0.7f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Turn on shaders & get shader variable locations
    glUseProgramObjectARB(prog1); // Use shaders
    GLint vloc = glGetAttribLocationARB(prog1, "thecolor");

    // Draw object, visible only if shaders work
    glColor3d(0.7, 0.7, 0.7);     // = backgnd color; shaders do not use
    glLoadIdentity();
    glTranslated(0., 0., -4.);
    glRotated(rotangle, 0.,1.,0.);
    glRotated(70., 1., 2., 3.);
    drawColoredCube(vloc);

    // Draw documentation
    glUseProgramObjectARB(0);     // No shaders
    glDisable(GL_DEPTH_TEST);
    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("Are shaders working? What do you see?");
    p.print("");
    p.print("Esc      Quit");
    glPopMatrix();                // Restore prev projection
    glMatrixMode(GL_MODELVIEW);
    glEnable(GL_DEPTH_TEST);

    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
    rotangle += rotspeed * 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;
    }
}


// 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
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60., double(w)/h, 0.1, 10.);

    glMatrixMode(GL_MODELVIEW);  // Always go back to model/view mode
}


// init
// Initializes GL states
// Called by main after window creation
void init()
{
    // Objects
    savetime = glutGet(GLUT_ELAPSED_TIME)/1000.;
    rotangle = 0.;

    // OpenGL Stuff

    // Shaders

    // *****************************************************************
    // Please do NOT write your shaders this way
    // Put shader source code in separate files
    // *****************************************************************

    prog1 = makeProgramObject(
        "// GLSL Vertex Shader\n"
        "attribute vec4 thecolor;\n"
        "varying vec3 myobjpos;\n"
        "void main()\n"
        "{\n"
        "    myobjpos = gl_Vertex.xyz / gl_Vertex.w;\n"
        "    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n"
        "    gl_FrontColor = thecolor;\n"
        "}\n",

        "// GLSL Fragment Shader\n"
        "varying vec3 myobjpos;\n"
        "void main()\n"
        "{\n"
        "    vec3 scaledpos = abs(myobjpos * 3.0);\n"
        "    vec3 iscaledpos = vec3(ivec3(scaledpos + 0.5));\n"
        "    if (distance(scaledpos, iscaledpos) < 0.4) discard;\n"
        "    gl_FragColor = gl_Color;\n"
        "}\n"
    );
}


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 381 - Check That GLSL Works");

    // Init GLEW & check status
    if (glewInit() != GLEW_OK)
    {
        cerr << "glewInit failed" << endl;
        exit(1);
    }

    // Initialize GL states & register GLUT callbacks
    init();
    glutDisplayFunc(myDisplay);
    glutIdleFunc(myIdle);
    glutKeyboardFunc(myKeyboard);
    glutReshapeFunc(myReshape);

    // Do something
    glutMainLoop();

    return 0;
}

