/*
 * Copyright 2023 Hartmut Seichter
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

// based on image_loader by Robin Rottstädt

#include "paradiso/bitmap_io.hpp"
#include "paradiso/bitmap.hpp"

#include <unordered_map>

// STB image loading
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#include <filesystem>
#include <iostream>

namespace paradiso {

struct BitmapIO::Impl {

    std::filesystem::path asset_path_ = {};

    using BitmapCacheType = std::unordered_map<std::string_view, Bitmap>;

    BitmapCacheType cache_;

    static Bitmap read(std::string_view filename) {

        // Load with stb_image
        stbi_set_flip_vertically_on_load(true); // flip y axis for OpenGL

        int width{}, height{}, channels{};

        auto stb_free = [](uint8_t* data) { stbi_image_free(data); };

        if (std::unique_ptr<uint8_t, decltype(stb_free)> image{
                stbi_load(filename.data(), &width, &height, &channels, 0)};
            image.get()) {

            int size = width * height;

            // Convert to Vector of RGBA
            std::vector<paradiso::RGBA> rgba =
                std::vector<paradiso::RGBA>(size);
            for (int i = 0; i < size; i++) {
                // get rgba values
                int pos = i * 4;
                int r = image.get()[pos + 0];
                int g = image.get()[pos + 1];
                int b = image.get()[pos + 2];
                int a = image.get()[pos + 3];

                // bug in from_rgba. it expects bgra, not rgba
                auto val = paradiso::RGBA::from_rgba(b, g, r, a);
                rgba[i] = val;
            }

            // rotate image by 90 degrees CW
            std::vector<paradiso::RGBA> rotated =
                std::vector<paradiso::RGBA>(size);
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++) {

                    int pos = y * width + x;
                    int pos_rotated = x * height + (height - y - 1);
                    rotated[pos_rotated] = rgba[pos];
                }
            }

            return paradiso::Bitmap::from_data(
                paradiso::Size{static_cast<unsigned int>(height),
                               static_cast<unsigned int>(width)},
                rotated);
        } else {
            std::cerr << "cannot load " << filename << '\n';
        }

        return Bitmap(); //  well, no exception ...
    }

    Bitmap load(std::string_view filename) {
        if (cache_.find(filename) == cache_.end()) {

            auto asset_filepath = asset_path_ / std::filesystem::path(filename);

            std::cout << "Loading " << asset_filepath << std::endl;

            // moving to C++23 this should be replaced with
            // std::expected
            auto data = read(asset_filepath.generic_string());

            cache_[filename] = data;
        }
        return cache_[filename.data()];
    }
};

BitmapIO::BitmapIO() : impl_{std::make_unique<BitmapIO::Impl>()} {}

BitmapIO& BitmapIO::get() {
    static BitmapIO instance;
    return instance;
}

Bitmap BitmapIO::load(std::string_view filename, bool ignore_cache) const {
    return impl_->load(filename);
}

void BitmapIO::set_path(std::string_view path)
{
    impl_->asset_path_ = path;
}

std::string BitmapIO::path() const
{
    return impl_->asset_path_.generic_string();
}


} // namespace paradiso