#include "cubewidget.h"

CubeWidget::CubeWidget(QWidget *parent)
    : QGLWidget(QGLFormat(QGL::SampleBuffers), parent)
{

}

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

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

void CubeWidget::initializeGL ()
{
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glEnable(GL_DEPTH_TEST);

    tesselation = 0;
    prepareShader();
    // enable default shading
    home();
    showFlat();

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    static GLfloat lightPosition[4] = { 0.5, 0.0, 2.0, 1.0 };
    glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
    //GLfloat white[] = {1.0,1.0,1.0};
    //glLightfv(GL_LIGHT0, GL_DIFFUSE, white);
    //glLightfv(GL_LIGHT0, GL_SPECULAR, white);
    //glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 2.0f);

}

void CubeWidget::prepareShader()
{
    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);
    if(!shader->link())
        qCritical()<< "Linking  failed"<<shader->log();
}

void CubeWidget::setMaterial(GLfloat *color )
{
    glMaterialfv(GL_FRONT,GL_AMBIENT,color);
    GLfloat white[] = {1.0,1.0,1.0};
    glMaterialfv(GL_FRONT,GL_DIFFUSE,color);
    glMaterialfv(GL_FRONT,GL_SPECULAR,white);

    GLfloat mShininess[] = {128};
    glMaterialfv(GL_FRONT,GL_SHININESS,mShininess);

}

void CubeWidget::paintGL ( )
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    glMultMatrixf(translation->data());

    QMatrix4x4 mat = QMatrix4x4();
    mat.rotate(*rotation);
    glMultMatrixf(mat.data());

    GLfloat red[] = {1.0,0.0,0.0};
    GLfloat green[] = {0.0,1.0,0.0};
    GLfloat blue[] = {0.0,0.0,1.0};
    GLfloat cyan[] = {0.0,1.0,1.0};
    GLfloat magenta[] = {1.0,0.0,1.0};
    GLfloat yellow[] = {1.0,1.0,0.0};
    float increment = 1.0/(pow(2,tesselation));

    setMaterial(red);
    glBegin(GL_QUADS);
    glNormal3f(0,1,0);
    for (float x = -0.5; x < 0.5 ; x+= increment) {
        for (float y = -0.5; y < 0.5 ; y+= increment ) {
            glVertex3f( x+increment, 0.5f,y);
            glVertex3f(x, 0.5f,y);
            glVertex3f(x, 0.5f, y+increment);
            glVertex3f( x+increment, 0.5f, y+increment);
        }
    }
    glEnd();


    setMaterial(magenta);
    glBegin(GL_QUADS);
    glNormal3f(0,-1,0);
    for (float x = -0.5; x < 0.5 ; x+= increment) {
        for (float y = -0.5; y < 0.5 ; y+= increment ) {
            glVertex3f( x+increment, -0.5f,y+increment);
            glVertex3f(x, -0.5f,y+increment);
            glVertex3f(x, -0.5f, y);
            glVertex3f( x+increment, -0.5f, y);
        }
    }
    glEnd();


    setMaterial(green);
    glBegin(GL_QUADS);
    glNormal3f(0,0,1);
    for (float x = -0.5; x < 0.5 ; x+= increment) {
        for (float y = -0.5; y < 0.5 ; y+= increment ) {
            glVertex3f( x+increment,y+increment, 0.5f);
            glVertex3f(x, y+increment, 0.5f);
            glVertex3f(x,  y, 0.5f);
            glVertex3f( x+increment, y, 0.5f);
        }
    }
    glEnd();


    setMaterial(yellow);
    glBegin(GL_QUADS);
    glNormal3f(0,0,-1);
    for (float x = -0.5; x < 0.5 ; x+= increment) {
        for (float y = -0.5; y < 0.5 ; y+= increment ) {
            glVertex3f( x+increment,y, -0.5f);
            glVertex3f(x, y,-0.5f);
            glVertex3f(x, y+increment,-0.5f);
            glVertex3f( x+increment,y+increment, -0.5f);
        }
    }
    glEnd();


    setMaterial(blue);
    glBegin(GL_QUADS);
    glNormal3f(-1,0,0);
    for (float x = -0.5; x < 0.5 ; x+= increment) {
        for (float y = -0.5; y < 0.5 ; y+= increment ) {
            glVertex3f( -0.5f, x+increment,y+increment);
            glVertex3f( -0.5f,x+increment,y);
            glVertex3f( -0.5f,x, y);
            glVertex3f(  -0.5f,x, y+increment);
        }
    }
    glEnd();


    setMaterial(cyan);
    glBegin(GL_QUADS);
    glNormal3f(1,0,0);
    for (float x = -0.5; x < 0.5 ; x+= increment) {
        for (float y = -0.5; y < 0.5 ; y+= increment ) {
            glVertex3f( 0.5f, x+increment,y);
            glVertex3f( 0.5f,x+increment,y+increment);
            glVertex3f( 0.5f,x, y+increment);
            glVertex3f(  0.5f,x, y);
        }
    }
    glEnd();
}



void CubeWidget::resizeGL(int width , int height )
{
    glMatrixMode(GL_PROJECTION);

    glViewport(0, 0, width, height);

    glLoadIdentity();
    perspective(45.0,1.0*width/height,0.01,100.0);

    glMatrixMode(GL_MODELVIEW);
}

void CubeWidget::perspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar)
{
    GLdouble xmin, xmax, ymin, ymax;

    ymax = zNear * tan( fovy * M_PI / 360.0 );
    ymin = -ymax;
    xmin = ymin * aspect;
    xmax = ymax * aspect;

    glFrustum( xmin, xmax, ymin, ymax, zNear, zFar );
}


void CubeWidget::showWireframe()
{

    qDebug("show Wireframe");
    glShadeModel(GL_FLAT);

    glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
    shader->release();
    updateGL();
}

void CubeWidget::showFlat()
{
    qDebug("show Flat");
    glShadeModel(GL_FLAT);
    glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
    shader->release();
    updateGL();
}

void CubeWidget::showGouraut()
{
    qDebug("show Gouraut");
    glShadeModel(GL_SMOOTH);
    glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
    shader->release();
    updateGL();
}

void CubeWidget::showPhong()
{
    qDebug("show Phong");
    glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
    shader->bind();
    updateGL();

}

void CubeWidget::setTessellation(int t)
{
    tesselation = t;
    updateGL();
    qDebug()<<"Tesselation" << t;
}

void CubeWidget::home()
{
    translation = new QMatrix4x4();
    translation->translate(0.0,0.0,-3.0);
    rotation = new QQuaternion();
    updateGL();
}

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


}

void CubeWidget::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 CubeWidget::rotate(QVector3D *newPos )
{
    QVector3D axis = QVector3D::crossProduct(*lastSpeherePos,*newPos);

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

    //axis = rotation->conjugate().rotatedVector(axis);
    QQuaternion newRot = QQuaternion::fromAxisAndAngle(axis, angle) * *rotation;
    rotation = new QQuaternion(newRot.toVector4D());

    lastSpeherePos = newPos;
    updateGL();

}

void CubeWidget::move(QPointF * newPos){
    QPointF dt = *newPos;
    dt -= *lastScreenPos;
    dt *= 0.01f;

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

    translation->translate(dx,dy,0.0);

    lastScreenPos = newPos;
    updateGL();
}

QVector3D* CubeWidget::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 CubeWidget::wheelEvent(QWheelEvent *event )
{
    if(event->delta()<0)
        //TODO
        translation->translate(0.0,0.0,1.0);
    else
        translation->translate(0.0,0.0,-1.0);

    updateGL();
}
