/*
 * @topic T0L063 Vertex array objects (VAO) and Vertex buffer objects (VBO)
 * @brief OpenGL client program
*/

#include <GL/glew.h>
#include <GL/freeglut.h>
#include <stdio.h>

// main_Lecture06.cpp
// shader path configuration:
#define    BCC_PROJECT_NAME "Lecture06_Buffers"
#define    BCC_ROOT_PATH "../../example/"
#define    BCC_VERTEX_SHADER BCC_ROOT_PATH BCC_PROJECT_NAME "/Shaders/vertexProgram_v.c"
#define    BCC_FRAGMENT_SHADER  BCC_ROOT_PATH BCC_PROJECT_NAME "/Shaders/fragmentProgram_f.c"

#define BUFFER_OFFSET(i) ((char *)NULL + (i))
GLuint shader_program_ID;
GLuint vao = 0;
GLuint vbo;
GLuint position_ID, colorID;

// Begin shader functions
static char* load_text_file(const char* filename) {
    // Open the file
    FILE* fp = fopen (filename, "r");
    // Move the file pointer to the end of the file and determing the length
    fseek(fp, 0, SEEK_END);
    long file_length = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    char* contents = new char[file_length+1];
    // zero out memory
    for (int i = 0; i < file_length+1; i++) {
        contents[i] = 0;
    }
    // Here's the actual read
    fread (contents, 1, file_length, fp);
    // This is how you denote the end of a string in C
    contents[file_length] = '\0';
    fclose(fp);
    return contents;
}

bool is_shader_compiled_okay(GLint shader_ID){
    GLint compiled = 0;
    glGetShaderiv(shader_ID, GL_COMPILE_STATUS, &compiled);
    if (compiled) {
        return true;
    }
    else {
        GLint log_length;
        glGetShaderiv(shader_ID, GL_INFO_LOG_LENGTH, &log_length);
        char* msg_buffer = new char[log_length];
        glGetShaderInfoLog(shader_ID, log_length, NULL, msg_buffer);
        printf ("%s\n", msg_buffer);
        delete[] (msg_buffer);
        return false;
    }
}

GLuint compile_vertex_shader(const char* shader_source) {
    GLuint vertex_shader_ID = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource (vertex_shader_ID, 1, (const GLchar**)&shader_source, NULL);
    glCompileShader(vertex_shader_ID);
    bool compiled_correctly = is_shader_compiled_okay(vertex_shader_ID);
    if (compiled_correctly) {
        return vertex_shader_ID;
    }
    return -1;
}

GLuint compile_fragment_shader(const char* shader_source) {
    GLuint fragment_shader_ID = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader_ID, 1, (const GLchar**)&shader_source, NULL);
    glCompileShader(fragment_shader_ID);
    bool compiled_correctly = is_shader_compiled_okay(fragment_shader_ID);
    if (compiled_correctly) {
        return fragment_shader_ID;
    }
    return -1;
}

GLuint link_shader_program (GLuint vertex_shader_ID, GLuint fragment_shader_ID) {
    GLuint shader_ID = glCreateProgram();
    glAttachShader(shader_ID, vertex_shader_ID);
    glAttachShader(shader_ID, fragment_shader_ID);
    glLinkProgram(shader_ID);
    return shader_ID;
}
// End shader functions

// Any time the window is resized, this function gets called. Set by the 
// "glutReshapeFunc" in main.
void change_viewport(int w, int h){
    glViewport(0, 0, w, h);
}

// Here is the function that gets called each time the window needs to be redrawn.
// It is the "paint" function for our program, and is set up from the glutDisplayFunc in main
void render() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    glutSwapBuffers();
}

int main (int argc, char** argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH);
    glutInitWindowSize(800, 600);
    glutCreateWindow("Shaders");
    glutReshapeFunc(change_viewport);
    glutDisplayFunc(render);
    glewInit();

    // Vertices and colors of a triangle 
    // notice, position values are between -1.0f and +1.0f
    GLfloat vertices[] = {-0.5f, -0.5f, 0.0f,    // Lower-left
                           0.5f, -0.5f, 0.0f,    // Lower-right
                           0.0f,  0.5f, 0.0f};   // Top
    GLfloat colors[] = {1.0f, 0.0f, 0.0f, 1.0f,  // red
                        0.0f, 1.0f, 0.0f, 1.0f,  // green
                        0.0f, 0.0f, 1.0f, 1.0f}; // blue

    // Make a shader
    char* cstr_vertex_shader_source = load_text_file( BCC_VERTEX_SHADER );
    char* cstr_fragment_shader_source = load_text_file( BCC_FRAGMENT_SHADER );
    GLuint vert_shader_ID = compile_vertex_shader(cstr_vertex_shader_source);
    GLuint frag_shader_ID = compile_fragment_shader(cstr_fragment_shader_source);
    shader_program_ID = link_shader_program(vert_shader_ID, frag_shader_ID);
    
    printf ("vert_shader_ID is %d\n", vert_shader_ID);
    printf ("frag_shader_ID is %d\n", frag_shader_ID);
    printf ("shader_program_ID is %d\n", shader_program_ID);
    printf ("s_vPosition's ID is %d\n", position_ID);

    // Generate vertex array object names
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    // Create the buffer, but don't load anything yet
    GLuint buffer_size = ( 3 * 3 + 3 * 4 ) * sizeof(GLfloat);
    //                     |   |   |   |
    //                     |   |   |   `--> # of color components
    //                     |   |   `------> # of vertices
    //                     |   `----------> # of vertex components
    //                     `--------------> # of vertices
    // Allocate array buffer:
    glBufferData(GL_ARRAY_BUFFER, buffer_size, NULL, GL_STATIC_DRAW);

    GLuint offset = 0;
    GLuint vertex_bytes = 3 * 3 * sizeof(GLfloat);
    // Load the vertex points
    glBufferSubData(GL_ARRAY_BUFFER, offset, vertex_bytes, vertices); 

    offset += vertex_bytes;
    GLuint color_bytes = 3 * 4 * sizeof(GLfloat);
    // Load the colors right after that
    glBufferSubData(GL_ARRAY_BUFFER, offset, color_bytes, colors);
    
    // Find the position of the variables in the shader
    position_ID = glGetAttribLocation(shader_program_ID, "s_vPosition");
    colorID = glGetAttribLocation(shader_program_ID, "s_vColor");

    glVertexAttribPointer(position_ID, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glVertexAttribPointer(colorID, 4, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(offset));
    glUseProgram(shader_program_ID);
    glEnableVertexAttribArray(position_ID);
    glEnableVertexAttribArray(colorID);
    
    glutMainLoop();
    
    return 0;
}