#include "Shader.h"
#include "Utils.h"
#include <cmath>
#include "Wrapper.h"

Shader::Shader()
{
    unifProjMatrix = Engine::getUniformLocation("projMatrix");
    unifViewMatrix = Engine::getUniformLocation("viewMatrix");
    unifModelMatrix = Engine::getUniformLocation("modelMatrix");
    
    unifUseTexture = Engine::getUniformLocation("useTexture");
    unifUseColor = Engine::getUniformLocation("useColor");
    unifUseMixColor = Engine::getUniformLocation("useMixColor");
    unifMixColorLoc = Engine::getUniformLocation("mixColor");
    unifUseNormals = Engine::getUniformLocation("useNormals");
}

Shader::Shader(const Shader& orig)
{
}

Shader::~Shader()
{
}

void Shader::set3DMode(float lag)
{
    float q = 1.0f / tanf((0.5f * fovY) * M_PI / 180.0f);

    proj.set(0, 0, (q * Engine::getHeight()) / Engine::getWidth());
    proj.set(1, 1, q);
    proj.set(2, 2, (nearClip + farClip) / (nearClip - farClip));
    proj.set(3, 2, -1.0f);
    proj.set(2, 3, (2.0f * nearClip * farClip) / (nearClip - farClip));
    proj.set(3, 3, 0);   
    
    Engine::setMatrix(unifProjMatrix, proj.getValues());
    
    // -------------------------------------------------------------------------
    // calculate vectors for the view matrix
    // -------------------------------------------------------------------------
    
    // front
    front.setAngles(interpolate(lag, oldLengthAngle, lengthAngle), interpolate(lag, oldWidthAngle, widthAngle));

    // back
    back.setInverse(front);

    // right
    right.set(front);
    right.cross(0.0f, 1.0f, 0.0f);
    right.normalize();
    
    // left
    left.setInverse(right);
    
    // up
    up.set(front);
    up.cross(left);
    up.normalize();
    
    Vector3D interCamera = oldCamera;
    interCamera.addMul(camera, lag);
    interCamera.addMul(oldCamera, -lag);
    
    view.set(0, 0, right.getX());
    view.set(0, 1, right.getY());
    view.set(0, 2, right.getZ());
    view.set(0, 3, right.dotInverse(interCamera));
    
    view.set(1, 0, up.getX());
    view.set(1, 1, up.getY());
    view.set(1, 2, up.getZ());
    view.set(1, 3, up.dotInverse(interCamera));
    
    view.set(2, 0, back.getX());
    view.set(2, 1, back.getY());
    view.set(2, 2, back.getZ());
    view.set(2, 3, back.dotInverse(interCamera));
    
    view.set(3, 0, 0.0f);
    view.set(3, 1, 0.0f);
    view.set(3, 0, 0.0f);
    view.set(3, 3, 1.0f);
   
    // -------------------------------------------------------------------------
    // calculate vectors for movement
    // -------------------------------------------------------------------------
    
    // front
    front.setY(0.0f);
    front.normalize();

    // back
    back.setInverse(front);

    // right
    right.set(front);
    right.cross(0.0f, 1.0f, 0.0f);
    right.normalize();
    
    // left
    left.setInverse(right);

    // up
    up.set(0.0f, 1.0f, 0.0f);
    
    // down
    down.setInverse(up);
    
    Engine::setMatrix(unifViewMatrix, view.getValues());
}

void Shader::set2DMode()
{
    proj.setToIdentity();
    Engine::setMatrix(unifProjMatrix, proj.getValues());
    
    int scale = Engine::getScale();
    view.set(0, 0, (2.0f * scale) / Engine::getWidth());
    view.set(0, 1, 0.0f);
    view.set(0, 2, 0.0f);
    view.set(0, 3, -1.0f);
    
    view.set(1, 0, 0.0f);
    view.set(1, 1, (-2.0f * scale) / Engine::getHeight());
    view.set(1, 2, 0.0f);
    view.set(1, 3, 1.0f);
    
    view.set(2, 0, 0.0f);
    view.set(2, 1, 0.0f);
    view.set(2, 2, (-1.0f * scale) / Engine::getHeight());
    view.set(2, 3, 0.5f);
    
    view.set(3, 0, 0.0f);
    view.set(3, 1, 0.0f);
    view.set(3, 0, 0.0f);
    view.set(3, 3, 1.0f);
    
    Engine::setMatrix(unifViewMatrix, view.getValues());
}

void Shader::storeCamera()
{
    oldCamera.set(camera);
    oldLengthAngle = lengthAngle;
    oldWidthAngle = widthAngle;
}

void Shader::setCamera(float x, float y, float z, float length, float width)
{
    camera.set(x, y, z);
    lengthAngle = length;
    widthAngle = width;
}

const Vector3D& Shader::getFront() const
{
    return front;
}

const Vector3D& Shader::getBack() const
{
    return back;
}

const Vector3D& Shader::getRight() const
{
    return right;
}

const Vector3D& Shader::getLeft() const
{
    return left;
}

const Vector3D& Shader::getUp() const
{
    return up;
}

const Vector3D& Shader::getDown() const
{
    return down;
}

void Shader::setTextureEnabled(bool use)
{
    Engine::setInt(unifUseTexture, use);
}

void Shader::setColorEnabled(bool use)
{
    Engine::setInt(unifUseColor, use);
}

void Shader::setMixColorEnabled(bool use)
{
    Engine::setInt(unifUseMixColor, use);
}

void Shader::setMixColor(float r, float g, float b, float a)
{
    Engine::setFloat(unifUseMixColor, r, g, b, a);
}

void Shader::setTextMode()
{
    setTextureEnabled(true);
    setColorEnabled(true);
    setUseBlending(true);
    setMixColorEnabled(false);
    setNormalsEnabled(false);
}

void Shader::setNormalsEnabled(bool use)
{
    Engine::setInt(unifUseNormals, use);
}

void Shader::setUseBlending(bool use)
{
    if(use)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glBlendEquation(GL_FUNC_ADD);
    }
    else
    {
        glDisable(GL_BLEND);
    }
}

void Shader::pop()
{
    model.pop();
}

void Shader::push()
{
    model.push();
}

void Shader::setToIdentity()
{
    model.get().setToIdentity();
}

void Shader::scale(float sx, float sy, float sz)
{
    model.get().scale(sx, sy, sz);
}

void Shader::translate(float tx, float ty, float tz)
{
    model.get().translate(tx, ty, tz);
}

void Shader::translateX(float tx)
{
    model.get().translateX(tx);
}

void Shader::translateY(float ty)
{
    model.get().translateY(ty);
}

void Shader::translateZ(float tz)
{
    model.get().translateZ(tz);
}

void Shader::translateTo(float tx, float ty, float tz)
{
    model.get().translateTo(tx, ty, tz);
}

void Shader::rotate(float xDegrees, float yDegrees, float zDegrees)
{
    model.get().rotate(xDegrees, yDegrees, zDegrees);
}

void Shader::rotateX(float degrees)
{
    model.get().rotateX(degrees);
}

void Shader::rotateY(float degrees)
{
    model.get().rotateY(degrees);
}

void Shader::rotateZ(float degrees)
{
    model.get().rotateZ(degrees);
}

void Shader::updateModelMatrix()
{
    Engine::setMatrix(unifModelMatrix, model.get().getValues());
}