#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <SDL2/SDL.h>
#include <fstream>
#include <stdexcept>
#include <dirent.h>
#include "loaders.h"

int main(int argc, char* args[]) {
    GLFWwindow* window;
    GLuint texBG;
    int textw, texth;
    int textw2, texth2;


    /* Initialize the library */

    /* GLFW */
    if (!glfwInit()) {
        printf("GLFW Error");
        return 1;
    }
    /* Create a windowed mode window and its OpenGL context */
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if (!window) {
        glfwTerminate();
        return 1;
    }
    /* Make the window's context current */
    glfwMakeContextCurrent(window);

    /* GLEW */
    glewExperimental = GL_FALSE;
    GLenum error = glGetError();
    if (error != GL_NO_ERROR)
    {
        printf("OpenGL Error: %d",error);
    }
    GLenum glewinit = glewInit();
    if (glewinit != GLEW_OK) {
        printf("Glew not okay! %d",glewinit);
        return 1;
    }

    /* SDL */
    if (InitLoaders() < 0) {
        printf("SDL Error");
        return 1;
    }
    // INIT






    // An array of 3 vectors which represents 3 vertices
    static const GLfloat g_vertex_buffer_data[] = {
       -1.0f, -1.0f, 0.0f,  // (bottom - left )  TRIANGLE 1
        1.0f, -1.0f, 0.0f,  // (bottom - right)
       -1.0f,  1.0f, 0.0f,  // (top    - left )
        1.0f,  1.0f, 0.0f,  // (top    - right)  TRIANGLE 2
        1.0f, -1.0f, 0.0f,  // (bottom - right)
       -1.0f,  1.0f, 0.0f,  // (top    - left )
    };
    // One color for each vertex. They were generated randomly.
    static const GLfloat g_uv_buffer_data[] = {
        0.0f, 1.0f-0.0f,         // (bottom - left )  TRIANGLE 1
        1.0f, 1.0f-0.0f,         // (bottom - right)
        0.0f, 1.0f-1.0f,         // (top    - left )
        1.0f, 1.0f-1.0f,         // (top    - right)  TRIANGLE 2
        1.0f, 1.0f-0.0f,         // (bottom - right)
        0.0f, 1.0f-1.0f,         // (top    - left )
    };


    // Identify our vertex buffer
    GLuint vertexbuffer;
    glGenBuffers(1, &vertexbuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);

    // Identify our texture coordinate buffer
    GLuint colorbuffer;
    glGenBuffers(1, &colorbuffer);
    glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(g_uv_buffer_data), g_uv_buffer_data, GL_STATIC_DRAW);

    // The framebuffer, which regroups one texture, and zero depth buffer.
    GLuint FramebufferName;
    glGenFramebuffers(1, &FramebufferName);
    glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);

    // load foreground and background textures
    int lenTexFG = 10;
    GLint texFGs[lenTexFG];
    char buffer[50];
    texBG = LoadTexture("../img/backgrounds/sky.jpg",&textw2,&texth2);
    for (int i=0; i<lenTexFG; i++) {
        snprintf(buffer, sizeof(buffer), "../img/foregrounds/surfing/%d.jpg", i);
        texFGs[i] = LoadTexture(buffer,&textw,&texth);
    }

    // Generate texture for frame buffer
    GLuint renderedTexture;
    glGenTextures(1, &renderedTexture);
    glBindTexture(GL_TEXTURE_2D, renderedTexture);
    // Load texture and set filtering
    glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, textw, texth, 0,GL_RGB, GL_UNSIGNED_BYTE, 0);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

    // Set "renderedTexture" as our color attachment #2 (render to location 2)
    glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, renderedTexture, 0);
    // Set the list of draw buffers
    GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT2};
    glDrawBuffers(1, DrawBuffers); // "1" is the size of DrawBuffers

    // Always check that our frame buffer is ok
    if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        return false;

    // Set window size to texture size (window size, opengl context size, resize constraints)
    glfwSetWindowSize(window, textw, texth);
    glViewport(0, 0, textw, texth);
    glfwSetWindowSizeLimits(window, textw, texth, textw, texth);

    // Load shader programs
    GLuint progTrimapID = LoadShaders("trimap.vertexshader", "trimap.fragmentshader");
    GLuint progRefineID = LoadShaders("refinement.vertexshader", "refinement.fragmentshader");

    // Get shader locations for trimap shader
    GLint locTriTexForeground = glGetUniformLocation(progTrimapID, "foreground");
    GLint locTriPixWidth = glGetUniformLocation(progTrimapID, "pixWidth");
    GLint locTriPixHeight = glGetUniformLocation(progTrimapID, "pixHeight");

    // Get shader locations for refinement shader
    GLint locRefTexTrimap = glGetUniformLocation(progRefineID, "trimap");
    GLint locRefTexForeground = glGetUniformLocation(progRefineID, "foreground");
    GLint locRefTexBackground = glGetUniformLocation(progRefineID, "background");
    GLint locRefPixWidth = glGetUniformLocation(progRefineID, "pixWidth");
    GLint locRefPixHeight = glGetUniformLocation(progRefineID, "pixHeight");

    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, texBG);
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, renderedTexture);



    // INIT END

    int loopNumber = -1;
    double lastTime = glfwGetTime();
    double lastTimeLoop = glfwGetTime();
    int nbFrames = 0;

    /* Loop until the user closes the window */
    while (!glfwWindowShouldClose(window)) {



        // Measure speed
        double currentTime = glfwGetTime();
        nbFrames++;
        if ( currentTime - lastTime >= 1.0 ){
            // If last prinf() was more than 1 sec ago
            // printf and reset timer
            printf("%f ms/frame\n", 1000.0/double(nbFrames));
            nbFrames = 0;
            lastTime += 1.0;
        }

        // Foreground texture counter
        if ( currentTime - lastTimeLoop >= 0.08 ){
            loopNumber++;
            if (loopNumber >= lenTexFG) {
                loopNumber = 0;
            }
            lastTimeLoop += 0.08;
        }

        // Set current foreground texture
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texFGs[loopNumber]);






        /* RENDER TO FRAMEBUFFER */

        // Render to our framebuffer
        glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);

        // Clear buffer
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Init trimap shader program
        glUseProgram(progTrimapID);
        glUniform1f(locTriPixWidth, 1.0f/textw);
        glUniform1f(locTriPixHeight, 1.0f/texth);
        glUniform1i(locTriTexForeground, 0);

        // 1st attribute buffer: vertices
        glEnableVertexAttribArray(0);
        glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
        glVertexAttribPointer(
           0,                  // attribute 0. No particular reason for 0, but must match the layout in the shader.
           3,                  // size
           GL_FLOAT,           // type
           GL_FALSE,           // normalized?
           0,                  // stride
           (void*)0            // array buffer offset
        );

        // 2nd attribute buffer : colors
        glEnableVertexAttribArray(1);
        glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
        glVertexAttribPointer(
            1,                 // attribute. No particular reason for 1, but must match the layout in the shader.
            2,                 // size
            GL_FLOAT,          // type
            GL_FALSE,          // normalized?
            0,                 // stride
            (void*)0           // array buffer offset
        );


        // Draw the triangles (and render trimap shaders) to framebuffer/texture
        glDrawArrays(GL_TRIANGLES, 0, 2*3); // 2*3 indices starting at 0 -> 2 triangles -> 1 square

        glDisableVertexAttribArray(0);
        glUseProgram(0);










        // Render to the screen
        glBindFramebuffer(GL_FRAMEBUFFER, 0);

        // Init refinement shader program
        glUseProgram(progRefineID);
        glUniform1i(locRefTexForeground, 0);
        glUniform1i(locRefTexBackground, 1);
        glUniform1i(locRefTexTrimap, 2);
        glUniform1f(locRefPixWidth, 1.0f/textw);
        glUniform1f(locRefPixHeight, 1.0f/texth);

        // 1st attribute buffer : vertices
        glEnableVertexAttribArray(0);
        glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
        glVertexAttribPointer(
           0,                  // attribute 0. No particular reason for 0, but must match the layout in the shader.
           3,                  // size
           GL_FLOAT,           // type
           GL_FALSE,           // normalized?
           0,                  // stride
           (void*)0            // array buffer offset
        );

        // 2nd attribute buffer : texture coordinates
        glEnableVertexAttribArray(1);
        glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
        glVertexAttribPointer(
            1,                 // attribute. No particular reason for 1, but must match the layout in the shader.
            2,                 // size
            GL_FLOAT,          // type
            GL_FALSE,          // normalized?
            0,                 // stride
            (void*)0           // array buffer offset
        );


        // Draw the triangles (and render refinement shaders) to screen
        glDrawArrays(GL_TRIANGLES, 0, 2*3); // 2*3 indices starting at 0 -> 2 triangles -> 1 square
        glDisableVertexAttribArray(0);



        // RENDER END
        /* Swap front and back buffers */
        glfwSwapBuffers(window);
        /* Poll for and process events */
        glfwPollEvents();
    }
    glfwTerminate();
    return 0;
}
