#define GL_ARB_draw_instanced 1

#include "texturewidget.h"

using namespace std;

textureWidget::textureWidget() : QGLWidget()
{

    program = 0;
    Index = 0;
    background = false;

}

textureWidget::~textureWidget()
{


}


// Create a GLSL program object from vertex and fragment shader files
void textureWidget::InitShader(const char* vShaderFile, const char* fShaderFile)
{

    struct Shader {
        const char*  filename;
        GLenum       type;
        GLchar*      source;
    }  shaders[2] = {
        { vShaderFile, GL_VERTEX_SHADER, NULL },
        { fShaderFile, GL_FRAGMENT_SHADER, NULL }
    };


    QGLShader *vshader = new QGLShader(QGLShader::Vertex, this);
    QGLShader *fshader = new QGLShader(QGLShader::Fragment, this);
    // Es llegeixen els dos shaders: el vertex i el fragment shader
    for ( int i = 0; i < 2; ++i ) {
        Shader& s = shaders[i];
        s.source = Common::readShaderSource( s.filename );
        if ( shaders[i].source == NULL ) {
            std::cerr << "Failed to read " << s.filename << std::endl;
            exit( EXIT_FAILURE );
        }
   }
    vshader->compileSourceCode(shaders[0].source);
    fshader->compileSourceCode(shaders[1].source);
    program = new QGLShaderProgram(this);
    program->addShader(vshader);
    program->addShader(fshader);

    program->link();

    program->bind();

}

void textureWidget::initShadersGPU()
{
  // Carrega dels shaders i posa a punt per utilitzar els programes carregats a la GPU
  InitShader( "../TFGProject/vshader41_textura.glsl", "../TFGProject/fshader41_textura.glsl" );

}


void textureWidget::refresh()
{
    initializeGL();
    updateGL();
}

void textureWidget::initializeGL()
{
    //static const GLfloat lightPos[4] = { 5.0f, 5.0f, 10.0f, 1.0f };
    glEnable(GL_COLOR_MATERIAL);
    glEnable (GL_BLEND);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_DEPTH_TEST); // <-- asi se activa
    glDepthFunc(GL_LEQUAL);
    glEnable(GL_BACK);
    glAlphaFunc ( GL_GREATER, 0.2 ) ;
    glEnable ( GL_ALPHA_TEST ) ;
    glEnable(GL_NORMALIZE);
    glDisable(GL_POINT_SMOOTH);
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

    initShadersGPU();

    glViewport(0, 0, this->size().width(), this->size().height());

    creaPla();

    dades2DtoGPU((GLuint)0, 0, 0, 0);


}

void textureWidget::creaPla(){

    point4 vertices[4] = {
            point4(-1.0, -1.0, 0.0, 1.0),
            point4(-1.0, 1.0, 0.0, 1.0),
            point4(1.0, 1.0, 0.0, 1.0),
            point4(1.0, -1.0, 0.0, 1.0)
        };

    color4 color[8] = {
        color4( 0.0, 0.0, 0.0, 1.0 ),  // black
        color4( 1.0, 0.0, 0.0, 1.0 ),  // red
        color4( 1.0, 1.0, 0.0, 1.0 ),  // yellow
        color4( 0.0, 1.0, 0.0, 1.0 ),  // green
        color4( 0.0, 0.0, 1.0, 1.0 ),  // blue
        color4( 1.0, 0.0, 1.0, 1.0 ),  // magenta
        color4( 0.0, 1.0, 1.0, 1.0 ),  // white
        color4( 1.0, 1.0, 1.0, 1.0 )   // cyan
        };

    colors[Index] = color[Index]; points[Index] = vertices[Index]; tex_coords[Index] = vec2(0.0, 0.0); Index++;
    colors[Index] = color[Index]; points[Index] = vertices[Index]; tex_coords[Index] = vec2(0.0, 1.0); Index++;
    colors[Index] = color[Index]; points[Index] = vertices[Index]; tex_coords[Index] = vec2(1.0, 1.0); Index++;
    colors[Index] = color[Index]; points[Index] = vertices[Index]; tex_coords[Index] = vec2(1.0, 0.0); Index++;
}


void textureWidget::paintGL()
{

    if (!this->isVisible())
        return;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    drawTexture();
}

void textureWidget::dades2DtoGPU(GLuint texName, GLint texWidth, GLint texHeight, GLubyte* texPtr){

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    glGenTextures(1, &texName);
    glBindTexture(GL_TEXTURE_2D, texName);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texWidth,
                texHeight, 0, GL_RGB, GL_UNSIGNED_BYTE,
                texPtr);

    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
    glBindTexture(GL_TEXTURE_2D, texName);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texName);

    GLuint texLoc;
    texLoc = program->uniformLocation("textura");

    glUniform1i(texLoc, 0);

    // Creació d'un vertex array object
    GLuint vao;
    glGenVertexArrays( 1, &vao );
    glBindVertexArray( vao );

    // Creacio i inicialitzacio d'un buffer object
    GLuint buffer;
    glGenBuffers( 1, &buffer );
    glBindBuffer( GL_ARRAY_BUFFER, buffer );
    glBufferData( GL_ARRAY_BUFFER, sizeof(points) + sizeof(colors) + sizeof(tex_coords),
    NULL, GL_STATIC_DRAW );
    glBufferSubData( GL_ARRAY_BUFFER, 0, sizeof(points), points );
    glBufferSubData( GL_ARRAY_BUFFER, sizeof(points), sizeof(colors), colors );
    glBufferSubData( GL_ARRAY_BUFFER, sizeof(points)+sizeof(colors), sizeof(tex_coords), tex_coords );

    // set up vertex arrays

    int vertexLocation = program->attributeLocation("vPosition");
    int colorLocation = program->attributeLocation("vColor");
    int texcoordLocation = program->attributeLocation("vTexCoord");

    program->enableAttributeArray(vertexLocation);
    program->setAttributeBuffer(vertexLocation, GL_FLOAT, 0, 4);

    program->enableAttributeArray(colorLocation);
    program->setAttributeBuffer(colorLocation, GL_FLOAT, sizeof(points), 4);

    program->enableAttributeArray(texcoordLocation);
    program->setAttributeBuffer(texcoordLocation, GL_FLOAT, sizeof(points)+sizeof(colors), 2);


    program->bindAttributeLocation("vPosition", vertexLocation);
    program->bindAttributeLocation("vColor", colorLocation);
    program->bindAttributeLocation("vTexCoord", texcoordLocation);

    program->link();
    program->bind();

}

void textureWidget::drawTexture(){

    int vertexLocation = program->attributeLocation("vPosition");
    int colorLocation = program->attributeLocation("vColor");
    int texcoordLocation = program->attributeLocation("vTexCoord");

    glBufferSubData( GL_ARRAY_BUFFER, 0, sizeof(points), points );
    glBufferSubData( GL_ARRAY_BUFFER, sizeof(points), sizeof(colors), colors );
    glBufferSubData( GL_ARRAY_BUFFER, sizeof(points)+sizeof(colors), sizeof(tex_coords), tex_coords );

    program->enableAttributeArray(vertexLocation);
    program->setAttributeBuffer(vertexLocation, GL_FLOAT, 0, 4);

    program->enableAttributeArray(colorLocation);
    program->setAttributeBuffer(colorLocation, GL_FLOAT, sizeof(points), 4);

    program->enableAttributeArray(texcoordLocation);
    program->setAttributeBuffer(texcoordLocation, GL_FLOAT, sizeof(points)+sizeof(colors), 2);

    glDrawArrays( GL_POLYGON, 0, NumVertices );

    program->disableAttributeArray(vertexLocation);
    program->disableAttributeArray(colorLocation);
    program->disableAttributeArray(texcoordLocation);

}

void textureWidget::showRaycastingCPUResult(GLubyte *texture){

    makeCurrent();
    initShadersGPU();

    GLint w, h;

    FILE *fin;
    fin = fopen("imatge.ppm", "rb");
    if(fin!=0){

        GLubyte* texture = readPPM(fin, &w, &h );

        fclose(fin);

        dades2DtoGPU((GLuint)0, w,h,texture);

    }

    free(texture);

    updateGL();
}

void textureWidget::resizeGL(int width, int height)
{
    glViewport(0, 0, width, height);
}


/*
 * ppmRead: read a PPM raw (type P6) file.  The PPM file has a header
 * that should look something like:
 *
 *   P6
 *   # comment
 *   width height max_value
 *   rgbrgbrgb...

 */
GLubyte* textureWidget::readPPM(FILE *fin, GLsizei *wid, GLsizei *ht)
{
  GLubyte  *bytes;
  char cookie[3];
  int width, height, maxComp;
  int n, r,c;

    fscanf(fin, "%2s", &cookie);

    if (strcmp("P6", cookie)) return NULL; /* not right file type */
    fscanf(fin, "%d", &width); *wid = width;
    fscanf(fin, "%d", &height); *ht = height;
    fscanf(fin, "%d", &maxComp);

    if (maxComp > 255) return NULL; /* data error */
    fgetc(fin);	/* skip 1 character */

    n = width * height * 3;
    bytes = (GLubyte  *) malloc(n);
    if (bytes == NULL) return NULL; /* couldn't allocate space */


    for (r=height-1; r>=0; r--)
      for (c=0; c<width; c++) {
         bytes[3*(r*width + c)] = fgetc(fin); // red byte
         bytes[3*(r*width + c)+1] = fgetc(fin); // green byte
         bytes[3*(r*width + c)+2] = fgetc(fin); // blue byte



      }

    return bytes;
}

void textureWidget::cambibackground(){
    if (background) {
        background = false;
         glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    }
    else{
        background = true;
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    }
  //  cout<<" background "<<background;
    this->repaint();
    updateGL();
}

