#include "engine/shaders.hpp"
#include <iostream>
#include <fstream>
#include <SDL3/SDL_log.h>

Shader::Shader(const char* vertexPath, const char* fragmentPath) {
    // Load and compile vertex shader
    unsigned int shader = glCreateShader(GL_VERTEX_SHADER);
    if (shader == 0) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create shader.");
    }

    const char* vertex_shader_source = read_file(vertexPath);
    glShaderSource(shader, 1, &vertex_shader_source, nullptr);
    glCompileShader(shader);

    // Check for vertex shader compilation errors
    int success;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (!success) {
        char info_log[512];
        glGetShaderInfoLog(shader, sizeof(info_log), nullptr, info_log);
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Vertex shader compilation error: %s", info_log);
        glDeleteShader(shader);
    }

    // Load and compile fragment shader
    unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    if (fragment_shader == 0) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create shader.");
    }

    const char* fragment_shader_source = read_file(fragmentPath);
    glShaderSource(fragment_shader, 1, &fragment_shader_source, nullptr);
    glCompileShader(fragment_shader);

    // Check for fragment shader compilation errors
    glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
    if (!success) {
        char info_log[512];
        glGetShaderInfoLog(fragment_shader, sizeof(info_log), nullptr, info_log);
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Fragment shader compilation error: %s", info_log);
        glDeleteShader(fragment_shader);
    }

    // Create shader program
    ID = glCreateProgram();
    if (ID == 0) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create shader program.");
    }

    glAttachShader(ID, shader);
    glAttachShader(ID, fragment_shader);
    glLinkProgram(ID);

    // Check for linking errors
    glGetProgramiv(ID, GL_LINK_STATUS, &success);
    if (!success) {
        char info_log[512];
        glGetProgramInfoLog(ID, sizeof(info_log), nullptr, info_log);
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Shader program linking error: %s", info_log);
        glDeleteProgram(ID);
        ID = 0; // Reset ID to indicate failure
    }
    
    // Clean up shaders as they are no longer needed
    glDeleteShader(shader);
    glDeleteShader(fragment_shader);
}

void Shader::use() const {
    if (ID != 0) {
        glUseProgram(ID);
    } else {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Shader program not initialized.");
    }
}

void Shader::setBool(const std::string &name, bool value) const {
    glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}

void Shader::setInt(const std::string &name, int value) const {
    glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}

void Shader::setFloat(const std::string &name, float value) const {
    glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}

void Shader::setMat4(const std::string &name, const glm::mat4 &mat) const {
    glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}

Shader::~Shader() {
    if (ID != 0) {
        glDeleteProgram(ID);
        ID = 0; // Reset ID to indicate the shader program has been deleted
    }
}

const char* Shader::read_file(const char* filePath) {
    std::ifstream file(filePath);
    if (!file.is_open()) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to open shader file: %s", filePath);
        return "";
    }
    std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
    file.close();
    char* buffer = new char[content.size() + 1];
    std::copy(content.begin(), content.end(), buffer);
    buffer[content.size()] = '\0';
    return buffer;
}