#include "graphics/opengl/shader.hpp"

#include <filesystem>
#include <fstream>
#include <string_view>
#include <vector>

using namespace kuiper::gl;

shader::s_ptr shader::make(shader_type type) noexcept {

    GLenum gl_type = 0;
    switch (type) {
    case shader_type::vertex:
        gl_type = GL_VERTEX_SHADER;
        break;
    case shader_type::fragment:
        gl_type = GL_FRAGMENT_SHADER;
        break;
    case shader_type::compute:
        gl_type = GL_COMPUTE_SHADER;
        break;
    default:
        return nullptr;
    }

    GLuint name = glCreateShader(gl_type);

    if (!name)
        return nullptr;

    return std::make_shared<shader>(name, gl_type);
}

shader::s_ptr shader::from_path(const std::filesystem::path& shader_path, shader_type type) noexcept {
    if (shader_path.empty() || !std::filesystem::is_regular_file(shader_path) || !std::filesystem::exists(shader_path))
        return nullptr;

    const auto        file_size = std::filesystem::file_size(shader_path);
    std::vector<char> read_buf(file_size + 1); // +1 for NUL terminator
    std::ifstream     in_file(shader_path);    // TODO: std::ios::ate & tellg() trick

    if (!in_file.is_open())
        return nullptr;

    in_file.read(read_buf.data(), file_size);

    std::string_view file_ext(shader_path.extension().c_str());

    if (type == shader_type::none) {
        if (file_ext.compare(".vs") == 0 || file_ext.compare(".vert") == 0) {
            type = shader_type::vertex;
        } else if (file_ext.compare(".fs") == 0 || file_ext.compare(".frag") == 0) {
            type = shader_type::fragment;
        } else if (file_ext.compare(".cs") == 0 || file_ext.compare(".comp") == 0) {
            type = shader_type::compute;
        }
    }

    return shader::from_source(read_buf.data(), type);
}

shader::s_ptr shader::from_source(std::string_view source, shader_type type) noexcept {
    auto shader = shader::make(type);
    shader->attach_source(source);
    shader->compile();

    return shader;
}

void shader::destroy() noexcept {
    if (m_id) {
        glDeleteShader(m_id);
        m_id = 0;
    }

    return;
}

void shader::attach_source(std::string_view source) noexcept {
    const GLint       n_shader_sources = 1;
    const char* const s                = source.data();
    const GLint       s_len            = static_cast<GLint>(source.size());

    return glShaderSource(m_id, n_shader_sources, &s, &s_len);
}

void shader::compile() noexcept {

    return glCompileShader(m_id);
}
