//
// Created by sebastian on 29.07.26.
//

#include "ShaderProgram.h"
#include <glad/glad.h>
#include <fstream>
#include <functional>
#include <sstream>
#include <stdexcept>
using namespace engine;

std::string ShaderProgram::loadSource(const std::string &filePath) {
    std::ifstream file(filePath);
    if (!file.is_open()) {
        throw std::runtime_error("Could not open shader file: " + filePath);
    }
    std::stringstream buffer;
    buffer << file.rdbuf();
    return buffer.str();
}

unsigned int ShaderProgram::compileShader(unsigned int type, const std::string &source, const std::string &debugPath) {
    unsigned int id = glCreateShader(type);
    const char* src = source.c_str();
    glShaderSource(id, 1, &src, nullptr);
    glCompileShader(id);

    int success;
    glGetShaderiv(id, GL_COMPILE_STATUS, &success);
    if (!success) {
        char log[512];
        glGetShaderInfoLog(id, 512, nullptr, log);
        throw std::runtime_error("Shader compile error (" + debugPath + "): " + log);
    }
    return id;
}

ShaderProgram::ShaderProgram(const std::string &vertexShader, const std::string &fragmentShader) {
    unsigned int vertexShaderId = compileShader(GL_VERTEX_SHADER, loadSource(vertexShader), vertexShader);
    unsigned int fragmentShaderId = compileShader(GL_FRAGMENT_SHADER, loadSource(fragmentShader), fragmentShader);

    m_programId = glCreateProgram();
    glAttachShader(m_programId, vertexShaderId);
    glAttachShader(m_programId, fragmentShaderId);
    glLinkProgram(m_programId);

    int success;
    glGetProgramiv(m_programId, GL_LINK_STATUS, &success);
    if (!success) {
        char log[512];
        glGetProgramInfoLog(m_programId, 512, nullptr, log);
        throw std::runtime_error(std::string("Shader link error: ") + log);
    }

    glDetachShader(m_programId, vertexShaderId);
    glDetachShader(m_programId, fragmentShaderId);
    glDeleteShader(vertexShaderId);
    glDeleteShader(fragmentShaderId);
}

void ShaderProgram::storeAllUniformLocations(const std::initializer_list<std::reference_wrapper<Uniform>> uniforms) const {
    for (const auto& uniform : uniforms) {
        uniform.get().storeUniformLocation(m_programId);
    }
    glValidateProgram(m_programId);
}

ShaderProgram::~ShaderProgram() {
    glDeleteProgram(m_programId);
}

void ShaderProgram::start() const {
    glUseProgram(m_programId);
}

void ShaderProgram::stop() const {
    glUseProgram(0);
}

