#include "glview.h"
#include <QMatrix4x4>
#include <QDebug>



GLView::GLView(Scene *scene,Camera * camera,Controler *controler )
{
    this->camera = camera;
    this->scene = scene;
    this->controler = controler;
    gridSize = 5;
    gridStepSize = 1;
    isGridEnabled = false;
    MIP = false;
    afterInit = false;
}

QSize GLView::minimumSizeHint() const
{
    return QSize(50, 50);
}

QSize GLView::sizeHint() const
{
    return QSize(600, 400);
}


void GLView::initializeGL ( ) {
    Q_ASSERT(initializeOpenGLFunctions());

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glEnable(GL_DEPTH_TEST);


    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    static GLfloat lightPosition[4] = { 0.0, 0.0, 4.0, 1.0 };
    glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);


    uchar *trans = new uchar[4*256];

    for(int i = 0; i < 256; i+=1){
        int index = i*4;
        trans[index] = i;
        trans[index+1] = i;
        trans[index+2] = i;
        trans[index+3] = i;
    }

    loadTransfer(trans);

    //Shader Setup
    initShader();
    shader->bind();
    afterInit = true;
}

void GLView::initShader()
{
    QGLShader *vertex = new QGLShader(QGLShader::Vertex);
    if(!vertex->compileSourceFile(QLatin1String(":/phong.vert")))
        qCritical()<< "Vertex Shader failed"<< vertex->log();

    QGLShader *fragment = new QGLShader(QGLShader::Fragment);
    if(!fragment->compileSourceFile(QLatin1String(":/phong.frag")))
        qCritical()<< "Fragment Shader failed"<< fragment->log();

    shader = new QGLShaderProgram(this);
    shader->addShader(vertex);
    shader->addShader(fragment);
    shader->bindAttributeLocation("pickID",4);
    if(!shader->link()){
        qCritical()<< "Linking  failed"<<shader->log();
    }



    vertex = new QGLShader(QGLShader::Vertex);
    if(!vertex->compileSourceFile(QLatin1String(":/display.vert")))
        qCritical()<< "Vertex Shader 2 failed"<< vertex->log();

    fragment = new QGLShader(QGLShader::Fragment);
    if(!fragment->compileSourceFile(QLatin1String(":/display.frag")))
        qCritical()<< "Fragment Shader 2 failed"<< fragment->log();

    displayShader = new QGLShaderProgram(this);
    displayShader->addShader(vertex);
    displayShader->addShader(fragment);
    if(!displayShader->link()){
        qCritical()<< "Linking 2 failed:"<<displayShader->log();
    }

}

void GLView::home(){
    camera->home();
    updateGL();
}

void GLView::setMIP(bool mip){
    this->MIP = mip;
    updateGL();
}



void GLView::paintGL ()
{
    bool useFBO = true;
    //QOpenGLFunctions functions = QOpenGLContext::currentContext()->functions();
    // QGLFunctions functions = QGLFunctions(this->context());


    shader->bind();
    if(useFBO){
        glBindFramebuffer(GL_FRAMEBUFFER,fbo);
        GLenum buffers[] = {GL_COLOR_ATTACHMENT0,GL_COLOR_ATTACHMENT1,GL_COLOR_ATTACHMENT2,GL_COLOR_ATTACHMENT3};
        glDrawBuffers(4,buffers);

        //http://stackoverflow.com/questions/7207422/setting-up-opengl-multiple-render-targets
    }


    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);

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

    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();



    shader->setUniformValue("shaded",true);


    //set Projection and Camera Rotation
    camera->setupCamera(aspect);

    //draw Scene
    scene->draw(shader);

    shader->setAttributeValue(3,0);

    if(isGridEnabled){
        drawGrid();
    }



    if(isActive){
        glDisable(GL_DEPTH_TEST);
        glMatrixMode (GL_MODELVIEW);
        glLoadIdentity ();
        glMatrixMode (GL_PROJECTION);
        glLoadIdentity ();
        shader->setUniformValue("shaded",false);
        glLineWidth(10);
        GLfloat color[] = {1.0,1.0,0.0};
        glMaterialfv(GL_FRONT,GL_AMBIENT,color);
        glMaterialfv(GL_FRONT,GL_DIFFUSE,color);
        glMaterialfv(GL_FRONT,GL_SPECULAR,color);
        glMaterialf(GL_FRONT,GL_SHININESS,128);

        glBegin (GL_LINE_LOOP);
        glVertex3i (-1, -1, 0);
        glVertex3i (1, -1, 0);
        glVertex3i (1, 1, 0);
        glVertex3i (-1, 1, 0);
        glEnd ();
    }

    shader->release();
    if(useFBO){
        displayShader->bind();
        displayShader->setUniformValue("MIP", MIP);

        QMatrix4x4 mat = QMatrix4x4();
        mat.rotate(*camera->rotation);
        displayShader->setUniformValue("NormalMatrix",mat.inverted().transposed());
        //

        glBindFramebuffer(GL_FRAMEBUFFER,0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glViewport(0,0,this->width(),this->height());
        glMatrixMode (GL_MODELVIEW);
        glLoadIdentity ();
        glMatrixMode (GL_PROJECTION);
        glLoadIdentity ();

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D,color);
        displayShader->setUniformValue("Texture",0);

        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D,picID);
        displayShader->setUniformValue("PickID",1);

        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D,startRay);
        displayShader->setUniformValue("StartRay",2);

        glActiveTexture(GL_TEXTURE3);
        glBindTexture(GL_TEXTURE_2D,stopRay);
        displayShader->setUniformValue("StopRay",3);

        glActiveTexture(GL_TEXTURE4);
        glBindTexture(GL_TEXTURE_3D,texture3D);
        displayShader->setUniformValue("volumeData",4);

        glActiveTexture(GL_TEXTURE5);
        glBindTexture(GL_TEXTURE_1D,transferFunction);
        displayShader->setUniformValue("transferData",5);

        //displayShader->setUniformValue("active",scene->getActive()->getID());

        glBegin (GL_QUADS);
        glTexCoord2d(0,0);
        glVertex3i (-1, -1, 0);
        glTexCoord2d(1,0);
        glVertex3i (1, -1, 0);
        glTexCoord2d(1,1);
        glVertex3i (1, 1, 0);
        glTexCoord2d(0,1);
        glVertex3i (-1, 1, 0);
        glEnd ();



        displayShader->release();

    }

}


void GLView::drawGrid()
{
    shader->release();

    GLfloat specularColor[] = {0,0,0};
    GLfloat shininess[] = {128};
    glMaterialfv(GL_FRONT,GL_SPECULAR,specularColor);
    glMaterialfv(GL_FRONT,GL_SHININESS, shininess);
    GLfloat grey[] = {1,0.5,0.5};
    glMaterialfv(GL_FRONT,GL_DIFFUSE,grey);
    glNormal3f(0,1,0);

    glDisable(GL_LIGHTING);
    glEnable(GL_COLOR_MATERIAL);

    glLineWidth(1);
    glPolygonMode(GL_FRONT_AND_BACK,GL_LINES);
    glBegin(GL_LINES);
    float stepSize = gridStepSize;
    float x = stepSize;
    float y = stepSize;
    if (stepSize <= 0) stepSize = 1;

    for(; x < gridSize; x += stepSize){
        glVertex3f(x,0,-gridSize);
        glVertex3f(x,0,gridSize);
        glVertex3f(-x,0,-gridSize);
        glVertex3f(-x,0,gridSize);
    }
    for (; y < gridSize; y += stepSize){
        glVertex3f(-gridSize, 0, y);
        glVertex3f(gridSize,0,y);
        glVertex3f(-gridSize,0,-y);
        glVertex3f(gridSize,0,-y);
    }
    glEnd();

    glBegin(GL_LINES);
    x = stepSize;
    y = stepSize;
    if (stepSize <= 0) stepSize = 1;

    for(; x < gridSize; x += stepSize){
        glVertex3f(0,x,-gridSize);
        glVertex3f(0,x,gridSize);
        glVertex3f(0,-x,-gridSize);
        glVertex3f(0,-x,gridSize);
    }
    for (; y < gridSize; y += stepSize){
        glVertex3f(0,-gridSize,  y);
        glVertex3f(0,gridSize,y);
        glVertex3f(0,-gridSize,-y);
        glVertex3f(0,gridSize,-y);
    }
    glEnd();

    glBegin(GL_LINES);
    x = stepSize;
    y = stepSize;
    if (stepSize <= 0) stepSize = 1;

    for(; x < gridSize; x += stepSize){
        glVertex3f(x,-gridSize,0);
        glVertex3f(x,gridSize,0);
        glVertex3f(-x,-gridSize,0);
        glVertex3f(-x,gridSize,0);
    }
    for (; y < gridSize; y += stepSize){
        glVertex3f(-gridSize, y,0);
        glVertex3f(gridSize,y,0);
        glVertex3f(-gridSize,-y,0);
        glVertex3f(gridSize,-y,0);
    }
    glEnd();

    glBegin(GL_LINES);
    glVertex3f(0,0,-gridSize);
    glVertex3f(0,0,gridSize);
    glEnd();
    glBegin(GL_LINES);
    glVertex3f(-gridSize,0,0);
    glVertex3f(gridSize,0,0);
    glEnd();
    glBegin(GL_LINES);
    glVertex3f(0,-gridSize,0);
    glVertex3f(0,gridSize,0);
    glEnd();




    glPolygonMode(GL_FRONT,GL_FILL);
    glEnable(GL_LIGHTING);
    shader->bind();

}

void GLView::resizeGL(int width , int height )
{
    aspect = 1.0*width/height;

    //QGLFunctions functions = QGLFunctions(this->context());
    //QOpenGLFunctions_4_3_Core functions = *this;

    glGenFramebuffers(1, &fbo);
    glBindFramebuffer(GL_FRAMEBUFFER,fbo);



    // Create the color buffer
    glGenTextures(1, &color);
    glBindTexture(GL_TEXTURE_2D, color);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);


    // Create the pcik buffer
    glGenTextures(1, &picID);
    glBindTexture(GL_TEXTURE_2D, picID);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);


    // Create the pcik startRay
    glGenTextures(1, &startRay);
    glBindTexture(GL_TEXTURE_2D, startRay);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

    // Create the pcik stopRay
    glGenTextures(1, &stopRay);
    glBindTexture(GL_TEXTURE_2D, stopRay);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);



    // Create the depth buffer
    glGenRenderbuffers(1, &depth);
    glBindRenderbuffer(GL_RENDERBUFFER, depth);
    glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH_COMPONENT,width,height);

    glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,color,0);
    glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT1,GL_TEXTURE_2D,picID,0);
    glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT2,GL_TEXTURE_2D,startRay,0);
    glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT3,GL_TEXTURE_2D,stopRay,0);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_RENDERBUFFER,depth);

    GLenum err = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if(err == GL_FRAMEBUFFER_COMPLETE){
        // qDebug()<<"FBO OK";
    } else {
        qDebug()<<"FBO ERROR"<<err;
    }

    glBindFramebuffer(GL_FRAMEBUFFER, 0);


}

void GLView::loadData(int width, int height, int depth, char* data, int type  )
{

    glEnable(GL_TEXTURE_3D);
    glGenTextures( 1, &texture3D );
    glBindTexture(GL_TEXTURE_3D, texture3D);

    // Filtering
    glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

    // Wrap
    glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP );
    glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP );
    glTexParameteri( GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP );

    glTexImage3D( GL_TEXTURE_3D, 0, GL_R8,//
                  width, height, depth,  0,
                  GL_RED,type , data); // Imagedata as ByteBuffer
}

void GLView::loadTransfer(uchar* data )
{
//    uchar *test = new uchar[4*256];

//    for(int i = 0; i< 4*256;i++){
//       test[i] = data[i];
//    }
    glGenTextures( 1, &transferFunction );
    glBindTexture(GL_TEXTURE_1D, transferFunction);

    // Filtering
    glTexParameteri( GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
    glTexParameteri( GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

    // Wrap
    glTexParameteri( GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP );

    glTexImage1D(GL_TEXTURE_1D,0,GL_RGBA,256,0,GL_RGBA,GL_UNSIGNED_BYTE,data);
   if(afterInit) updateGL();
}



void GLView::setAcive(bool active)
{
    this->isActive = active;
    updateGL();
}

Camera *GLView::getCamera()
{
    return this->camera;
}


void GLView::mousePressEvent(QMouseEvent *event )
{
    controler->mousePressed(this,event);
}

void GLView::mouseMoveEvent(QMouseEvent *event )
{
    controler->mouseMoveEvent(this,event);
}

void GLView::wheelEvent(QWheelEvent *event )
{
    controler->wheelEvent(this,event);
}

