#define GL_GLEXT_PROTOTYPES 1

#include <GL/glut.h> // this includes <GL/gl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// This program builds on the previous program, and finally
// draws a triangle. And once we have one triangle, we can
// have a million.
struct State {
    GLhandleARB shaderProgram;
    // the location of the uniform variable 'vertex' in our shader program.
    GLint       vertexLocation;
    // the OpenGL name, or ID, or handle, to the buffer full of vertex data
    GLuint      bufferID;
};
State state;

GLchar vertexShaderSource[] = "\n\
#version 150\n\
in vec3 vertex;\n\
void\n\
main()\n\
{\n\
    gl_Position = vec4(vertex,1.0);\n\
}\n\
";

GLchar fragmentShaderSource[] = "\n\
#version 150\n\
out vec4 fragmentColor;\n\
void\n\
main()\n\
{\n\
    fragmentColor = vec4(0.0, 1.0, 0.0, 1.0);\n\
}\n\
";

GLhandleARB
shaderCompile(const GLchar *text, GLenum type)
{
    GLhandleARB shaderHandle;
    GLint       status;
    GLint       loglen;
    GLint       maxloglen;
    GLchar     *log = 0;


   shaderHandle = glCreateShader(type);

    if (shaderHandle == 0) {
        fprintf(stderr,"glCreateShader() failed.\n");
        fprintf(stderr,"Possibly outside an OpenGL context. Possible out of resources.\n");
        exit(1);
    }

    glShaderSource(shaderHandle, 1, &text, NULL);
    glutReportErrors();

    glCompileShader(shaderHandle);
    glutReportErrors();

    glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &status);

    if (status != 0) {
        return shaderHandle;
    }

    // log length includes the c string null terminator
    glGetShaderiv(shaderHandle, GL_INFO_LOG_LENGTH, &maxloglen);
    log = new GLchar[maxloglen];

    glGetShaderInfoLog(shaderHandle, maxloglen, &loglen, log);

    fprintf(stderr,"compile failed.\n");
    fprintf(stderr,"shader text:\n");
    fprintf(stderr,"------------\n");
    fprintf(stderr,"%s\n",text);
    fprintf(stderr,"log text:\n");
    fprintf(stderr,"------------\n");
    fprintf(stderr,"%s\n",log);

    delete [] log;
    exit(1);
}

GLhandleARB
shaderProgramBuild(const GLchar *vertex, const GLchar *fragment)
{
    GLhandleARB programHandle;
    GLint       status;
    GLint       loglen;
    GLint       maxloglen;
    GLchar     *log = 0;

    programHandle = glCreateProgram();

    if (programHandle < 1) {
        fprintf(stderr,"glCreateShader() failed.\n");
        exit(1);
    }

    glutReportErrors();

    glAttachShader(programHandle, shaderCompile(vertex,GL_VERTEX_SHADER));
    glAttachShader(programHandle, shaderCompile(fragment,GL_FRAGMENT_SHADER));
    glutReportErrors();

    glLinkProgram(programHandle);
    glutReportErrors();

    glGetProgramiv(programHandle, GL_LINK_STATUS, &status);

    if (status != 0) {
        return programHandle;
    }

    glGetProgramiv(programHandle, GL_INFO_LOG_LENGTH, &maxloglen);
    log = new GLchar[maxloglen];

    glGetProgramInfoLog(programHandle, maxloglen, &loglen, log);

    fprintf(stderr,"link failed.\n");
    fprintf(stderr,"log text:\n");
    fprintf(stderr,"------------\n");
    fprintf(stderr,"%s\n",log);

    delete [] log;
    exit(1);
}

void
initialize()
{
    // this function is where OpenGL initialization will occur.


    // changing the color to dark grey. There is actually a reason to choose grey over black.
    // Occasionally, we may have primitives that are incorrectly or completely unlit, which may
    // end up being drawn as black. They would be invisible on a black background. So I chose grey. 
    glClearColor(0.2f, 0.2f, 0.2f, 1.0f) ;

    state.shaderProgram = shaderProgramBuild(vertexShaderSource, fragmentShaderSource);

    // make the shader program active
    glUseProgram(state.shaderProgram);

    // now that the shader program is activated, get the location of the vertex
    // uniform variable. 
    state.vertexLocation = glGetAttribLocation(state.shaderProgram, "vertex");

    // deactivate the shader program
    glUseProgram(0);

    // make sure we were able to successfully get the location.
    if (state.vertexLocation < 0) {
        fprintf(stderr,"unable to get the location of 'vertex'\n");
        exit(1);
    }

    // we're using this array of floats to define a single triangle
    // for OpenGL to display. 3 vertices per triangle, 3 floats per vertex,
    // hence the size is 9
    GLfloat triangle_data[9];

   triangle_data[0] = -0.9;
   triangle_data[1] = -0.9;
   triangle_data[2] = -1;

   triangle_data[3] =  0.9;
   triangle_data[4] = -0.9;
   triangle_data[5] = -1;

   triangle_data[6] =  0.9;
   triangle_data[7] =  0.9;
   triangle_data[8] = -1;

   // ask OpenGL to generate an identifier, or name, or handle for a new buffer
   // of data
   glGenBuffers(1, &state.bufferID);
   // bind that handle as a GL_ARRAY_BUFFER.
   glBindBuffer(GL_ARRAY_BUFFER, state.bufferID);
   // define the "shape" of the buffer, that is, how bit it is 9*sizeof(GLfloat)
   // and hand the triangle_data to OpenGL.
   glBufferData(GL_ARRAY_BUFFER, 9*sizeof(GLfloat), triangle_data, GL_STATIC_DRAW);

   // block until all previously called gl commands have completed
   // since we're passing a local, we'll want to be sure glBufferData() has completed
   // before the local variable 'triangle_data' disappears.
   // After glBufferData completes, OpenGL will have a copy on the GPU.
   glFinish();
}

void
display()
{
    static bool doInitialize = true;

    if (doInitialize == true) {
        doInitialize = false;
        initialize();
    }

    glClear(GL_COLOR_BUFFER_BIT) ;

    glUseProgram(state.shaderProgram);

    // bind the vertex data we created for use
    glBindBuffer(GL_ARRAY_BUFFER, state.bufferID);

    // connect the buffer we just bound to the shader via the location of the uniform variable 'vertex'
    // which is stored in state.vertexLocation
    // The last '0' in the parameter list indicates use the currently bound buffer
    glVertexAttribPointer(state.vertexLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);

    // enable the array of data
    glEnableVertexAttribArray(state.vertexLocation);

    // DRAW DRAW DRAW !!!
    glDrawArrays(GL_TRIANGLES, 0, 3);

    glUseProgram(0);

    glFlush();

    glutReportErrors();

    return ;
}

void
keyboard(unsigned char key, int x, int y)
{
    switch (key) {
        // exit if the user hits the escape key or the 'q' key
        case 27:
        case 'q':
            exit(0);
            break;

        default:
            // print data to stderr so we know a key had been hit
            fprintf(stderr,"key %d hit. mouse at %d %d\n",key,x,y);
            break;
    };
}

void
mouse(int button, int state, int x, int y)
{
    switch (button) {

        case GLUT_LEFT_BUTTON:
            if (state == GLUT_DOWN) {
                fprintf(stderr,"left mouse button pressed\n");
            } else if (state == GLUT_UP) {
                fprintf(stderr,"left mouse button released\n");
            }
            break;

        case GLUT_MIDDLE_BUTTON:
            break;

        case GLUT_RIGHT_BUTTON:
            break;
    };
}

void
reshape(int width, int height)
{
    glViewport(0, 0, width, height);
}

int main(int argc, char **argv)
{
    glutInit( &argc, argv);
    glutInitWindowSize(800,600);
    glutInitDisplayMode(GLUT_RGB|GLUT_SINGLE);

    glutCreateWindow("example-03");

    glutDisplayFunc( display );
    glutKeyboardFunc( keyboard );
    glutMouseFunc( mouse );
    glutReshapeFunc( reshape );

    glutMainLoop() ;

    return(0) ;
}