#include <QtGui>
#ifdef NEEDGLEE
  #include "GLee.h"
#endif
#include "openglcanvas.h"
#include <cmath>
#include <sys/types.h>  // for fstat, getpwuid, getuid
#include <sys/stat.h>   // for fstat
#ifdef _WIN32
  #include <stdlib.h>   // for getenv (TODO: use getenv_s)
#else
  #include <pwd.h>      // for getpwuid
#endif
#include <cstdio>       // for getc
#include <cstring>

#include "files.h"

#define VERT_SHADER_FILE "vertex_shader.vert"
#define FRAG_SHADER_FILE "fragment_shader.frag"

#define CONST_PI        3.141592653589793115997963468544185161590576171875
#define CONST_PI_2      1.5707963267948965579989817342720925807952880859375
#define CONST_PI_F      3.1415927410125732421875f
#define CONST_PI_2_F    1.57079637050628662109375f
//#define CONST_PI        (0x1.921fb54442d18p+1)
//#define CONST_PI_2      (0x1.921fb54442d18p+0)
//#define CONST_PI_F      (0x1.921fb6p+1f)
//#define CONST_PI_2_F    (0x1.921fb6p+0f)

OpenGLCanvas::OpenGLCanvas(QWidget *parent) :
    QGLWidget(parent)
{
    setFormat(QGL::DoubleBuffer | QGL::DepthBuffer);
    fov = 60.f;
    fov_max = 60.f; // TODO: I only use floats here because Leo did... check
    scale = 1.0f;
    center_lambda = 0.f;
    center_phi = 0.f;
    fov_scale_relation = "Naive";
    visualization = "Moebius";
    auto_fov_max=false;
    zblambda=.1f; // Zorin-Barr transformation lambda
    zbR=1.f; // Zorin-Barr transformation R

    time_frames = 0;
    time_timer.setInterval(0);
    connect(&time_timer, SIGNAL(timeout()), this, SLOT(slotTimer()));
    time_start = time_time.time();
}

void OpenGLCanvas::slotTimer(void) {
    updateGL();
}

void OpenGLCanvas::change_fov(double f){

    if (fov!=f && f>=1.f && f<=360.f)
        fov=f;
    if (fov<=fov_max)
        scale=1.f;
    //else if (fov>295.f)
    //    scale = 0.02f; // TODO: check this value wrt fov_max
    else {
        if (fov_scale_relation == "Naive")
            scale=fov_max/fov;
        else if (fov_scale_relation == "Square Root")
            scale=sqrt((360.f-fov_max-fov)/(360.-2*fov_max));
        else if (fov_scale_relation == "Linear")
            scale=(360.f-fov_max-fov)/(360.-2*fov_max);
        else if (fov_scale_relation == "Square Power")
            scale=powf((360.f-fov_max-fov)/(360.-2*fov_max),2);
        else if (fov_scale_relation == "Cubic Power")
            scale=powf((360.f-fov_max-fov)/(360.-2*fov_max),3);
        else if (fov_scale_relation == "Logarithm")
            scale=log(exp(1.f)+(1.f-exp(1.f))*(fov-fov_max)/(360.-2*fov_max));
    }

//    scale = 0.3f;

    fprintf(stderr,"change fov, fov=%f, fov_max=%f, scale=%f\n",fov,fov_max,scale);
    emit fov_changed((int)fov);

    updateGL();

}

void OpenGLCanvas::automaxbutton(bool autovalue){
    auto_fov_max=autovalue;
    change_fov_max(compute_auto_fov_max(fov));
}

void OpenGLCanvas::change_fov(int new_fov){
    if(new_fov<=360&&new_fov>=1){
        change_fov((double)new_fov);
        if(auto_fov_max)
            change_fov_max(compute_auto_fov_max(new_fov));
    }
}

void OpenGLCanvas::change_fov_max(int new_fov_max){
    if(new_fov_max<=360&&new_fov_max>=1)
            fov_max=(double)new_fov_max;
    if (fov<=fov_max)
        scale=1.f;
    //else if (fov>295.f)
    //    scale = 0.02f; // TODO: check this value wrt fov_max
    else {
        if (fov_scale_relation == "Naive")
            scale=fov_max/fov;
        else if (fov_scale_relation == "Square Root")
            scale=sqrt((360.f-fov_max-fov)/(360.-2*fov_max));
        else if (fov_scale_relation == "Linear")
            scale=(360.f-fov_max-fov)/(360.-2*fov_max);
        else if (fov_scale_relation == "Square Power")
            scale=powf((360.f-fov_max-fov)/(360.-2*fov_max),2);
        else if (fov_scale_relation == "Cubic Power")
            scale=powf((360.f-fov_max-fov)/(360.-2*fov_max),3);
        else if (fov_scale_relation == "Logarithm")
            scale=log(exp(1.f)+(1.f-exp(1.f))*(fov-fov_max)/(360.-2*fov_max));
    }
    fprintf(stderr,"change fov_max, fov=%f, fov_max=%f, new scale=%f\n",fov,fov_max,scale);
    emit max_fov_changed((int)fov_max);
    updateGL();
}

//void OpenGLCanvas::change_scale(double s){

//    if (scale!=s && s>=0.0 && s<=1.0) scale = s;
//    updateGL();

//}

void OpenGLCanvas::change_center_lambda(double lambda){

    if (center_lambda!=lambda && lambda>=-CONST_PI && lambda<=CONST_PI) {
        center_lambda = lambda;
        updateGL();
    }

}

void OpenGLCanvas::change_center_phi(double phi){

    if (center_phi!=phi && phi>=-CONST_PI_2 && phi<=CONST_PI_2) {
        center_phi = phi;
        updateGL();
    }

}

void OpenGLCanvas::re_center(){
    center_phi=.0f;
    center_lambda=.0f;
    updateGL();
}

void OpenGLCanvas::change_fov_scale_relation(QString name){

   fov_scale_relation = name;
   if (fov<fov_max) scale = 1.f;
   //else if (fov>295.f) scale = 0.01f;
   else{
       if (fov_scale_relation == "Naive")
           scale=fov_max/fov;
       else if (fov_scale_relation == "Square Root")
           scale=sqrt((360.f-fov_max-fov)/(360.-2*fov_max));
       else if (fov_scale_relation == "Linear")
           scale=(360.f-fov_max-fov)/(360.-2*fov_max);
       else if (fov_scale_relation == "Square Power")
           scale=powf((360.f-fov_max-fov)/(360.-2*fov_max),2);
       else if (fov_scale_relation == "Cubic Power")
           scale=powf((360.f-fov_max-fov)/(360.-2*fov_max),3);
       else if (fov_scale_relation == "Logarithm")
           scale=log(exp(1.f)+(1.f-exp(1.f))*(fov-fov_max)/(360.-2*fov_max));
   }
   fprintf(stderr,"changed scale relation, scale=%f, fov_max=%f\n",scale,fov_max);
   updateGL();

}

void OpenGLCanvas::change_visualization(QString name){

    visualization = name;
    updateGL();

}

// This function reads the contents of the ~/.panorc file and stores the
// options in private variables.
void OpenGLCanvas::read_config_file(){
#ifdef _WIN32
    char* envvar=(char*)malloc(12*sizeof(char));
    strcpy(envvar,"USERPROFILE\0");
    char *filepath=(char*)malloc(512*sizeof(char));
    strcpy(filepath,getenv(envvar));
    free(envvar);
    strcat(filepath,"\\.panorc");
#else
    struct passwd *pw=getpwuid(getuid());
    char *filepath=pw->pw_dir;
    strcat(filepath,"/.panorc");
#endif
    shader_dir=(char*)malloc(512*sizeof(char));
    shader_dir[0]='\0';
    input_image_file=(char*)malloc(512*sizeof(char));
    input_image_file[0]='\0';
    char *read_line=(char*)malloc(64*sizeof(char));
    struct stat testbuf;
    if(stat(filepath,&testbuf)){
        fprintf(stderr,"%s does not exist\n",filepath);
    }else{
        FILE *rcfile;
        FOPEN_RO(rcfile,filepath);
        char c;
        char *line=(char*)malloc(512*sizeof(char));
        while((c=getc(rcfile))!=EOF){
            while(c=='\n') // discard empty lines
                c=getc(rcfile);
            if(c==EOF)
                break;
            line[0]=c; // first char on the line was already read
            if(!fgets(line+1,511,rcfile)){
                fprintf(stderr,"error reading rcfile\n");
                exit(-1);
            }
            // check for 'shader_dir' option
            if(!strncmp(line,"shader_dir=",11)){
                strcpy(shader_dir,line+11);
                shader_dir[strlen(line)-12]='\0';
                fprintf(stderr,"shader_dir=%s\n",shader_dir);
            }
            // check for 'image_file' option
            if(!strncmp(line,"image_file=",11)){
                strcpy(input_image_file,line+11);
                input_image_file[strlen(line)-12]='\0';
                fprintf(stderr,"input_image_file=%s\n",input_image_file);
            }
            // check for 'max_fov' option
            if(!strncmp(line,"max_fov=",8)){
                strcpy(read_line,line+8);
                read_line[strlen(line)-9]='\0';
                fov_max=atof(read_line);
                fprintf(stderr,"max_fov=%f\n",fov_max);
            }
            // check for 'auto_max_fov' option
            if(!strncmp(line,"auto_max_fov=",13)){
                strcpy(read_line,line+13);
                read_line[strlen(line)-14]='\0';
                auto_fov_max=atof(read_line);
                fprintf(stderr,"auto_max_fov=%d\n",auto_fov_max);
            }
            // check for the Zorin-Barr transformation parameters, lambda and R
            if(!strncmp(line,"zblambda=",9)){
                strcpy(read_line,line+9);
                read_line[strlen(line)-10]='\0';
                zblambda=atof(read_line);
                fprintf(stderr,"zblambda=%f\n",zblambda);
            }
            if(!strncmp(line,"zbR=",4)){
                strcpy(read_line,line+4);
                read_line[strlen(line)-5]='\0';
                zbR=atof(read_line);
                fprintf(stderr,"zbR=%f\n",zbR);
            }
        }
        fclose(rcfile);
    }
#ifdef _WIN32
    free(filepath);
#endif
    emit fov_changed((int)fov);
    emit max_fov_changed((int)fov_max);
}

void OpenGLCanvas::load_image(const char *new_image){
    unsigned char *textureBytes=read_image(new_image,&image_size_x,&image_size_y);
    glPixelStorei(GL_UNPACK_ALIGNMENT,1);
    GLuint tex;
    glGenTextures(1,&tex);
    glBindTexture(GL_TEXTURE_2D,tex);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D,
                 0,
                 GL_RGB,
                 image_size_x,
                 image_size_y,
                 0,
                 GL_RGB,
                 GL_UNSIGNED_BYTE,
                 textureBytes);
}

void OpenGLCanvas::change_input_image(){
        const char *fname=QFileDialog::getOpenFileName(this,tr("Choose Panorama File"),"",FileRead::image_types).toStdString().c_str();
        if(strlen(fname)>0){
                load_image(fname);
                updateGL();
        }
}

void OpenGLCanvas::initializeGL(){
    glShadeModel(GL_SMOOTH);
    glClearColor(1.0f,1.0f,1.0f,0.0f);
    glClearDepth(1.0f);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);

#ifndef _MSC_VER
#ifdef __APPLE__
    const char * const progname = "PROJ_ROOT_DIR";
#else
    // progname is a file name or a path???
    const char * const progname = (char*)(PROGNAME);
#endif
    fprintf(stderr,"progname=%s\n",progname);
#endif // MSC_VER

    read_config_file();
    // If the input file does not exist or was not specified.
    struct stat testbuf;
    if(stat(input_image_file,&testbuf)||!strcmp(input_image_file,"")){
        load_image(QFileDialog::getOpenFileName(this,tr("Choose Panorama File"),"",FileRead::image_types).toStdString().c_str());
    }else{
        load_image(input_image_file);
    }
    free(input_image_file);

    // mesh resolution
    int m,n;
    m = n = 100;

    //defining texture coordinates
    int meshNumTexCoord = m*n;
    float *texCoord = (float *)malloc(2*meshNumTexCoord*sizeof(float));
    if (texCoord == NULL){
        printf("problem allocating memory for texture coordinates \n");
    }
    define_texture_coordinates(texCoord, m, n, -CONST_PI_2_F, CONST_PI_2_F, -CONST_PI_F, CONST_PI_F);

    //defining positions of the sphere vertices
    int meshNumVertices = m*n;
    float* positions = (float *)malloc(3*meshNumVertices*sizeof(float));
    if (positions == NULL){
        printf("problem allocating memory for positions \n");
    }
//    vertex_transformation(positions, m, n, center_lambda, center_phi, fov_rads, scale); //passar pelo vertex shader
    load_sphere_mesh(positions, m, n); //colocar essa e funcoes para textura e triangulos no initializeGL

    //defining triagle indices
    unsigned int meshNumFaces = 2*(m-1)*(n-1);
    unsigned int meshNumIndices = 3*meshNumFaces;
    unsigned int * indices = (unsigned int *)malloc(meshNumIndices*sizeof(unsigned int));
    define_triangle_indices(indices, m, n);

    // draw setup
    verticesPositions = positions;
    textureCoordinates = texCoord;
    numberOfIndices = meshNumIndices;
    triangleIndices = indices;

    setShaders();
}

void OpenGLCanvas::define_texture_coordinates(float *texCoord, int m, int n, float min_phi, float max_phi, float min_lambda, float max_lambda){

    float delta_lambda = (max_lambda-min_lambda)/(1.0*(n-1));
    float delta_phi = (max_phi-min_phi)/(1.0*(m-1));

    for (int i = 0; i<m; i++){
        for (int j = 0; j<n; j++){
            texCoord[2*(j+i*n)] = (min_lambda+delta_lambda*j)/(2*CONST_PI_F) + 0.5;
            texCoord[2*(j+i*n)+1] = (min_phi+delta_phi*i)/(CONST_PI_F) + 0.5;
        }
    }

}

void OpenGLCanvas::vertex_transformation(float *positions, int m, int n, float center_lambda, float center_phi, float fov_rads, float scale){

    float min_lambda = -CONST_PI_F;
    float max_lambda = CONST_PI_F;
    float min_phi = -CONST_PI_2_F;
    float max_phi = CONST_PI_2_F;

    float delta_lambda = (max_lambda-min_lambda)/(1.0*(n-1));
    float delta_phi = (max_phi-min_phi)/(1.0*(m-1));

    float lambda, phi, x, y, z, u, v, r, theta;

    //calculating the extent of the projection for the given FOV
    lambda = fov_rads;
    phi = 0.f;
    // OpenGL: x is the vertical axes pointg downwards, and y is horizontal axes
    y = sin(phi);
    x = -sin(lambda)*cos(phi);
    z = -cos(lambda)*cos(phi);
    u = 2.f*x/(-z+1.f);
    v = 2.f*y/(-z+1.f);
    r = sqrt(u*u+v*v);
    theta = atan2(u,v);
    r *= scale;
    u = -r*sin(theta);
    v = r*cos(theta);
    x = (4.f*u)/(u*u+v*v+4.f);
    y = (4.f*v)/(u*u+v*v+4.f);
    z = (u*u+v*v-4.f)/(u*u+v*v+4.f);
    u = x/(-z);
    v = y/(-z);
    float extent = u;

    for (int i = 0; i<m; i++){
        for (int j = 0; j<n; j++){

            lambda = (min_lambda+delta_lambda*j);
            phi = (min_phi+delta_phi*i);

            // OpenGL: x is the vertical axes pointg downwards, and y is horizontal axes
            y = sin(phi);
            x = -sin(lambda)*cos(phi);
            z = -cos(lambda)*cos(phi);

            //Rotation 1: (-center_lambda)-rotation on the xz-plane
            float x_copy = x;
            x = cos(-center_lambda)*x - sin(-center_lambda)*z;
            y = 1.f*y;
            z = sin(-center_lambda)*x_copy + cos(-center_lambda)*z;

            //Rotation 2: (-center_phi)-rotation on the yz-plane
            float y_copy = y;
            x = 1.f*x;
            y = cos(-center_phi)*y - sin(-center_phi)*z;
            z = sin(-center_phi)*y_copy + cos(-center_phi)*z;


            u = 2.f*x/(-z+1.f);
            v = 2.f*y/(-z+1.f);

            r = sqrt(u*u+v*v);
            theta = atan2(u,v);

            // scaling the complex plane according to scale specified in the interface (relate it to FOV)
            r *= scale;

            u = -r*sin(theta);
            v = r*cos(theta);

            x = (4.f*u)/(u*u+v*v+4.f);
            y = (4.f*v)/(u*u+v*v+4.f);
            z = (u*u+v*v-4.f)/(u*u+v*v+4.f);

            lambda = atan2(x,-z)/CONST_PI_F;
            phi = asin(y)/CONST_PI_2_F;

            if (visualization=="Moebius"){
                u = x/(-z);
                v = y/(-z);
                positions[3*(j+i*n)] = u/extent;
                positions[3*(j+i*n)+1] = v/extent;
                positions[3*(j+i*n)+2] = z;
            }

            if (visualization=="3D Sphere"){
                positions[3*(j+i*n)] = 0.9f*x;
                positions[3*(j+i*n)+1] = 0.9f*y;
                positions[3*(j+i*n)+2] = z;
            }

            if (visualization=="Equi-Rectangular"){
                positions[3*(j+i*n)] = lambda;
                positions[3*(j+i*n)+1] = phi;
                positions[3*(j+i*n)+2] = z;
            }

            if (visualization=="Stereographic"){
                u = 2*x/(-z+1);
                v = 2*y/(-z+1);
                positions[3*(j+i*n)] = u/extent;
                positions[3*(j+i*n)+1] = v/extent;
                positions[3*(j+i*n)+2] = z;
            }

            if (visualization=="Mercator"){
                u = lambda;
                v = log((1.0/cos(phi)) + tan(phi));
                positions[3*(j+i*n)] = u/extent;
                positions[3*(j+i*n)+1] = v/extent;
                positions[3*(j+i*n)+2] = z;
            }

            // perspective
            if (visualization=="Zorin-Barr"){
                // perspective
                u = x/(-z);
                v = y/(-z);
                // apply Z-B transformation to (u,v)
                float lambda=.1f;
                float R=1.f;
                float alpha=atanf(v/u);
                float r=hypotf(u,v);
                float rhoprime=(lambda*r/R)+(1.f-lambda)*(R*(sqrtf(r*r+1.f)-1.f))/(r*(sqrtf(R*R+1.f)-1.f));
                u=rhoprime*cosf(alpha);
                v=rhoprime*sinf(alpha);
                //
                positions[3*(j+i*n)] = u/extent;
                positions[3*(j+i*n)+1] = v/extent;
                positions[3*(j+i*n)+2] = z;
            }
        }
    }

}

void OpenGLCanvas::load_sphere_mesh(float *positions, int m, int n){

    float min_lambda = -CONST_PI_F;
    float max_lambda = CONST_PI_F;
    float min_phi = -CONST_PI_2_F;
    float max_phi = CONST_PI_2_F;

    float delta_lambda = (max_lambda-min_lambda)/(1.0*(n-1));
    float delta_phi = (max_phi-min_phi)/(1.0*(m-1));

    float lambda, phi, x, y, z;

    for (int i = 0; i<m; i++){
        for (int j = 0; j<n; j++){

            lambda = (min_lambda+delta_lambda*j);
            phi = (min_phi+delta_phi*i);

            // OpenGL: x is the vertical axes pointg downwards, and y is horizontal axes
            y = sin(phi);
            x = -sin(lambda)*cos(phi);
            z = -cos(lambda)*cos(phi);

            positions[3*(j+i*n)] = x;
            positions[3*(j+i*n)+1] = y;
            positions[3*(j+i*n)+2] = z;

        }
    }

}

float OpenGLCanvas::calculate_extent(float fov_rads){

    double lambda, phi, x, y, z, u, v, r, theta;
    //calculating the extent of the projection for the given FOV
    lambda = fov_rads;
    phi = 0.;
    // OpenGL: x is the vertical axes pointg downwards, and y is horizontal axes
    y = sin(phi);
    x = -sin(lambda)*cos(phi);
    z = -cos(lambda)*cos(phi);
    u = 2.*x/(-z+1.);
    v = 2.*y/(-z+1.);
    r = sqrt(u*u+v*v);
    theta = atan2(u,v);
    r *= scale;
    u = -r*sin(theta);
    v = r*cos(theta);
    x = (4.*u)/(u*u+v*v+4.);
    y = (4.*v)/(u*u+v*v+4.);
    z = (u*u+v*v-4.)/(u*u+v*v+4.);
    if (visualization=="Moebius"){
        u = x/(-z);
        v = y/(-z);
    }
    else if (visualization=="Stereographic"){
        u = 2*x/(-z+1);
        v = 2*y/(-z+1);
    }
    else if (visualization=="Mercator"){
        u = fov_rads;
        // Warning: this extent calculation is wrong.
        // Write now it's olny showing the entire panorama intead of
        // the corresponging FOV.
    }
    if (visualization=="Zorin-Barr"){
        // TODO: check whether this is correct
        u = x/(-z);
        v = y/(-z);
    }
    return u;
}

void OpenGLCanvas::define_triangle_indices(unsigned int * indices, int m, int n){

    for (int i = 0; i<m-1; i++){
        for (int j = 0; j<n-1; j++){

            unsigned int index = (j+i*n);

            indices[3*(2*(j+i*(n-1)))] = index;
            indices[3*(2*(j+i*(n-1)))+1] = index+1;
            indices[3*(2*(j+i*(n-1)))+2] = index+n;

            indices[3*(2*(j+i*(n-1))+1)] = index+1;
            indices[3*(2*(j+i*(n-1))+1)+1] = index+n+1;
            indices[3*(2*(j+i*(n-1))+1)+2] = index+n;

        }
    }

}

// This function computes a new value of the maximum fov and must be called
// when the fov is changed and the auto setting is enabled.
int OpenGLCanvas::compute_auto_fov_max(int new_fov){
        if(new_fov<60)
            return 60;
        if(new_fov>179)
            return 1;
        //if(new_fov>60)
            return (90-new_fov/2);
}

void OpenGLCanvas::resizeGL(int w, int h){
    if(w>h)
        glViewport(0,(h-w)/2,w,w);
    else
        glViewport((w-h)/2,0,h,h);
}

char * OpenGLCanvas::textFileRead(char *fn) {

    FILE *fp;
    char *content = NULL;
    int f, count;
    f = OPEN_FILE(fn, O_RDONLY);
    count = LSEEK_FD(f, 0, SEEK_END);
//    close(f);
    if (fn != NULL) {
        FOPEN_RO(fp,fn);
        if (fp != NULL) {
            if (count > 0) {
                content = (char *)malloc(sizeof(char) * (count+1));
                count = fread(content,sizeof(char),count,fp);
                content[count] = '\0';
            }
            fclose(fp); // maybe this line must be outside the {}
        }
    }
    return content;
}

void OpenGLCanvas::setShaders() {

    char *vs,*fs;

#ifndef __APPLE__
  #ifdef GLEW_VERSION_1_5
    GLenum err=glewInit();
    if(err!=GLEW_OK){
        fprintf(stderr,"error in GLEW initialization: %s\n",glewGetString(err));
        exit(-1);
    }
  #endif
#endif

    GLuint v = glCreateShader(GL_VERTEX_SHADER);
    GLuint f = glCreateShader(GL_FRAGMENT_SHADER);

    // Configure vertex and fragment shader files.
    char *vs_file=(char*)malloc(512*sizeof(char*));
    char *fs_file=(char*)malloc(512*sizeof(char*));
    if(!strcmp(shader_dir,"")){ // if shader_dir was not configured
        if(!GET_WORKDIR(vs_file,512)||!GET_WORKDIR(fs_file,512)){
            fprintf(stderr,"error reading shader files\n");
            exit(-1);
        }
        strcat(vs_file,"/shaders/");
        strcat(fs_file,"/shaders/");
    }else{;
        strcpy(vs_file,shader_dir);
        strcat(vs_file,"/");
        strcpy(fs_file,shader_dir);
        strcat(fs_file,"/");
    }
    strcat(vs_file,VERT_SHADER_FILE);
    strcat(fs_file,FRAG_SHADER_FILE);
    fprintf(stderr,"vs_file=%s\nfs_file=%s\n",vs_file,fs_file);

    struct stat vs_testbuf,fs_testbuf;
    if(stat(vs_file,&vs_testbuf)||stat(fs_file,&fs_testbuf)){
        fprintf(stderr,"a shader file does not exist!\n");
        free(vs_file);
        free(fs_file);
        exit(-1);
    }

    vs=textFileRead(vs_file);
    fs=textFileRead(fs_file);

    const char * vv = vs;
    const char * ff = fs;

    glShaderSource(v, 1, &vv,NULL);
    glShaderSource(f, 1, &ff,NULL);

    free(vs);free(fs);
    free(vs_file);free(fs_file);

    glCompileShader(v);
    glCompileShader(f);

    GLuint p = glCreateProgram();

    // Bind attributes zblambda and zbR to the vertex shader. Nvidia hardware
    // only leaves attributes 1 and 7 unreserved; attributes 8 to 15 are
    // reserved for textures.
    // TODO: we use attributes 14 and 15, which work for Nvidia; we need to
    // test with other hardware.
#define ZBL_ATTR 14
#define ZBR_ATTR 15
    glVertexAttrib1f(ZBL_ATTR,zblambda);
    glBindAttribLocation(p,ZBL_ATTR,"zblambda");
    glVertexAttrib1f(ZBR_ATTR,zbR);
    glBindAttribLocation(p,ZBR_ATTR,"zbR");

    glAttachShader(p,v);
    glAttachShader(p,f);

    glLinkProgram(p);
    glUseProgram(p);

}

void OpenGLCanvas::mousePressEvent(QMouseEvent *event){
    lastPos=event->pos();
    //fprintf(stderr,"mouse click\n");
}

void OpenGLCanvas::mouseMoveEvent(QMouseEvent *event){
    // scroll with the left button
    if(event->buttons()==Qt::LeftButton){
        // compute the delta and move the image
        center_lambda+=(event->x()-lastPos.x())*CONST_PI_F/image_size_x;
        center_phi+=(event->y()-lastPos.y())*CONST_PI_F/image_size_y;
        lastPos=event->pos();
        updateGL();
    }
}

void OpenGLCanvas::wheelEvent(QWheelEvent *event){
    if(event->orientation()==Qt::Vertical){
        if(event->modifiers()==Qt::ShiftModifier){
            change_fov_max(fov_max+((double)event->delta())/30);
        }else{
            int new_fov=fov+event->delta()/30;
            change_fov((double)new_fov);
            if(auto_fov_max)
                change_fov_max(compute_auto_fov_max(new_fov));
        }
    }
}

void OpenGLCanvas::paintGL(){

    float fov_rads = (fov/360.)*CONST_PI;

//    // changing scale to generate the figures for the paper (remove it after)
//    scale = 0.8;


    // defining transformation parameters (that will be passed to the vertex shader)
    float extent = calculate_extent(fov_rads);
    float vis_mode=.0;
    if (visualization=="Moebius") vis_mode=1.0;
    else if (visualization=="3D Sphere") vis_mode=2.0;
    else if (visualization=="Equi-Rectangular") vis_mode=3.0;
    else if (visualization=="Stereographic") vis_mode=4.0;
    else if (visualization=="Mercator") vis_mode=5.0;
    else if (visualization=="Zorin-Barr") vis_mode=6.0;
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0, 2.0/extent, 0.0, 2.0/scale, 0.0, -2.0/vis_mode);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glOrtho(0.0, 2.0/center_lambda, 0.0, 2.0/center_phi, -1.0, 1.0);

    // drawing the mesh
    glClearColor(1.0, 1.0, 1.0, 1.0);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_TEXTURE_2D);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

    glColor3f(1, 0, 0);

    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, verticesPositions);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glTexCoordPointer(2, GL_FLOAT, 0, textureCoordinates);

    glDrawElements(GL_TRIANGLES, numberOfIndices, GL_UNSIGNED_INT, triangleIndices);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);

    time_frames++;
//    if (time_frames > 0) {
        double dt = time_time.elapsed();
//        if (dt > 0.5) {
            time_fps = time_frames/dt;
            time_frames = 0;
            time_time.reset();
            emit fps(QString("%1 fps").arg((int)(time_fps+0.5)));
            //printf("fps = %d ", (int)(time_fps+0.5));
//        }
//    }

}
