// simpletex.cpp
// Glenn G. Chappell
// 10 Nov 2012
//
// For CS 381 Fall 2012
// Simple Texture-Using Application

// 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 "lib381/tshapes.h"
                         // For shape-drawing functions
#include <iomanip>
using std::setprecision;
using std::fixed;
#include <sstream>
using std::ostringstream;
#include <string>
using std::string;
#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)
bool rotateflag;               // True if object rotating

// Shaders
string vshader1fname;          // Filename for vertex shader source
string fshader1fname;          // Filename for fragment shader source
GLhandleARB prog1;             // GLSL Program Object

bool shaderbool1;              // Boolean to send to shaders
GLfloat shaderfloat1;          // Float to send to shaders

// Textures
const int NUM_TEXTURES = 1;
GLuint texnames[NUM_TEXTURES];
const int IMG_WIDTH = 8, IMG_HEIGHT = IMG_WIDTH;
GLubyte teximage[IMG_HEIGHT][IMG_WIDTH][3];  // Texture temp storage
   // The image
   // 3rd subscript 0 = R, 1 = G, 2 = B


// 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);
    glEnable(GL_DEPTH_TEST);

    // Camera transformation
    glLoadIdentity();
    glTranslated(0., 0., -4.);

    // Position light source 0
    // Also give spot direction
    glPushMatrix();
    glTranslated(0.0, 0.0, 1.0);
    glTranslated(-1., 1., 1.);
    GLfloat origin4[] = { 0.0f, 0.0f, 0.0f, 1.0f };
    glLightfv(GL_LIGHT0, GL_POSITION, origin4);
    GLfloat spotdir[] = { 1.0f, -1.0f, -1.0f };
    glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spotdir);
    glPopMatrix();

    // Make program object active
    glUseProgramObjectARB(prog1);

    // Send values to shaders
    GLint tanloc = -1;
    GLint bloc = glGetUniformLocationARB(prog1, "myb1");
    if (bloc != -1)
    {
        glUniform1i(bloc, shaderbool1);
    }
    GLint floc = glGetUniformLocationARB(prog1, "myf1");
    if (floc != -1)
    {
        glUniform1f(floc, shaderfloat1);
    }
    GLint tloc = glGetUniformLocationARB(prog1, "mytex0");
    if (tloc != -1)
    {
        glUniform1i(tloc, 0);   // Send texture channel
    }

    // Get location of tangent-vector attribute vec, if available
    tanloc = glGetAttribLocationARB(prog1, "vtangent_in");

    // Draw the object
    glRotated(rotangle, 1.,2.,0.);
    glColor3d(0.8, 0.4, 0.6);
    // draw torus, with varying subdivisions
    //glutSolidTorus(0.3, 1.0, 30, 100);
    // Above has no texture coordinates <sigh>
    drawTorus(0.3, 1.0, 30, 100, tanloc);

    // 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("R        Toggle object rotation");
    p.print(string("Space    Toggle shader boolean (") +
        (shaderbool1 ? "true" : "false") + ")");
    ostringstream os2;
    os2 << fixed << setprecision(2) << shaderfloat1;
    p.print("[ ]      Change shader float (" + os2.str() + ")");
    p.print("Esc      Quit");
    glPopMatrix();                // Restore prev projection
    glMatrixMode(GL_MODELVIEW);

    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

    // Rotate main object
    if (rotateflag)
    {
        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;
    case 'r':     // R: toggle object rotation
    case 'R':
        rotateflag = !rotateflag;
        break;
    case ' ':     // Space: toggle shader bool
        shaderbool1 = !shaderbool1;
        glutPostRedisplay();
        break;
    case '[':     // '[': Decrease shader float
        shaderfloat1 -= 0.02f;
        if (shaderfloat1 <= 0.0f)
            shaderfloat1 = 0.0f;
        glutPostRedisplay();
        break;
    case ']':     // ']': Increase shader float
        shaderfloat1 += 0.02f;
        if (shaderfloat1 >= 1.0f)
            shaderfloat1 = 1.0f;
        glutPostRedisplay();
        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.5, 100.);

    glMatrixMode(GL_MODELVIEW);  // Always go back to model/view mode
}


// makeTexImage
// Make texture image in global teximage
// using given color (components 0..255)
void makeTexImage(GLubyte r, GLubyte g, GLubyte b)
{
    string lines[8] = {
        "--------",
        "--####--",
        "-#----#-",
        "-#------",
        "-#--###-",
        "-#----#-",
        "--####--",
        "--------"
    };

    for (int i = 0; i < IMG_HEIGHT; ++i)
    {
        for (int j = 0; j < IMG_WIDTH; ++j)
        {
            double coeff = (lines[i][j] == '#' ? 0.4 : 1.0);
            teximage[IMG_HEIGHT-1-i][j][0] = GLubyte(coeff*r);
            teximage[IMG_HEIGHT-1-i][j][1] = GLubyte(coeff*g);
            teximage[IMG_HEIGHT-1-i][j][2] = GLubyte(coeff*b);
        }
    }
}


// init
// Initialize GL states & global data
// Called by main after window creation
void init()
{
    // Objects
    savetime = glutGet(GLUT_ELAPSED_TIME)/1000.;
    rotangle = 0.;
    rotateflag = false;

    shaderbool1 = true;
    shaderfloat1 = 1.0;

    // OpenGL Stuff

    // Shaders
    prog1 = makeProgramObjectFromFiles(vshader1fname, fshader1fname);

    // Textures
    glGenTextures(NUM_TEXTURES, texnames);

    // Make Texture 0
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texnames[0]);

    makeTexImage(64, 192, 192);
    gluBuild2DMipmaps(GL_TEXTURE_2D,
        GL_RGBA,
        IMG_WIDTH, IMG_HEIGHT,
        GL_RGB,
        GL_UNSIGNED_BYTE,
        &teximage[0][0][0]);
    glTexParameteri(GL_TEXTURE_2D,
        GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D,
        GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,
        GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D,
        GL_TEXTURE_WRAP_T, GL_REPEAT);
}


int main(int argc, char ** argv)
{
    // Initialize OpenGL/GLUT
    glutInit(&argc, argv);
    getShaderFilenames(vshader1fname, fshader1fname, argc, argv);
        // Set shader source filenames. Done here, as opposed to in
        //  function init, so that we can use command-line arguments.
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

    // Make a window
    glutInitWindowSize(startwinsize, startwinsize);
    glutInitWindowPosition(50, 50);
    glutCreateWindow("CS 381 - Simple Texture-Using Application");

    // 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;
}

