#include "include/DataManager.hpp"
#include "include/texture3D.hpp"
#include "include/framebuffer.hpp"
#include "include/io.hpp"
#include "include/messages.h"
#include "include/skeleton.hpp"

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

DataManager::DataManager() {
    dim = new int[2];
    fbo = (Framebuffer *) 0;

}

DataManager::~DataManager() {
    delete [] dim;
    delete dataLoc;
    delete fbo;
    delete sh_alpha;
}


/* Draw all disks and compute the skeleton of the result (along with the skeleton
 * we will get the distance from the border. This can be used to interpolate at the
 * boundary.
 */
Texture2D* DataManager::getDistanceTransform(int l) {
    int i;
    float border_size_OLD = border_size;


    float *data = (float *) malloc(dim[0] * dim[1] * sizeof (float));
    unsigned char *texdata = (unsigned char *) calloc(dim[0] * dim[1] * 4, sizeof (char));

    border_size = EPSILON;
    Texture2D *tex = getAlphaMapOfLayer(l);
    border_size = border_size_OLD;
    if (tex == NULL) {
        fbo->texture->setData(texdata);
        free(data);
        free(texdata);
        return fbo->texture;
    }

    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, tex->tex);

    /* Altering range [0..1] -> [0 .. 255] */
    glGetTexImage(GL_TEXTURE_2D, 0, GL_RED, GL_FLOAT, data);
    for (i = 0; i < dim[0] * dim[1]; i++) {
        data[i] *= 255;
    }
    FIELD<float> *im = new FIELD<float>(0, 0);
    im->setAll(dim[0], dim[1], data);
    computeDT(im);
   
    /* Expand result of Distance Transform from 1 channel to 4 channels */
    for (i = 0; i < dim[0] * dim[1]; i++) {
        texdata[4 * i] =
                texdata[4 * i + 1] =
                texdata[4 * i + 2] = (unsigned char) (MIN((255.0 / border_size) * data[i], 255.0));
        texdata[4 * i + 3] = 255;
    }
    
    /* Expand to RGBA */
    tex->setData(texdata);
    

    free(data);
    free(texdata);
    return fbo->texture;
}

/* This function draws all disks. The alpha will be 1 at the location that should be drawn, otherwise 0. */
Texture2D* DataManager::getAlphaMapOfLayer(int l) {
    layer_t *da = readLayer(l);
    int radius;

    if (fbo == 0) {
        PRINT(MSG_ERROR, "Framebuffer object was not initialised. Creating new FBO (FIX THIS!)\n");
        initFBO();
    }

    /* Draw on a texture. */
    fbo->bind();
    if (SHADER == GLOBAL_ALPHA) {
        glClearColor(1.0, 1.0, 1.0, 1.0);
    }

    glEnable(GL_DEPTH_TEST);
    glClear(GL_DEPTH_BUFFER_BIT);
    glClear(GL_COLOR_BUFFER_BIT);
    sh_alpha->bind();
    if (SHADER == LOCAL_ALPHA)
        glUniform1f(sh_alpha->uniform("border"), border_size);

    glBegin(GL_QUADS);
    for (unsigned int j = 0; j < da->size(); ++j) {
        radius = (*da)[j].third;
        radius += 0.5 * border_size;
        
        glTexCoord2f(-1.0, -1.0);
        glVertex2f((*da)[j].first - radius, (*da)[j].second - radius);
        glTexCoord2f(-1.0, 1.0);
        glVertex2f((*da)[j].first - radius, (*da)[j].second + radius);
        glTexCoord2f(1.0, 1.0);
        glVertex2f((*da)[j].first + radius, (*da)[j].second + radius);
        glTexCoord2f(1.0, -1.0);
        glVertex2f((*da)[j].first + radius, (*da)[j].second - radius);
    }
    glEnd();

    sh_alpha->unbind();
    fbo->unbind();

    glClearColor(0.0, 0.0, 0.0, 1.0);

    return da->size() != 0 ? fbo->texture : NULL;

}

/* Return all disks for a certain intensity */
layer_t *DataManager::readLayer(int l) {
    return (*data)[l];
}

/* When the lowest intensity is >0, we should set the clear color to i-1, where
 * i is the first intensity with disks.
 */
void DataManager::setClearColor(){
    layer_t *tmp;
    int i=0;
    int numLayers=0;

    while(tmp= readLayer(i+1), i<256 && (tmp == NULL || tmp->size()==0)) ++i;
    if(i==256){
        PRINT(MSG_ERROR, "Could not find any points. Empty image file?\n");
        exit(0);
    }
    for(int j=i; j<256; ++j){
        if(readLayer(j) != 0 && readLayer(j)->size()>0){
            numLayers++;
        }
    }
    cout << "Found " << numLayers << "layers." << endl;
    if(numLayers>1)
    glClearColor(i/255.0,i/255.0,i/255.0,0);
    else
    glClearColor(0,0,0,0);

    PRINT(MSG_VERBOSE, "Clear color set to: %f, %f, %f (layer %d)\n", i/255.0,i/255.0,i/255.0,i);
}

void DataManager::initFBO() {
    fbo = new Framebuffer(dim[0], dim[1], GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, true);
}

void DataManager::setAlphaShader(SHADER_TYPE st) {
    SHADER = st;

    if (st == LOCAL_ALPHA) {
        string aunifs[] = {"border"};
        sh_alpha = new GLSLProgram("glsl/alpha.vert", "glsl/alpha.frag");
        sh_alpha->compileAttachLink();
        sh_alpha->bindUniforms(ARRAY_SIZE(aunifs), aunifs);
        PRINT(MSG_VERBOSE, "Succesfully compiled shader 1 (local alpha computation)\n");
    } else if (st == GLOBAL_ALPHA) {
        sh_alpha = new GLSLProgram("glsl/nointerpolationinv.vert", "glsl/nointerpolationinv.frag");
        sh_alpha->compileAttachLink();
        PRINT(MSG_VERBOSE, "Succesfully compiled shader 1 (global alpha computation)\n");
    } else if (st == NORMAL) {
        sh_alpha = new GLSLProgram("glsl/nointerpolation.vert", "glsl/nointerpolation.frag");
        sh_alpha->compileAttachLink();
        PRINT(MSG_VERBOSE, "Succesfully compiled shader 1 (No interpolation)\n");
    } else {
        PRINT(MSG_ERROR, "Invalid shader. Did not compile.");
        exit(1);
    }



}

/* Changes the interpolation border size. */
void DataManager::setBorderSize(float b) {
    PRINT(MSG_VERBOSE, "Changing border size to: %f.\n", b);
    border_size = (b > EPSILON) ? b : EPSILON;
}
