#include "GL/gl3w.h"
#include "GL/gl.h"
#include "Shader.hpp"
#include "Debug.hpp"

TSE::uint TSE::OpenGL::Shader::activeProgramID = 0;

void TSE::OpenGL::Shader::Bind() const
{
    Enable(true);
}

void TSE::OpenGL::Shader::Unbind() const
{
    Disable(true);
}

void TSE::OpenGL::Shader::Enable(bool notify) const
{
    activeProgramID = programID;
    glUseProgram(programID);
    if(notify) OnEnable();
}

void TSE::OpenGL::Shader::Disable(bool notify) const
{
    activeProgramID = 0;
    glUseProgram(0);
    if(notify) OnDisable();
}

void TSE::OpenGL::Shader::Flush()
{
    OnFlush();
}

void TSE::OpenGL::Shader::DrawCall(int indexCount)
{
    OnDrawCall(indexCount);
}

void TSE::OpenGL::Shader::PostDraw()
{
    OnPostDraw();
}

void TSE::OpenGL::Shader::Submit(const Transformable &t, float *&target, TransformationStack &stack, void (*restartDrawcall)(IRenderer &), IRenderer &rnd)
{
    OnSubmit(t, target, stack, restartDrawcall, rnd);
}

bool TSE::OpenGL::Shader::IsEnabled() const
{
    return programID == activeProgramID;
}

int TSE::OpenGL::Shader::packageSize()
{
    return PackageSize;
}

TSE::OpenGL::Shader::Shader(const std::vector<std::unique_ptr<ShaderPart>> &parts)
{
    programID = glCreateProgram();

    for (const auto& part : parts) {
        glAttachShader(programID, part->shaderPartID);
    }

    glLinkProgram(programID);

    int success;

    glGetProgramiv(programID, GL_LINK_STATUS, &success);

    if(!success)
    {
        char log[512];
        glGetProgramInfoLog(programID, 512, nullptr, log);
        TSE_ERROR(log);
    }

    for (const auto& part : parts) {
        glDetachShader(programID, part->shaderPartID);
    }
}

TSE::OpenGL::Shader::~Shader()
{
    glDeleteProgram(programID);
}

int TSE::OpenGL::Shader::GetUniformLocation(const char *name)
{
    auto it = uniformLocations.find(name);
    if (it != uniformLocations.end()) return it->second;

    int loc = glGetUniformLocation(programID, name);
    uniformLocations[name] = loc;
    return loc;
}

void TSE::OpenGL::Shader::SetUniform(const char *name, int value)
{
    glUniform1i(GetUniformLocation(name), value);
}

void TSE::OpenGL::Shader::SetUniform(const char *name, const int *value, const int count)
{
    glUniform1iv(GetUniformLocation(name), count, value);
}

void TSE::OpenGL::Shader::SetUniform(const char *name, const Matrix4x4 *value)
{
    float colmbMajor[16];
    value->ToArrayColumnMajor(colmbMajor);
    glUniformMatrix4fv(GetUniformLocation(name),1, false, colmbMajor);
}

void TSE::OpenGL::Shader::SetUniform(const char *name, float value)
{
    glUniform1f(GetUniformLocation(name), value);
}

void TSE::OpenGL::Shader::SetUniform(const char *name, const float *value, const int count)
{
    glUniform1fv(GetUniformLocation(name), count, value);
}

void TSE::OpenGL::Shader::SetUniform(const char *name, const Vector2 *value)
{
    glUniform2f(GetUniformLocation(name), value->x, value->y);
}

void TSE::OpenGL::Shader::SetUniform(const char *name, const Vector3 *value)
{
    glUniform3f(GetUniformLocation(name), value->x, value->y, value->z);
}

void TSE::OpenGL::Shader::SetUniform(const char *name, const Vector4 *value)
{
    glUniform4f(GetUniformLocation(name), value->x, value->y, value->z, value->w);
}
