#include "resources/image.hpp"

#include "utils/assert.hpp"
#include <cstddef>
#include <cstdint>
#include <stb_image.h>
#include <stb_image_resize2.h>

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

using namespace kuiper::resource;

image image::make_blank(std::uint32_t width, std::uint32_t height, std::uint32_t n_channels) noexcept {
    const std::size_t               size = width * height * n_channels;
    const std::vector<std::uint8_t> pixel_data(size, 0u);

    return image {width, height, n_channels, pixel_data.data(), pixel_data.size()};
}

std::expected<image, image_error> image::from_path(const std::filesystem::path& path,
                                                   std::uint8_t                 num_channels) noexcept {
    if (path.empty() || !std::filesystem::is_regular_file(path) || !std::filesystem::exists(path))
        return std::unexpected(image_error {"invalid path"});

    auto       input_file = std::ifstream(path, std::ios::binary | std::ios::ate);
    const auto file_size  = input_file.tellg();

    std::vector<std::uint8_t> buf(file_size);
    input_file.read((char*) buf.data(), file_size);
    input_file.close();

    return image::from_memory(buf.data(), buf.size(), num_channels);
}

std::expected<image, image_error> image::from_memory(const std::uint8_t* data,
                                                     std::size_t         len,
                                                     std::uint8_t        num_channels) noexcept {
    stbi_set_flip_vertically_on_load(false);

    int width      = 0;
    int height     = 0;
    int n_channels = 0;

    auto decoded_image_data =
        stbi_load_from_memory(data, static_cast<int>(len), &width, &height, &n_channels, num_channels);

    if (!decoded_image_data)
        return std::unexpected(image_error {stbi_failure_reason()});

    image ret {static_cast<std::uint32_t>(width),
               static_cast<std::uint32_t>(height),
               static_cast<std::uint32_t>(n_channels),
               decoded_image_data,
               static_cast<std::uint32_t>(width * height * num_channels)};

    stbi_image_free(decoded_image_data);

    return ret;
}

void image::resize(std::uint32_t width, std::uint32_t height) noexcept {
    std::vector<std::uint8_t> out_buf(width * height * m_channels);

    stbir_pixel_layout pixel_layout {STBIR_RGBA};

    switch (m_channels) {
    case 4:
        pixel_layout = stbir_pixel_layout::STBIR_RGBA;
        break;
    case 3:
        pixel_layout = stbir_pixel_layout::STBIR_RGB;
        break;
    case 2:
        pixel_layout = stbir_pixel_layout::STBIR_2CHANNEL;
        break;
    case 1:
        pixel_layout = stbir_pixel_layout::STBIR_1CHANNEL;
        break;
    default:
        pixel_layout = stbir_pixel_layout::STBIR_RGBA;
        break;
    }

    if (!stbir_resize_uint8_srgb(m_data.data(),
                                 static_cast<int>(m_width),
                                 static_cast<int>(m_height),
                                 0, // packed in memory
                                 out_buf.data(),
                                 static_cast<int>(width),
                                 static_cast<int>(height),
                                 0, // packed in memory
                                 pixel_layout))
        KUIPER_ASSERT(false);

    m_data   = out_buf;
    m_width  = width;
    m_height = height;

    return;
}
