#include <fstream>
#include <iostream>

#include "rendering/Shader.h"
#include "wrapper/GL.h"

Shader::Shader(const char* vertexPath, const char* fragmentPath,
               const char* geometryPath)
    : vertexShader(0), geometryShader(0), fragmentShader(0), program(0) {
    if(compile(vertexPath, vertexShader, GL::VERTEX_SHADER) ||
       compile(fragmentPath, fragmentShader, GL::FRAGMENT_SHADER)) {
        return;
    }
    if(geometryPath != nullptr &&
       compile(geometryPath, geometryShader, GL::GEOMETRY_SHADER)) {
        return;
    }
    program = GL::createProgram();
    GL::attachShader(program, vertexShader);
    if(geometryPath != nullptr) {
        GL::attachShader(program, geometryShader);
    }
    GL::attachShader(program, fragmentShader);
    GL::linkProgram(program);
    if(GL::printError("cannot link")) {
        return;
    }
    if(GL::logLinkerError(program)) {
        clean();
        return;
    }
}

Shader::~Shader() {
    clean();
}

void Shader::clean() {
    GL::deleteShader(vertexShader);
    GL::deleteShader(geometryShader);
    GL::deleteShader(fragmentShader);
    GL::deleteProgram(program);
    program = 0;
}

bool Shader::compile(const char* path, GL::Shader& s, GL::ShaderType st) {
    std::cout << "shader: " << path << '\n';
    List<char> code;
    if(readFile(code, path)) {
        return true;
    }
    return compile(s, code, st);
}

bool Shader::readFile(List<char>& code, const char* path) const {
    std::ifstream in;
    in.open(path);
    if(!in.good()) {
        std::cout << "cannot read file\n";
        return true;
    }
    while(true) {
        int c = in.get();
        if(c == EOF) {
            break;
        }
        code.add(c);
    }
    code.add('\0');
    return false;
}

bool Shader::hasError() const {
    return vertexShader == 0 || fragmentShader == 0 || program == 0;
}

bool Shader::compile(GL::Shader& s, const List<char>& code, GL::ShaderType st) {
    s = GL::createShader(st);
    GL::compileShader(s, code.begin());
    if(GL::printError("compile error")) {
        return true;
    }
    return GL::logCompileError(s);
}

void Shader::use() const {
    GL::useProgram(program);
}

void Shader::setMatrix(const char* name, const float* data) {
    GL::setMatrix(program, name, data);
}

void Shader::setInt(const char* name, int data) {
    GL::setInt(program, name, data);
}

void Shader::setFloat(const char* name, float data) {
    GL::setFloat(program, name, data);
}

void Shader::setVector(const char* name, const Vector2& v) {
    GL::set2Float(program, name, &(v[0]));
}

void Shader::setVector(const char* name, const Vector3& v) {
    GL::set3Float(program, name, &(v[0]));
}

void Shader::setVector(const char* name, const Vector4& v) {
    GL::set4Float(program, name, &(v[0]));
}