#include "Shader.h"
#include "../core/utils/Logging.h"
#include "../core/utils/utils.h"

#include <fstream>
#include <sstream>

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

void Shader::Clear() {
    if (id != 0) {
        Logger::LogVerbose("[Shader] Deleting program ID: %u", id);
        glDeleteProgram(id);
        id = 0;
    }
    uniformCache.clear();
}

std::string Shader::ReadFile(const std::string& path) {
    std::ifstream stream(path);
    std::stringstream buffer;
    buffer << stream.rdbuf();

    std::string filename = GetFilenameFromPath(path);
    Logger::LogVerbose("[Shader] ReadFile: %s", filename.c_str());
    Logger::LogVerbose("[Shader] Size (%zu bytes)", buffer.str().size());

    return buffer.str();
}

GLuint Shader::Compile(GLenum type, const std::string& source) {
    Logger::LogVerbose("[Shader] Compiling %s shader...",
                       type == GL_VERTEX_SHADER ? "Vertex" : "Fragment");

    GLuint shader = glCreateShader(type);
    const char* src = source.c_str();
    glShaderSource(shader, 1, &src, nullptr);
    glCompileShader(shader);

    GLint success;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (!success) {
        char info[512];
        glGetShaderInfoLog(shader, 512, nullptr, info);
        Logger::LogError("[Shader] Type %s:\n%s",
                         type == GL_VERTEX_SHADER ? "Vertex" : "Fragment", info);
    } else {
        Logger::LogVerbose("[Shader] Compiled %s shader successfully.",
                           type == GL_VERTEX_SHADER ? "Vertex" : "Fragment");
    }


    return shader;
}

bool Shader::LoadFromFile(const std::string& vertexPath, const std::string& fragmentPath) {
    Logger::LogVerbose("[Shader] Loading from file: %s + %s", vertexPath.c_str(), fragmentPath.c_str());
    return LoadFromSource(ReadFile(vertexPath), ReadFile(fragmentPath));
}

bool Shader::LoadFromSource(const std::string& vertexSrc, const std::string& fragmentSrc) {
    Clear();

    GLuint vertex = Compile(GL_VERTEX_SHADER, vertexSrc);
    GLuint fragment = Compile(GL_FRAGMENT_SHADER, fragmentSrc);

    id = glCreateProgram();
    glAttachShader(id, vertex);
    glAttachShader(id, fragment);
    glLinkProgram(id);

    glDeleteShader(vertex);
    glDeleteShader(fragment);

    GLint success;
    glGetProgramiv(id, GL_LINK_STATUS, &success);
    if (!success) {
        char info[512];
        glGetProgramInfoLog(id, 512, nullptr, info);
        Logger::LogError("[Shader]]:\n%s", info);
        return false;
    }

    Logger::LogVerbose("[Shader] Linked shader program successfully (ID: %u)", id);
    return true;
}

void Shader::Use() const {
    glUseProgram(id);
}

GLuint Shader::GetID() const {
    return id;
}

GLint Shader::GetUniformLocation(const std::string& name) {
    auto it = uniformCache.find(name);
    if (it != uniformCache.end())
        return it->second;

    GLint loc = glGetUniformLocation(id, name.c_str());
    if (loc == -1)
        Logger::LogWarning("[Shader] Uniform not found: %s", name.c_str());

    uniformCache[name] = loc;
    return loc;
}
