#include <fstream>
#include <cmath>
#include <sys/stat.h>
#include <GL/glew.h>
#include <GL/freeglut.h>
#include "include/main.hpp"
#include "include/io.hpp"
#include "include/messages.h"
#include "include/glslProgram.h"
#include "include/framebuffer.hpp"
#include "include/DataManager.hpp"
#include "include/ImageDecoder.hpp"

#define MIN(a,b) (a) < (b) ? (a):(b)
#define MAX(a,b) (a) > (b) ? (a):(b)
#define ARRAY_SIZE(arr) sizeof(arr)/sizeof(arr[0])


SHADER_TYPE SHADER = NORMAL;
// Possible values: GLOBAL_ALPHA, LOCAL_ALPHA, NORMAL;
/* Global alpha uses a distance transform on the entire layer.
 * Local alpha uses a distance for each disk (TESTING)
 * Normal does not perform additional interpolation.
 */

/* Output level: MSG_QUIET, MSG_ERROR, MSG_NORMAL, MSG_VERBOSE */
int MSG_LEVEL = MSG_VERBOSE;
int WWIDTH = 0, WHEIGHT = 0;
DataManager *dm;

GLSLProgram *sh_render;

using namespace std;

char *outFile=0; /* Press 's' to make a screenshot to location specified by outFile. */


void saveOutput(){
    unsigned char *sdata = (unsigned char *) malloc(WWIDTH * WHEIGHT * 4);
    glReadPixels(0, 0, WWIDTH, WHEIGHT, GL_RGB, GL_UNSIGNED_BYTE, sdata);
    Texture2D *t = new Texture2D(WWIDTH, WHEIGHT, GL_RGB, GL_RGB, GL_UNSIGNED_BYTE);
    t->setData(sdata);
    t->saveAsPNG(outFile);
   
}



void initDataManager(const char *file){
    ImageDecoder id;
    id.load(file);
    dm = new DataManager();
    dm->setWidth(id.width);
    dm->setHeight(id.height);
    dm->setData(id.getImage());
}

void initShader(){
    string runifs[] = {"layer", "alpha"};
    sh_render = new GLSLProgram("glsl/render.vert", "glsl/render.frag");
    sh_render->compileAttachLink();
    sh_render->bindUniforms(ARRAY_SIZE(runifs), runifs);
}


void display(void) {
    int i;
    Texture2D *alphaMap=0;
    glClear(GL_COLOR_BUFFER_BIT);
    glActiveTexture(GL_TEXTURE0);

    for (i = 0; i <= 255; ++i) {
        PRINT(MSG_VERBOSE, "%d/255\n", i);
        
        if(SHADER == GLOBAL_ALPHA){
                alphaMap = dm->getDistanceTransform(i);
        }else{
                alphaMap = dm->getAlphaMapOfLayer(i);
        }
        if(alphaMap == NULL) {
            continue;
        }

        glDisable(GL_DEPTH_TEST);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        /* SECOND PASS: Render using alpha map */
        sh_render->bind();
        glUniform1f(sh_render->uniform("alpha"), 0);
        glUniform1f(sh_render->uniform("layer"), i / 255.0);
        alphaMap->bind();
        glBegin(GL_QUADS);
        glTexCoord2f(0.0, 1.0);
        glVertex2f(0, 0);
        glTexCoord2f(0.0, 0.0);
        glVertex2f(0, WHEIGHT);
        glTexCoord2f(1.0, 0.0);
        glVertex2f(WWIDTH, WHEIGHT);
        glTexCoord2f(1.0, 1.0);
        glVertex2f(WWIDTH, 0);
        glEnd();
        sh_render->unbind();
        glDisable(GL_BLEND);

    }

    glFlush(); //Finish rendering
    glutSwapBuffers();

}

void reshape(int w, int h) {
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(0, WWIDTH, WHEIGHT, 0);
    glViewport(0, 0, w, h);
    glMatrixMode(GL_MODELVIEW);
    return;
}

void idle(void) {
    usleep(1000);
}

void keyboard(unsigned char key, int x, int y) {

    switch (key) {
        case 's':
            outFile = (char*) "output.png";
            saveOutput();
            return;
            break;
        case 'q':
        case 27:
            exit(0);
            break;
        default:
            printf("Unsupported input: %c", key);
            fflush(stdout);
            break;
    }
    glutPostRedisplay();
}

void mouse(int button, int state, int x, int y) {
    glutPostRedisplay();
}





int main(int argc, char *argv[]) {

    /* Read meta data and set variables accordingly */
    initDataManager(argv[1]);
    WHEIGHT = dm->getHeight();
    WWIDTH = dm->getWidth();
    PRINT(MSG_VERBOSE, "Image dimensions: %d x %d\n", WWIDTH, WHEIGHT);


    /* Initialize GLUT */
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowSize(WWIDTH, WHEIGHT);
    glutCreateWindow(WINDOW_TITLE);

    /* Initialize GLEW */
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        /* Problem: glewInit failed, something is seriously wrong. */
        PRINT(MSG_ERROR, "Error: %s\n", glewGetErrorString(err));
        exit(-1);
    }
    PRINT(MSG_NORMAL, "GLEW: Using GLEW %s\n", glewGetString(GLEW_VERSION));

    /* Set OPENGL states */
    glEnable(GL_TEXTURE_2D);
    glDisable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    /* Set clear color one below the first layer that has points*/
    dm->setClearColor();

    /* Set texture parameters */
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

        
    glutDisplayFunc(display);
    glutKeyboardFunc(keyboard);
    glutReshapeFunc(reshape);
    glutIdleFunc(idle);
    
    initShader();
    dm->initFBO();


    if(argc > 2){
        SHADER = GLOBAL_ALPHA;
        dm->setBorderSize(atoi(argv[2]));
    }else
        dm->setBorderSize(0);

    dm->setAlphaShader(SHADER);
    glutMainLoop();

    return 0;
}






