#include <fstream>
#include <iostream>

#include "client/rendering/wrapper/Shader.h"
#include "client/rendering/wrapper/GLWrapper.h"

Shader::Shader(const GLchar* vPath, const GLchar* fPath) : vShader(0), fShader(0), program(0) {
    if(readFileAndCompile(vPath, vShader, GL_VERTEX_SHADER) || readFileAndCompile(fPath, fShader, GL_FRAGMENT_SHADER)) {
        return;
    }
    program = glCreateProgram();
    glAttachShader(program, vShader);
    glAttachShader(program, fShader);
    glLinkProgram(program);
    if(GLWrapper::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;
    }
}

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

bool Shader::hasError() const {
    return vShader == 0 || fShader == 0 || program == 0;
}

bool Shader::readFileAndCompile(const GLchar* path, GLuint& shader, GLenum shaderType) {
    GLchar buffer[8192];
    if(readFile(buffer, 8192, path)) {
        return true;
    }
    return compile(shader, buffer, shaderType);
}

bool Shader::readFile(GLchar* buffer, size_t bufferSize, const GLchar* path) const {
    std::ifstream in;
    in.open(path);
    if(in.fail()) {
        std::cout << "cannot read shader file: '" << path << "'\n";
        return true;
    }
    in.get(buffer, bufferSize, EOF);
    return false;
}

bool Shader::compile(GLuint& shader, const GLchar* code, GLenum shaderType) {
    shader = glCreateShader(shaderType);
    glShaderSource(shader, 1, &code, nullptr);
    glCompileShader(shader);
    if(GLWrapper::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 GLfloat* data) const {
    glUniformMatrix4fv(glGetUniformLocation(program, name), 1, GL_FALSE, data);
}

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

void Shader::setFloat(const GLchar* name, GLfloat data) const {
    glUniform1f(glGetUniformLocation(program, name), data);
}