#include "VoxelProgram.hpp"

#include "../Renderer.hpp"

using namespace pass;

VoxelProgram::VoxelProgram(const VoxelProgram::options& opts, std::vector<std::string> flags): Program() {

    if (opts.pbr)
        flags.emplace_back("PBR");
    if (opts.planar == render::Planar::Biplanar)
        flags.emplace_back("BIPLANAR");
    if (opts.planar == render::Planar::Triplanar)
        flags.emplace_back("TRIPLANAR");
    if (opts.stochastic)
        flags.emplace_back("STOCHASTIC");
    if (opts.fog)
        flags.emplace_back("FOG");
    if (opts.geometry) {
        flags.emplace_back("GEOMETRY");
        if (opts.blend)
            flags.emplace_back("BLEND");
    }
    if (opts.curvature) {
        flags.emplace_back("CURVATURE");
        if (opts.curv_depth)
            flags.emplace_back("CURV_DEPTH");
    }

    std::vector<Shader*> shaders;
    shaders.push_back(loadShader(GL_VERTEX_SHADER, flags));
    shaders.push_back(loadShader(GL_FRAGMENT_SHADER, flags));
    if (opts.geometry)
        shaders.push_back(loadShader(GL_GEOMETRY_SHADER, flags));
    load(shaders);

    ViewMatrixID = glGetUniformLocation(ProgramID, "View");
    ProjMatrixID = glGetUniformLocation(ProgramID, "Proj");

    TextureID = glGetUniformLocation(ProgramID, "TextureAtlas");
    NormalID = glGetUniformLocation(ProgramID, "NormalAtlas");
    HOSID = glGetUniformLocation(ProgramID, "HOSAtlas");

    LightInvDirID = glGetUniformLocation(ProgramID, "LightInvDirection_worldspace");

    FogDepthID = glGetUniformLocation(ProgramID, "FogDepth"); //MAYBE: merge as vec4
    FogColorID = glGetUniformLocation(ProgramID, "FogColor");

    SphereProjID = glGetUniformLocation(ProgramID, "SphereProj");
    CurvatureID = glGetUniformLocation(ProgramID, "Curvature");
}

VoxelProgram::~VoxelProgram() { }

std::string VoxelProgram::getName() const {
    return "Voxel";
}
void VoxelProgram::start(render::gl::Renderer *renderer) {
    bindTexture(renderer->getTextureAtlas());
    bindNormal(renderer->getNormalAtlas());
    bindHOS(renderer->getHOSAtlas());
    setLightInvDir(&renderer->LightInvDir[0]);
    setFog(&renderer->FogColor[0], renderer->FogDepth);
    setView(&renderer->getViewMatrix()[0][0]);
    setProj(&renderer->getProjectionMatrix()[0][0]);
}
void VoxelProgram::setup(glm::vec4 sphereProj, float curvature) {
    setSphereProj(&sphereProj[0]);
    setCurvature(curvature);
}

void VoxelProgram::setProj(const GLfloat *matrix) {
    glUniformMatrix4fv(ProjMatrixID, 1, GL_FALSE, matrix);
}
void VoxelProgram::setView(const GLfloat *matrix) {
    glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, matrix);
}

void VoxelProgram::bindTexture(GLuint textureID) {
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D_ARRAY, textureID);
    glUniform1i(TextureID, 0);
}
void VoxelProgram::bindNormal(GLuint textureID) {
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D_ARRAY, textureID);
    glUniform1i(NormalID, 1);
}
void VoxelProgram::bindHOS(GLuint textureID) {
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D_ARRAY, textureID);
    glUniform1i(HOSID, 2);
}

void VoxelProgram::setLightInvDir(const GLfloat *pos) {
    glUniform3fv(LightInvDirID, 1, pos);
}
void VoxelProgram::setFog(const GLfloat *color, const GLfloat depth) {
    glUniform3fv(FogColorID, 1, color);
    glUniform1f(FogDepthID, depth);
}

void VoxelProgram::setSphereProj(const GLfloat *posRad) {
    glUniform4fv(SphereProjID, 1, posRad);
}
void VoxelProgram::setCurvature(GLfloat ratio) {
    glUniform1f(CurvatureID, ratio);
}