//
// Created by slinky on 5/1/26.
//

#include "ShaderManager.h"

#include "glad/gl.h"

#include <fstream>
#include <sstream>

#include <spdlog/spdlog.h>

void log_shader_compile_errors(GLuint shader, std::string_view shaderType);
void log_program_compile_errors(GLuint program);

std::unordered_map<std::string, std::shared_ptr<ShaderProgram>> ShaderManager::shaders;

std::shared_ptr<ShaderProgram> ShaderManager::load(std::string_view vertexPath, std::string_view fragmentPath)
{
    std::string vertexSource;
    std::string fragmentSource;

    std::ifstream vertexFile {};
    std::ifstream fragmentFile {};

    try
    {
        vertexFile.open(vertexPath.data());
        fragmentFile.open(fragmentPath.data());

        std::stringstream vertexSourceStream {};
        vertexSourceStream << vertexFile.rdbuf();
        vertexSource = vertexSourceStream.str();

        std::stringstream fragmentSourceStream {};
        fragmentSourceStream << fragmentFile.rdbuf();
        fragmentSource = fragmentSourceStream.str();

        vertexFile.close();
        fragmentFile.close();
    } catch (std::ifstream::failure& e) {
        spdlog::error("Failed to load shader program {}, {}: {}", vertexPath, fragmentPath, std::string(e.what()).c_str());
        return nullptr;
    }

    unsigned int vertex;
    unsigned int fragment;

    vertex = glCreateShader(GL_VERTEX_SHADER);
    const char* vertex_str = vertexSource.data();
    glShaderSource(vertex, 1, &vertex_str, NULL);
    glCompileShader(vertex);
    log_shader_compile_errors(vertex, "vertex");

    fragment = glCreateShader(GL_FRAGMENT_SHADER);
    const char* frag_str = fragmentSource.data();
    glShaderSource(fragment, 1, &frag_str, NULL);
    glCompileShader(fragment);
    log_shader_compile_errors(fragment, "fragment");

    int shaderId = glCreateProgram();

    glAttachShader(shaderId, vertex);
    glAttachShader(shaderId, fragment);
    glLinkProgram(shaderId);
    log_program_compile_errors(shaderId);

    glDeleteShader(vertex);
    glDeleteShader(fragment);

    return std::make_shared<ShaderProgram>(ShaderProgram(shaderId));
}

void log_shader_compile_errors(GLuint shader, std::string_view shaderType)
{
    GLint success;
    GLchar infoLog[1024];
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(shader, 1024, NULL, infoLog);
        spdlog::error("Failed to compile {} shader: {}", shaderType, infoLog);
    }
}

void log_program_compile_errors(GLuint program)
{
    GLint success;
    GLchar infoLog[1024];
    glGetProgramiv(program, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(program, 1024, NULL, infoLog);
        spdlog::error("Failed to compile program: {}", infoLog);
    }
}