#include <fstream>
#include <cstring>

#include "client/rendering/Shader.h"
#include "client/Utils.h"

Shader::Shader(const GLchar* vPath, const GLchar* fPath) : valid(false), 
        vShader(0), fShader(0), program(0)
{
    std::vector<GLchar> vCode;
    std::vector<GLchar> fCode;
    bool vCheck = readFile(vCode, vPath);
    bool fCheck = readFile(fCode, fPath);
    if(vCheck || fCheck)
    {
        return;
    }
    
    vCheck = compileShader(vShader, vCode.data(), GL_VERTEX_SHADER);
    fCheck = compileShader(fShader, fCode.data(), GL_FRAGMENT_SHADER);
    if(vCheck || fCheck)
    {
        return;
    }
    
    program = glCreateProgram();
    glAttachShader(program, vShader);
    glAttachShader(program, fShader);
    glLinkProgram(program);
    
    if(checkAndPrintError("shader linking error"))
    {
        return;
    }

    GLint linked;
    glGetProgramiv(program, GL_LINK_STATUS, &linked);
    if(linked == GL_FALSE)
    {
        GLchar buffer[512];
        glGetProgramInfoLog(program, 512, nullptr, buffer);
        std::cout << "programm linking info log: " << buffer << "\n";
        return;
    }
    valid = true;
}

Shader::~Shader()
{
    glDeleteProgram(program);
    glDeleteShader(vShader);
    glDeleteShader(fShader);
}

bool Shader::isValid() const
{
    return valid;
}

bool Shader::readFile(std::vector<GLchar>& buffer, const GLchar* path) const
{
    std::ifstream in;
    in.open(path);
    if(in.fail())
    {
        std::cout << "cannot read shader file: '" << path << "'\n"; 
        return true;
    }
    while(true)
    {
        GLchar c = in.get();
        if(in.eof())
        {
            break;
        }
        buffer.push_back(c);
    }
    buffer.push_back('\0');
    return false;
}

bool Shader::compileShader(GLuint& shader, const GLchar* code, GLenum shaderType) const
{
    shader = glCreateShader(shaderType);
    glShaderSource(shader, 1, &code, nullptr);
    glCompileShader(shader);

    if(checkAndPrintError("shader error"))
    {
        return true;
    }
    
    GLint compiled;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
    if(compiled == GL_FALSE)
    {
        GLchar buffer[512];
        glGetShaderInfoLog(shader, 512, nullptr, buffer);
        std::cout << "shader info log: " << buffer << "\n";
        return true;
    }
    return false;
}

void Shader::use() const
{
    glUseProgram(program);
}

void Shader::setMatrix(const GLchar* name, const float* data) const
{
    glUniformMatrix4fv(glGetUniformLocation(program, name), 1, GL_FALSE, data);
}

void Shader::setInt(const GLchar* name, GLint data) const
{
    glUniform1i(glGetUniformLocation(program, name), data);
}