#define GL_GLEXT_PROTOTYPES 1

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

// adjusted the number of triangles for no particular reason
const int NROWS    = 24;
const int NCOLUMNS = 24;

// In this version, we're using one vertex buffer
// and packing in both vertex data and color data
//
// We still have two bind points in the shader
// one for vertices, located at vertexLocation
// and one for colors, located at colorLocation
// There is now only 1 vertex buffer, identified by dataBufferID
struct State {
    GLhandleARB shaderProgram;
    GLuint      dataBufferID;
    GLint       vertexLocation;
    GLint       colorLocation;
};
State state;

GLchar vertexShaderSource[] = "\n\
#version 150\n\
in vec3 vertex;   // the bind point for an input vertex\n\
in vec3 color;    // the bind point for an input color\n\
out vec4 mycolor; // the output color, transmitted to the fragment shader \n\
void\n\
main()\n\
{\n\
    gl_Position = vec4(vertex,1.0);\n\
    mycolor = vec4(color, 1.0); // not transforming the color, just passing it to the fragment shader\n\
}\n\
";

GLchar fragmentShaderSource[] = "\n\
#version 150\n\
in vec4 mycolor; // the input color from the vertex shader\n\
out vec4 fragmentColor;\n\
void\n\
main()\n\
{\n\
    fragmentColor = mycolor; // just sending the input color to the screen\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);
}

// this is a small function whose input is nanometers (nm)
// and whose output is an RGB value. Its a simple way to get
// a color spectrum.
void
color(float nm, float * r, float * g, float * b)
{
    float violet_nm = 400.0;
    float indigo_nm = 445.0; // hold your horses. I know this is controversal.
    float blue_nm   = 475.0;
    float green_nm  = 510.0;
    float yellow_nm = 570.0;
    float orange_nm = 590.0;
    float red_nm    = 650.0;

    float violet[3] = {238.0/255.0, 130.0/255.0, 238.0/255.0};
    float indigo[3] = {75.0/255.0, 0.0/255.0, 130.0/255.0};
    float blue[3]   = {0.0, 0.0, 1.0};
    float green[3]  = {0.0, 1.0, 0.0};
    float yellow[3] = {1.0, 1.0, 0.0};
    float orange[3] = {255.0/255.0, 165.0/255.0, 0.0/255.0};
    float red[3]    = {1.0, 0.0, 0.0};

    if (nm <= violet_nm) {
        *r = violet[0];
        *g = violet[1];
        *b = violet[2];

    } else if (nm <= indigo_nm) {
        float t = (nm-violet_nm)/(indigo_nm-violet_nm);

        *r = violet[0]*(1.0-t) + indigo[0]*t;
        *g = violet[1]*(1.0-t) + indigo[1]*t;
        *b = violet[2]*(1.0-t) + indigo[2]*t;

    } else if (nm <= blue_nm) {
        float t = (nm-indigo_nm)/(blue_nm-indigo_nm);

        *r = indigo[0]*(1.0-t) + blue[0]*t;
        *g = indigo[1]*(1.0-t) + blue[1]*t;
        *b = indigo[2]*(1.0-t) + blue[2]*t;

    } else if (nm <= green_nm) {
        float t = (nm-blue_nm)/(green_nm-blue_nm);

        *r = blue[0]*(1.0-t) + green[0]*t;
        *g = blue[1]*(1.0-t) + green[1]*t;
        *b = blue[2]*(1.0-t) + green[2]*t;

    } else if (nm <= yellow_nm) {
        float t = (nm-green_nm)/(yellow_nm-green_nm);

        *r = green[0]*(1.0-t) + yellow[0]*t;
        *g = green[1]*(1.0-t) + yellow[1]*t;
        *b = green[2]*(1.0-t) + yellow[2]*t;

    } else if (nm <= orange_nm) {
        float t = (nm-yellow_nm)/(orange_nm-yellow_nm);

        *r = yellow[0]*(1.0-t) + orange[0]*t;
        *g = yellow[1]*(1.0-t) + orange[1]*t;
        *b = yellow[2]*(1.0-t) + orange[2]*t;

    } else if (nm <= red_nm) {
        float t = (nm-orange_nm)/(red_nm-orange_nm);

        *r = orange[0]*(1.0-t) + red[0]*t;
        *g = orange[1]*(1.0-t) + red[1]*t;
        *b = orange[2]*(1.0-t) + red[2]*t;

    } else {
        *r = red[0];
        *g = red[1];
        *b = red[2];
    }
}

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");
    state.colorLocation  = glGetAttribLocation(state.shaderProgram, "color"); // get the location of the color shader input
    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);
    }

    // now using one vector to store the vertex data and the color data
    // to be uploaded to the graphics card
    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) {

            // find the color in nanometers
            // this time columnwise
            //float nm = 400.0 + (650.0-400.0)*float(count)/float(NCOLUMNS*NROWS-1);
            float nm = 400.0 + (650.0-400.0)*float(col)/float(NCOLUMNS-1);
            float red,green,blue;
            color(nm, &red, &green, &blue);

            // 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: color / vertex / color / vertex / color / vertex
            // This pattern may seem arbitrary, but earlier versions of the OpenGL
            // standard used a pattern like this, so I use it here.

            // color
            data.push_back(red);
            data.push_back(green);
            data.push_back(blue);

            // vertex position
            data.push_back( xshift );
            data.push_back( yshift );
            data.push_back( -1.0 );

            // color
            data.push_back(red);
            data.push_back(green);
            data.push_back(blue);

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

            // color
            data.push_back(red);
            data.push_back(green);
            data.push_back(blue);

            // vertex position
            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 );
        }
    }

    // generate and fill the vertex buffer from data
    glGenBuffers(1, &state.dataBufferID);
    glBindBuffer(GL_ARRAY_BUFFER, state.dataBufferID);
    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);

    // bind the vertex buffer for drawing
    glBindBuffer(GL_ARRAY_BUFFER, state.dataBufferID);

    // There are 6 floats per vertex: 
    //      3 floats for the color
    //      3 floats for the vertex position
    //      Hence the stride, in bytes, is 6*sizeof(GLfloat)
    // The colors are first in the array, so we're starting at byte 0 in the array
    glVertexAttribPointer(state.colorLocation, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), 0);

    // Again, 6 floats per vertex: 
    //      3 floats for the color
    //      3 floats for the vertex position
    //      Hence the stride, in bytes, is 6*sizeof(GLfloat)
    // The vertices are second in the array, so we're starting 3 floats, or 3*sizeof(GLfloat) bytes, into the array
    glVertexAttribPointer(state.vertexLocation, 3, GL_FLOAT, GL_FALSE,
            6*sizeof(GLfloat), reinterpret_cast<GLvoid *>(3*sizeof(GLfloat)));

    // enable both buffers
    glEnableVertexAttribArray(state.colorLocation);
    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-06");

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

    glutMainLoop() ;

    return(0) ;
}