/*
 *  <Short Description>
 *  Copyright (C) 2023  Brett Terpstra
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
#include <blt/gfx/renderer/resource_manager.h>
#include <blt/logging/logging.h>
#include <blt/std/thread.h>
#include <filesystem>

namespace blt::gfx
{
    
    std::mutex log_lock;
#define LOG_LOCKED(log) do { std::scoped_lock lock(log_lock); log; } while(0)
    
    void resource_manager::cleanup()
    {
        for (const auto& p : textures_2d)
            delete p.second;
        for (auto& t : texture_arrays_2d)
            delete t.second;
    }
    
    void resource_manager::load_resources(std::size_t threads)
    {
#ifndef __EMSCRIPTEN__
        blt::thread_pool<false> pool{threads, [&]() {
            texture_info texture;
            texture_destination destination;
            {
                std::scoped_lock lock(load_lock);
                if (textures_to_load.empty())
                    return;
                destination = textures_to_load.back().first;
                texture = std::move(textures_to_load.back().second);
                textures_to_load.pop_back();
            }
            auto path = resource_prefix + texture.get_path();
            LOG_LOCKED(BLT_DEBUG("Loading texture file '{}'", path.c_str()));
            if (!std::filesystem::exists(path))
            {
                LOG_LOCKED(BLT_WARN("Texture '%s' does not exist on disk!", path.c_str()));
                return;
            }
            auto* file = new texture_file(path, texture.get_name(), texture.get_desired_channels());
            file->resize(texture.get_desired_width(), texture.get_desired_height());
            {
                std::scoped_lock lock(save_lock);
                destination.add(file);
            }
        }};
        do
        {
            {
                std::scoped_lock lock(load_lock);
                if (textures_to_load.empty())
                    break;
            }
            {
                std::scoped_lock lock(save_lock);
                load_to_gl();
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        } while (true);
#else
        for (const auto& [destination, texture] : textures_to_load)
        {
            auto path = resource_prefix + texture.get_path();
            auto* file = new texture_file(path, texture.get_name(), texture.get_desired_channels());
            file->resize(texture.get_desired_width(), texture.get_desired_height());
            BLT_DEBUG("Loading texture file %s", path.c_str());
            destination.add(file);
        }
        textures_to_load.clear();
#endif

#ifndef __EMSCRIPTEN__
        pool.stop();
        while (!pool.complete());
#endif
        load_to_gl();
    }
    
    void resource_manager::load_to_gl()
    {
        while (!loaded_textures.empty())
        {
            auto* back = loaded_textures.back();
            textures_2d.insert({back->getName(), new texture_gl2D(back->texture())});
            LOG_LOCKED(BLT_DEBUG("Loaded texture '{}'", back->getName().c_str()));
            delete back;
            loaded_textures.pop_back();
        }
        while (!loaded_arrays.empty())
        {
            auto& back = loaded_arrays.back();
            auto& reference_v = back.second.back();
            // textures in an array are all the same size. we don't need to store the array width/height on this assumption
            auto gl = new gl_texture2D_array(reference_v->width(), reference_v->height(), static_cast<blt::i32>(back.second.size()));
            for (auto [index, texture] : blt::enumerate(back.second))
            {
                gl->upload(texture->texture().data(), static_cast<blt::i32>(index));
                delete texture;
            }
            texture_arrays_2d.insert({back.first.c_str(), gl});
            loaded_arrays.pop_back();
            LOG_LOCKED(BLT_DEBUG("Loaded texture array '%s' of size %ld", back.first.c_str(), back.second.size()));
        }
    }
}