#include "graphics/opengl/texture.hpp"

#include <filesystem>
#include <stb_image.h>

#include <fstream>
#include <vector>

using namespace kuiper::gl;

texture::s_ptr texture::make(texture_type type) noexcept {
    GLuint name = 0;

    glGenTextures(1, &name);

    if (!name)
        return nullptr;

    GLenum gl_target = 0;
    switch (type) {
    case texture_type::tex_2d:
        gl_target = GL_TEXTURE_2D;
        break;
    case texture_type::none:
        return nullptr;
    }

    return std::make_shared<texture>(name, gl_target);
}

texture::s_ptr texture::make_placeholder() noexcept {
    // FIXME: this might be cooked because it's endian-dependent
    static constexpr std::array<std::uint32_t, 16> data {0xff00ffff,
                                                         0xff00ffff,
                                                         0x000000ff,
                                                         0x000000ff,
                                                         0xff00ffff,
                                                         0xff00ffff,
                                                         0x000000ff,
                                                         0x000000ff,
                                                         0x000000ff,
                                                         0x000000ff,
                                                         0xff00ffff,
                                                         0xff00ffff,
                                                         0x000000ff,
                                                         0x000000ff,
                                                         0xff00ffff,
                                                         0xff00ffff};

    auto tex = texture::make();
    tex->upload(GL_RGBA, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
    tex->gen_mipmaps();

    return tex;
}

texture::s_ptr texture::from_image_path(const std::filesystem::path& image_path, bool flip_y) noexcept {
    if (image_path.empty() || !std::filesystem::is_regular_file(image_path) || !std::filesystem::exists(image_path))
        return nullptr;

    const auto                file_size = std::filesystem::file_size(image_path);
    std::vector<std::uint8_t> read_buf(file_size);
    std::ifstream             in_file(image_path, std::ios::binary); // TODO: std::ios::ate & tellg() trick

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

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

    return texture::from_image_data(read_buf.data(), read_buf.size(), flip_y);
}

texture::s_ptr texture::from_image_data(const std::uint8_t* buffer, std::size_t length, bool flip_y) noexcept {
    if (buffer == nullptr || length == 0)
        return nullptr;

    stbi_set_flip_vertically_on_load(flip_y);

    int           width            = 0;
    int           height           = 0;
    int           channels_in_file = 0;
    std::uint8_t* image_data =
        stbi_load_from_memory(buffer, static_cast<int>(length), &width, &height, &channels_in_file, 4);

    if (!image_data)
        return nullptr;

    auto tex = texture::make();
    tex->upload(GL_RGBA, width, height, GL_RGBA, GL_UNSIGNED_BYTE, image_data);

    stbi_image_free(image_data);

    return tex;
}

void texture::destroy() noexcept {
    if (m_id) {
        glDeleteTextures(1, &m_id);
        m_id = 0;
    }

    return;
}

void texture::bind() noexcept {
    return glBindTexture(m_target, m_id);
}

void texture::upload(
    GLint internal_fmt, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* data) noexcept {
    m_width        = width;
    m_height       = height;
    m_internal_fmt = internal_fmt;
    m_pixel_fmt    = format;
    m_pixel_type   = type;

    bind();
    return glTexImage2D(m_target, 0, internal_fmt, width, height, 0, format, type, data);
}

void texture::resize(std::uint32_t width, std::uint32_t height) noexcept {
    return upload(
        m_internal_fmt, static_cast<GLsizei>(width), static_cast<GLsizei>(height), m_pixel_fmt, m_pixel_type, nullptr);
}

void texture::set_param(GLenum name, GLint param) noexcept {
    bind();

    return glTexParameteri(m_target, name, param);
}

void texture::gen_mipmaps() noexcept {
    bind();

    return glGenerateMipmap(m_target);
}
