#include "mainwidget.h"

MainWidget::MainWidget(Camera *cam)
{
    this->cam = cam;
    startTime = QTime::currentTime();
    rotTime = QTime::currentTime();
    wireframe = true;
    rotation = false;
    subdivision = new Subdivision(this);
    subdivLevel = 0;
    QSurfaceFormat surfaceFormat;
    surfaceFormat.setSamples(32);
    setFormat(surfaceFormat);
}

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

QSize MainWidget::sizeHint() const
{
    return QSize(1280, 720);
}

void MainWidget::wheelEvent(QWheelEvent *event )
{
    if(event->delta()<0)
        cam->move(QVector3D(0.0,0.0,10));
    else
        cam->move(QVector3D(0.0,0.0,-10));
}

void MainWidget::keyPressEvent(QKeyEvent *event){
    if(event->key() == Qt::Key_Minus){
        cam->move(QVector3D(0.0,0.0,-10));
    }
    else if (event->key() == Qt::Key_Plus){
        cam->move(QVector3D(0.0,0.0,10));
    }
    else if(event->key() == Qt::Key_Right || event->key() == Qt::Key_D){
        emit subdivChange(1);
    }
    else if(event->key() == Qt::Key_Left || event->key() == Qt::Key_A){
        emit subdivChange(-1);
    }
    else{
        QOpenGLWidget::keyPressEvent(event);
    }
}

void MainWidget::mousePressEvent(QMouseEvent *event ){
    lastSpeherePos = trackballPoint(event->pos().x(),event->pos().y());
    lastScreenPos = new QPointF(event->screenPos());
    event->accept();

}

void MainWidget::mouseMoveEvent(QMouseEvent *event ){
    if (event->buttons() & Qt::LeftButton) {
        rotate(trackballPoint(event->pos().x(),event->pos().y()));
        event->accept();
    } else  if (event->buttons() & Qt::RightButton) {
        move(new QPointF(event->screenPos()));
        event->accept();
    }
}

void MainWidget::rotate(QVector3D *newPos )
{
    QVector3D axis = QVector3D::crossProduct(*lastSpeherePos,*newPos);

    float angle = 180 / M_PI * asin(sqrt(QVector3D::dotProduct(axis, axis)));
    axis.normalize();

    cam->rotate(QQuaternion::fromAxisAndAngle(axis, angle));

    lastSpeherePos = newPos;

}

void MainWidget::move(QPointF * newPos){
    QPointF dt = *newPos;
    dt -= *lastScreenPos;
    dt *= 0.1f;

    float dx = dt.x();
    float dy = dt.y()*-1.0;

    cam->move(QVector3D(dx,dy,0.0));

    lastScreenPos = newPos;
}


QVector3D* MainWidget::trackballPoint(int x, int y){
    float xo,yo,zo;

    // qDebug()<<"x:"<< x << " y:"<<y;
    xo = ((2.0*x)-width())/ height();
    yo = (height()-(2.0*y))/width();

    float d = sqrt(xo*xo+yo*yo);
    zo = qMax(qCos(M_PI_2*d),qreal(0.0)); //qMin(d,1.0f)


    QVector3D *pos = new QVector3D(xo,yo,zo);
    pos->normalize();
    //  qDebug()<<"x:"<< xo << " y:"<<yo<<" z:"<<zo;
    return pos;
}


void MainWidget::initializeGL(){
    bool glFunctionsOK = initializeOpenGLFunctions();
    Q_ASSERT(glFunctionsOK);
    if(!glFunctionsOK){
        qDebug()<<"ContextFormat"<<this->context()->format();
        exit(4);
    }

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

    // Shader
    QString vertSource = QLatin1String(":/subdivide.vert");
    QString tesselationControlSource = QLatin1String(":/subdivide.tcs");
    QString tesselationEvaluationSource = QLatin1String(":/subdivide.tes");
    QString tesselationControlSourceRegular = QLatin1String(":/subdivideRegular.tcs");
    QString tesselationEvaluationSourceRegular = QLatin1String(":/subdivideRegular.tes");
    QString geometrySource = QLatin1String(":/subdivide.geo");
    QString fragSource = QLatin1String(":/subdivide.frag");


    subdivisionShader = initShaderProgram(vertSource,
            tesselationControlSource,
            tesselationEvaluationSource,
            geometrySource,
            fragSource);
    regularShader = initShaderProgram(
            vertSource,
            tesselationControlSourceRegular,
            tesselationEvaluationSourceRegular,
            geometrySource,
            fragSource);

    subdivision->init();
    //loadNewMesh( "../Models/demon_head.3ds");
    loadNewMesh( "../Models/torus.obj");
}

void MainWidget::loadNewMesh(){

    QString fn = QFileDialog::getOpenFileName(NULL, tr("Open Mesh..."),
                                              QString("../Models"),
                                              tr("*.md5mesh *.3ds *.md2 *.obj *.dae *.dxf *.mesh.xml *.blend *.b3d" ));
    emit subdivChange(0);
    loadNewMesh(fn);

}

void MainWidget::loadNewMesh(QString path){
    QDir dir(".");
    //qDebug()<<dir.absolutePath();
    if(path.isEmpty())
        return;

    qDebug()<<"Opening File:"<<path;
    mesh =  new Mesh(this,path);
    qDebug()<<"Mesh loading finished";
    subdivision->splitRegular(mesh);
}

void MainWidget::subdivide(int level){
    subdivision->subdivide(mesh, level);
    subdivLevel = level;

}

void MainWidget::setRotation(bool started){
    qDebug()<<"rotation"<<started;
    this->rotation = started;
    rotTime = QTime::currentTime();
}

void MainWidget::setWireframe(bool active){
    this->wireframe = active;
}

QOpenGLShaderProgram* MainWidget::initShaderProgram(QString vertSource, QString tesselationControlSource, QString tesselationEvaluationSource, QString geometrySource, QString fragSource ){

    QOpenGLShader *vert = initGLShader(vertSource,QOpenGLShader::Vertex);
    QOpenGLShader *tesselationControl = initGLShader(tesselationControlSource,QOpenGLShader::TessellationControl);
    QOpenGLShader *tesselationEval = initGLShader(tesselationEvaluationSource,QOpenGLShader::TessellationEvaluation);
    QOpenGLShader *geometry = initGLShader(geometrySource,QOpenGLShader::Geometry);
    QOpenGLShader *frag = initGLShader(fragSource,QOpenGLShader::Fragment);

    qDebug()<<"Add Shaders ...";
    QOpenGLShaderProgram* shader = new QOpenGLShaderProgram();
    shader->addShader(vert);
    shader->addShader(tesselationControl);
    shader->addShader(tesselationEval);
    shader->addShader(geometry);
    shader->addShader(frag);
    qDebug()<<"Linking Shaders ...";
    if(!shader->link()){
        qCritical()<< "Linking shader failed:"<<shader->log();
        exit(5);
    }
    qDebug()<<"Linking Shader done";

    return shader;

}

QOpenGLShader* MainWidget::initGLShader(QString scource, QOpenGLShader::ShaderType type){
    QOpenGLShader *shader = new QOpenGLShader(type);
    if(!shader->compileSourceFile(scource)){
        qCritical()<<type<< "Shader"<<scource<<"failed"<< shader->log();
        exit(5);
    }
    return shader;
}


void MainWidget::paintGL(){
    glViewport(0,0,width(),height());

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    QMatrix4x4 rot;
    int time = rotTime.msecsTo(QTime::currentTime());
    if(rotation){
        //TODO schön machen
        rot.rotate(time/100.0*36/5,QVector3D(0,1,0));
    }
    QMatrix4x4 view = cam->getMatrix()*rot;
    QVector3D light = QVector3D(0,100,100);

    //============= Regular Patches====================
    regularShader->bind();
    regularShader->setUniformValue("wireframe",wireframe);
    regularShader->setUniformValue("colorTexture",0);
    regularShader->setUniformValue("LightPos",light);
    regularShader->setUniformValue("subdiv",subdivLevel);

    mesh->render(regularShader,view, m_projection,subdivLevel,true);

    if(subdivLevel>0){
        glEnable (GL_BLEND);
        glDepthMask(GL_FALSE);
        glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        regularShader->setUniformValue("wireframe",false);
        regularShader->setUniformValue("subdiv",0);
        mesh->render(regularShader,view, m_projection,0,true);
        glDisable(GL_BLEND);
        glDepthMask(GL_TRUE);
    }
    regularShader->release();

    //============= Irregular Patches====================
    subdivisionShader->bind();
    subdivisionShader->setUniformValue("wireframe",wireframe);
    subdivisionShader->setUniformValue("colorTexture",0);
    subdivisionShader->setUniformValue("LightPos",light);

    mesh->render(subdivisionShader,view, m_projection,subdivLevel,false);

    if(subdivLevel>0){
        glEnable (GL_BLEND);
        glDepthMask(GL_FALSE);
        glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        subdivisionShader->setUniformValue("wireframe",false);
        mesh->render(subdivisionShader,view, m_projection,0,false);
        //mesh->render(subdivisionShader,view, m_projection,0,true);
        glDisable(GL_BLEND);
        glDepthMask(GL_TRUE);
    }
    subdivisionShader->release();

    update();
}

void MainWidget::resizeGL(int width, int height){
    qDebug()<<"Resize"<<width<<height;

    m_projection = QMatrix4x4();
    m_projection.perspective(45.0f,1.0*width/height,0.01f,1000.0f);
}

