#include <Redacted3D/types/shader.h>
#include <Redacted3D/engine/engine.h>
#include <iostream>
#include <fstream>
#include <sstream>

void compilationError(GLuint shader) {
    GLint success;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);

    if (!success) {
        GLchar info[512];
        glGetShaderInfoLog(shader, sizeof(info), nullptr, info);
        std::cerr << info << std::endl;
        engine->setError(ENGINE_ERROR_CODE::SHADER_COMPILATION_ERROR, true);
    }
}

void linkingError(GLuint shader) {
    GLint success;
    glGetProgramiv(shader, GL_LINK_STATUS, &success);

    if (!success) {
        GLchar info[512];
        glGetProgramInfoLog(shader, sizeof(info), nullptr, info);
        std::cerr << info << std::endl;
        engine->setError(ENGINE_ERROR_CODE::SHADER_LINKING_ERROR, true);
    }
}

void Shader::load(std::string filePath, GLenum type) {
    std::ifstream file(filePath);

    if (!file.is_open())
        engine->setError(ENGINE_ERROR_CODE::SHADER_NOT_FOUND, true);

    if (type != GL_VERTEX_SHADER && type != GL_FRAGMENT_SHADER)
        engine->setError(ENGINE_ERROR_CODE::INVALID_SHADER_TYPE, true);

    std::stringstream buffer;
    buffer << file.rdbuf();
    file.close();

    std::string shaderSource = buffer.str();
    const GLchar* source = shaderSource.c_str();

    if (type == GL_VERTEX_SHADER) {
        vertex = glCreateShader(type);
        glShaderSource(vertex, 1, &source, nullptr);
        glCompileShader(vertex);
        compilationError(vertex);
    }

    if (type == GL_FRAGMENT_SHADER) {
        fragment = glCreateShader(type);
        glShaderSource(fragment, 1, &source, nullptr);
        glCompileShader(fragment);
        compilationError(fragment);
    }
}

void Shader::link() {
    if (id != NULL || vertex == NULL && fragment == NULL)
        engine->setError(ENGINE_ERROR_CODE::SHADER_LINKING_ERROR, true);

    id = glCreateProgram();
    if (vertex != NULL)
        glAttachShader(id, vertex);
    if (fragment != NULL)
        glAttachShader(id, fragment);

    glLinkProgram(id);
    linkingError(id);
    engine->world->shaderList.push_back(*this);
}

Shader::Shader(const char* name, const std::string& vertexPath, const std::string& fragmentPath) {
	this->name = name;
	load(vertexPath, GL_VERTEX_SHADER);
    load(fragmentPath, GL_FRAGMENT_SHADER);
    link();
}

Shader::Shader(const char *name, const std::string &filePath, const GLenum &type) {
    this->name = name;
    load(filePath, type);
    link();
}

void Shader::erase(Shader* shader) {
    if (shader->fragment != 0)
        glDeleteProgram(shader->fragment);

    if (shader->vertex != 0)
        glDeleteProgram(shader->vertex);

    if(shader->id != 0)
        glDeleteShader(shader->id);

}
