#include <FluidRegistry/FluidRegistry.hpp>
#include <FuelRuleRegistry/FuelRuleRegistry.hpp>
#include <ItemRecipeRegistry/ItemRecipeRegistry.hpp>
#include <MaceratorRuleRegistry/MaceratorRuleRegistry.hpp>
#include <SmeltingRuleRegistry/SmeltingRuleRegistry.hpp>
#include <WorldTypeRegistry/WorldTypeRegistry.hpp>
#include <chrono>
#include <memory>
#include <thread>
#include <Display/PreloaderDisplay/PreloaderDisplay.hpp>
#include <Material/Material.hpp>
#include <Display/WidgetDisplay/MainMenuDisplay/MainMenuDisplay.hpp>
#include <Font/Font.hpp>
#include <Log/Log.hpp>
#include <BlockRegistry/BlockRegistry.hpp>
#include <ItemRegistry/ItemRegistry.hpp>
#include <Cubemap/Cubemap.hpp>
#include <Sound/Sound.hpp>
#include <Animation/Animation.hpp>
#include <Color/Color.hpp>
#include <Model/Model.hpp>
#include <VoxelverseSession/VoxelverseClient/VoxelverseClient.hpp>

PreloaderDisplay::PreloaderDisplay() : tasksDone(0), tasksStarted(false)
{
    #define DECL_MATERIAL(name) tasks.push_back([](std::atomic<unsigned int> &tasksDone) { Material::name.load(tasksDone); })
    #include <Material/MaterialList.hpp>

    #define DECL_ANIMATION(name, fps) tasks.push_back([](std::atomic<unsigned int> &tasksDone) { Animation::name.load(tasksDone); })
    #include <Animation/AnimationList.hpp>

    #define DECL_SOUND(name) tasks.push_back([](std::atomic<unsigned int> &tasksDone) { Sound::name.load(); tasksDone++; })
    #include <Sound/SoundList.hpp>

    #define DECL_CUBEMAP(name) tasks.push_back([](std::atomic<unsigned int> &tasksDone) { Cubemap::name.load(tasksDone); })
    #define DECL_CUBEMAP_COMPUTE(name, width, height, shader) tasks.push_back([](std::atomic<unsigned int> &tasksDone) { Cubemap::name.generate(glm::uvec2(width, height), *Vk::ShaderModule::shader, tasksDone); })
    #include <Cubemap/CubemapList.hpp>

    #define DECL_VOXEL_SPRITE(name) tasks.push_back([](std::atomic<unsigned int> &tasksDone) { VoxelSprite::name.loadFromAssets(#name); Model::renderModel(Model::name, VoxelSprite::name, tasksDone); })
    #include <VoxelSprite/VoxelSpriteList.hpp>
    
    #define DECL_FONT(name) tasks.push_back([](std::atomic<unsigned int> &tasksDone) { Font::name.load(#name, tasksDone); })
    #include <Font/FontList.hpp>

    blockRegistry.addLoadingTasks(tasks);
}

void PreloaderDisplay::update()
{
    // NOP
}

void PreloaderDisplay::draw(GraphicsContext &context)
{
    if (!tasksStarted)
    {
        WorldDisplay::initStaticObjects();
    
        tasksStarted = true;

        std::thread([this]()
        {
            for (auto &task : tasks)
            {
                task(tasksDone);
            }
        }).detach();
    }

    int width = context.getVirtualWidth();
    int height = context.getVirtualHeight();

    int barX = width/2-32;
    int barY = height*3/4-2;
    int barHeight = 4;
    int barWidth = 64;

    context.clearGui(glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
    context.beginGui();
    context.color = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);
    context.drawMaterial(Material::internalGameIcon, width/2-32, height/2-32);
    context.color = Color::progressBackground;
    context.drawRect(barX, barY, barWidth, barHeight);
    context.color = Color::progressBar;
    context.drawRect(barX, barY, barWidth * (int) tasksDone / (int) tasks.size(), barHeight);
    context.endGui();

    if (tasksDone == tasks.size())
    {
        std::string vramUsage = formatDataSize(context.getTotalVramUsage());
        std::chrono::duration<double, std::ratio<1>> loadTime = std::chrono::steady_clock::now() - voxelverse->startTime;

        std::stringstream ss;
        ss << "Preloading complete." << std::endl;
        ss << "  VRAM usage:            " << vramUsage << std::endl;
        ss << "  Load time:             " << loadTime.count() << "s" << std::endl;
        ss << "  Blocks:                " << blockRegistry.getTotalBlocks() << std::endl;
        ss << "  Items:                 " << itemRegistry.getTotalItems() << std::endl;
        ss << "  Fluids:                " << fluidRegistry.getTotalFluids() << std::endl;
        ss << "  Construction recipes:  " << itemRecipeRegistry.getTotalRecipes() << std::endl;
        ss << "  Block atlas nodes:     " << blockRegistry.getAtlasNodes().size() << std::endl;
        ss << "  Voxels in palette:     " << blockRegistry.getNumVoxels() << std::endl;
        ss << "  Macerator rules:       " << maceratorRuleRegistry.getRuleCount() << std::endl;
        ss << "  Smelting rules:        " << smeltingRuleRegistry.getRuleCount() << std::endl;
        ss << "  Fuel rules:            " << fuelRuleRegistry.getRuleCount() << std::endl;
        ss << "  World types:           " << worldTypeRegistry.getTotalWorldTypes() << std::endl;
        
        Log::info(ss.str());
        Display::setCurrent<MainMenuDisplay>();

        static_cast<VoxelverseClient&>(*voxelverse).onPreloadingFinished();
    }
}