#define GL_GLEXT_PROTOTYPES 1

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

// Drawing 1 triangle was the hard part. 
// Here we define the number of rows and
// columns in a 2 dimensional array of triangles to draw.
const int NROWS    = 32;
const int NCOLUMNS = 16;

struct State {
    GLhandleARB shaderProgram;
    GLint       vertexLocation;
    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()
{
    glClearColor(0.2f, 0.2f, 0.2f, 1.0f) ;

    state.shaderProgram = shaderProgramBuild(vertexShaderSource, fragmentShaderSource);

    glUseProgram(state.shaderProgram);
    state.vertexLocation = glGetAttribLocation(state.shaderProgram, "vertex");
    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);
    }

    // use a std::vector to store the each triangle in the ROWS x COLUMNS 
    // array of triangles. Its worth noting that it may very well be inconvenient
    // to use a coordinate system for our triangles that ranges from [-1,1] in the
    // x direction and [-1,1] in the y direction. It is also worth noting that I
    // haven't explained why the -1 for the 'z' coordinate. Both of these questions
    // should be answered in a future example.
    //
    // For now, we focus on showing that many triangles are as easy as one.

    std::vector<GLfloat> data;

    float r = std::min(1.0/float(NCOLUMNS), 1.0/float(NROWS));
    unsigned int count = 0;

    for (int row = 0; row < NROWS; ++row) {

        for (int col = 0; col < NCOLUMNS; ++col) {

            // interpolate an x,y position on the screen in our increasingly inconvenient
            // [-1,1] coordinate system. An interpolate an angle theta to rotate the triangle
            float xshift  = -1.0 + r + (float(col)/float(NCOLUMNS-1))*(2.0-2.0*r);
            float yshift  = -1.0 + r + (float(row)/float(NROWS-1))*(2.0-2.0*r);
            float theta   = 2.0*M_PI*float(count)/float(NCOLUMNS*NROWS-1);

            ++count;

            // push back the 3 vertices for the triangle
            data.push_back( xshift );
            data.push_back( yshift );
            data.push_back( -1.0 );

            data.push_back( xshift + r*cos(theta) );
            data.push_back( yshift + r*sin(theta) );
            data.push_back( -1.0 );

            data.push_back( xshift + r*cos(theta + M_PI/2.0) );
            data.push_back( yshift + r*sin(theta + M_PI/2.0) );
            data.push_back( -1.0 );
        }
    }


    // as before, hand the data to OpenGL
   glGenBuffers(1, &state.bufferID);
   glBindBuffer(GL_ARRAY_BUFFER, state.bufferID);
   glBufferData(GL_ARRAY_BUFFER, data.size()*sizeof(GLfloat), &data[0], GL_STATIC_DRAW);

   glFinish();
}

void
display()
{
    static bool doInitialize = true;

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

    glClear(GL_COLOR_BUFFER_BIT) ;

    glUseProgram(state.shaderProgram);

    glBindBuffer(GL_ARRAY_BUFFER, state.bufferID);

    glVertexAttribPointer(state.vertexLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);

    glEnableVertexAttribArray(state.vertexLocation);

        // DRAW DRAW DRAW !!!
        glDrawArrays(GL_TRIANGLES, 0, NCOLUMNS*NROWS*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-04");

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

    glutMainLoop() ;

    return(0) ;
}