#include <Block/SpriteBlock/BasicBlock/OreBlock/OreBlock.hpp>
#include <Block/SpriteBlock/BasicBlock/UniformBlock/UniformBlock.hpp>
#include <Block/SpriteBlock/Pillar/Pillar.hpp>
#include <Block/SpriteBlock/Slab/Slab.hpp>
#include <FluidRegistry/FluidRegistry.hpp>
#include <BlockRegistry/BlockRegistry.hpp>
#include <Block/AirBlock/AirBlock.hpp>
#include <Block/SpriteBlock/BasicBlock/BasicBlock.hpp>
#include <Block/SpriteBlock/BasicBlock/GrassBlock/GrassBlock.hpp>
#include <Block/SpriteBlock/BasicBlock/LogBlock/LogBlock.hpp>
#include <Block/SpriteBlock/Accumulator/Accumulator.hpp>
#include <Block/SpriteBlock/EngineeringTable/EngineeringTable.hpp>
#include <Block/SpriteBlock/BasicBlock/Blob/Blob.hpp>
#include <Block/SpriteBlock/BasicBlock/Zype/Zype.hpp>
#include <Block/SpriteBlock/BasicBlock/Leaves/Leaves.hpp>
#include <Block/SpriteBlock/BasicBlock/ConstructTable/ConstructTable.hpp>
#include <Block/SpriteBlock/BasicBlock/Crate/Crate.hpp>
#include <Block/SpriteBlock/Pot/Pot.hpp>
#include <Block/FluidBlock/FluidBlock.hpp>
#include <Block/SpriteBlock/Barrel/Barrel.hpp>
#include <Block/SpriteBlock/Casing/Casing.hpp>
#include <Block/SpriteBlock/BasicBlock/Glass/Glass.hpp>
#include <Block/SpriteBlock/BasicBlock/GrindingBowl/GrindingBowl.hpp>
#include <Block/SpriteBlock/PipelineBlock/FluidPipe/FluidPipe.hpp>
#include <Block/SpriteBlock/PipelineBlock/Wire/Wire.hpp>
#include <Block/SpriteBlock/PipelineBlock/DataCable/DataCable.hpp>
#include <Block/SpriteBlock/Pump/Pump.hpp>
#include <Block/SpriteBlock/SteamGenerator/SteamGenerator.hpp>
#include <Block/SpriteBlock/MultiBlock/ChemicalReactor/ChemicalReactor.hpp>
#include <Block/SpriteBlock/Torch/Torch.hpp>
#include <Block/SpriteBlock/OilRefinery/OilRefinery.hpp>
#include <Block/SpriteBlock/CombustionGenerator/CombustionGenerator.hpp>
#include <Block/SpriteBlock/MultiBlock/ItemProcessor/ItemProcessor.hpp>
#include <Block/SpriteBlock/ElectricLamp/ElectricLamp.hpp>
#include <Block/SpriteBlock/TallGrass/TallGrass.hpp>
#include <Block/SpriteBlock/Mushroom/Mushroom.hpp>
#include <Block/SpriteBlock/FarmPlant/WheatPlant/WheatPlant.hpp>
#include <Block/SpriteBlock/FarmLand/FarmLand.hpp>
#include <Block/SpriteBlock/Tulip/Tulip.hpp>
#include <Block/SpriteBlock/Crystal/Crystal.hpp>
#include <Block/SpriteBlock/Stairs/Stairs.hpp>
#include <Machine/MItemProcessor/MMacerator/MMacerator.hpp>
#include <Machine/MItemProcessor/MElectricFurnace/MElectricFurnace.hpp>
#include <Block/SpriteBlock/BasicBlock/Dirt/Dirt.hpp>
#include <Block/SpriteBlock/BasicBlock/Pebble/Pebble.hpp>
#include <Block/SpriteBlock/IndicatorLights/IndicatorLights.hpp>
#include <Block/SpriteBlock/ConveyorBelt/ConveyorBelt.hpp>
#include <Block/SpriteBlock/BeltMotor/BeltMotor.hpp>
#include <Block/SpriteBlock/ItemExtractor/ItemExtractor.hpp>
#include <Block/SpriteBlock/BeltJunction/BeltJunction.hpp>
#include <Block/SpriteBlock/Dynamo/Dynamo.hpp>
#include <Block/SpriteBlock/MultiBlock/MiningRig/MiningRig.hpp>
#include <Block/SpriteBlock/BasicBlock/Incinerator/Incinerator.hpp>
#include <Block/SpriteBlock/Bricks/Bricks.hpp>
#include <Block/SpriteBlock/Chain/Chain.hpp>
#include <Block/SpriteBlock/Door/Door.hpp>
#include <Block/SpriteBlock/MultiBlock/UraniumRefinery/UraniumRefinery.hpp>
#include <Block/SpriteBlock/Centrifuge/Centrifuge.hpp>
#include <Block/SpriteBlock/MultiBlock/NuclearReactor/NuclearReactor.hpp>
#include <Block/SpriteBlock/MultiBlock/Substation/Substation.hpp>
#include <Block/SpriteBlock/SolarPanel/SolarPanel.hpp>
#include <Block/SpriteBlock/BlockPlacer/BlockPlacer.hpp>
#include <Block/SpriteBlock/BlockBreaker/BlockBreaker.hpp>
#include <Block/SpriteBlock/Sapling/Sapling.hpp>
#include <Block/SpriteBlock/FluidDispenser/FluidDispenser.hpp>
#include <Block/SpriteBlock/QuartzClock/QuartzClock.hpp>
#include <Block/SpriteBlock/ItemDeployer/ItemDeployer.hpp>
#include <Block/SpriteBlock/LogicBox/LogicBox.hpp>
#include <VoxelSpriteRenderer/VoxelSpriteRenderer.hpp>
#include <Item/StatelessItem/BasicItem/Dye/Dye.hpp>

BlockRegistry blockRegistry;

void BlockRegistry::registerDyedBlocks(std::string dyeColorName)
{
    const std::string &dyeDisplayName = Dye::colorDisplayNames[dyeColorName];

    registerBlock<Bricks>(
        withSlab | withStairs,
        formatString("main:$+Bricks", dyeColorName),
        formatString("$+ Bricks", dyeDisplayName),
        200.0f,
        formatString("$+Bricks", dyeColorName)
    );

    registerBlock<Glass>(
        withSlab | withStairs,
        formatString("main:$+Glass", dyeColorName),
        formatString("$+ Glass", dyeDisplayName),
        formatString("$+Glass", dyeColorName)
    );

    registerBlock<BasicBlock>(
        withSlab | withStairs,
        formatString("main:$+Planks", dyeColorName),
        formatString("$+ Wooden Planks", dyeDisplayName),
        90.0f,
        "<invalid>"
    )
        .withFallbackScript("dyedPlanks.lua")
        .withWoodSounds();

    registerBlock<BasicBlock>(
        formatString("main:$+WoodenPole", dyeColorName),
        formatString("$+ Wooden Pole", dyeDisplayName),
        90.0f,
        "<invalid>"
    )
        .withFallbackScript("dyedWoodenPole.lua")
        .withWoodSounds();

    registerBlock<BasicBlock>(
        withSlab | withStairs,
        formatString("main:$+Clay", dyeColorName),
        formatString("$+ Clay", dyeDisplayName),
        30.0f,
        "clay"
    )
        .digManually(Sound::clay)
        .withFallbackScript("dyedClay.lua")
        .withPlacingSound(Sound::clay)
        .withBreakingSound(Sound::clay)
        .withFootstepSound(Sound::clay)
        .withFallingSound(Sound::clay);
}

void BlockRegistry::registerAllBlocks()
{
    blockAir = &registerBlock<AirBlock>("main:air");

    for (const std::string &dyeColorName : Dye::colorNames)
    {
        registerDyedBlocks(dyeColorName);
    }

    registerBlock<BasicBlock>("main:sand", "Sand", 30.0f, "sand")
        .digManually(Sound::dirtDigging)
        .withDustSounds();
    registerBlock<BasicBlock>(withSlab | withStairs, "main:clay", "Clay", 30.0f, "clay")
        .digManually(Sound::clay)
        .withPlacingSound(Sound::clay)
        .withBreakingSound(Sound::clay)
        .withFootstepSound(Sound::clay)
        .withFallingSound(Sound::clay);
    registerDecorativeUniformStone("main:stone", "Stone", 200.0f, "stone");
    registerDecorativeUniformStone("main:stoneBasalt", "Basalt", 200.0f, "basalt");
    registerDecorativeUniformStone("main:stoneMarble", "Marble", 200.0f, "marble");
    registerDecorativeUniformStone("main:stoneLimestone", "Limestone", 100.0f, "limestone");
    registerDecorativeUniformStone("main:stoneGranite", "Granite", 300.0f, "granite");
    registerDecorativeUniformGlass("main:stoneEmerald", "Emerald Block", 500.0f, "emerald");
    registerBlock<OreBlock>("main:stoneCopperOre", "Copper Ore", 400.0f, "metal", "copper");
    registerBlock<OreBlock>("main:ironOre", "Iron Ore", 400.0f, "metal", "iron");
    registerBlock<OreBlock>("main:leadOre", "Lead Ore", 400.0f, "metal", "lead");
    registerBlock<OreBlock>("main:stoneTinOre", "Tin Ore", 400.0f, "metal", "tin");
    registerBlock<OreBlock>("main:silverOre", "Silver Ore", 400.0f, "metal", "silver");
    registerBlock<OreBlock>("main:uraniumOreMantle", "Uranium Ore", 400.0f, "metal", "uranium");
    registerBlock<OreBlock>("main:nickelOre", "Nickel Ore", 400.0f, "metal", "nickel");
    registerBlock<OreBlock>("main:diamondOreX", "Diamond Ore", 800.0f, "rocky", "diamond").grindsTo("main:diamond");
    registerBlock<OreBlock>("main:goldOre", "Gold Ore", 400.0f, "metal", "gold");
    registerBlock<OreBlock>("main:coalOre", "Coal Ore", 400.0f, "rocky", "coal").grindsTo("main:coal").withStoneSounds();
    registerBlock<OreBlock>("main:siliconOre", "Silicon Ore", 400.0f, "metal", "silicon");
    registerBlock<BasicBlock>(withSlab | withStairs, "main:planks", "Wooden Planks", 90.0f, "planks").grindsTo("main:woodPulp").withWoodSounds();
    registerBlock<BasicBlock>("main:woodenPole", "Wooden Pole", 90.0f, "<invalid>").digManually(Sound::woodBreak).withWoodSounds();
    registerBlock<Bricks>(withSlab | withStairs, "main:bricks", "Bricks", 200.0f, "bricks");
    registerBlock<Bricks>(withSlab | withStairs, "main:stoneBricks", "Stone Bricks", 200.0f, "stoneBricks");
    registerBlock<Bricks>(withSlab | withStairs, "main:stoneMarbleBricks", "Marble Bricks", 200.0f, "marbleBricks");
    registerBlock<Bricks>(withSlab | withStairs, "main:stoneBasaltBricks", "Basalt Bricks", 200.0f, "basaltBricks");
    registerBlock<Bricks>(withSlab | withStairs, "main:stoneLimestoneBricks", "Limestone Bricks", 200.0f, "limestoneBricks");
    registerBlock<Bricks>(withSlab | withStairs, "main:stoneGraniteBricks", "Granite Bricks", 200.0f, "graniteBricks");
    registerBlock<Bricks>(withSlab | withStairs, "main:stoneEmeraldBricks", "Emerald Bricks", 200.0f, "emeraldBricks");
    registerBlock<BasicBlock>(withSlab | withStairs, "main:lapis", "Lapis Lazuli", 200.0f, "lapis").withStoneSounds();
    registerBlock<BasicBlock>(withSlab | withStairs, "main:ringwoodite", "Ringwoodite", 8000.0f, "ringwoodite").withStoneSounds();
    registerBlock<BasicBlock>("main:burntChemicalReactor", "Burnt Chemical Reactor", 60.0f, "chemicalReactor/burnt")
        .digManually()
        .withMetalSounds();
    registerBlock<Pebble>("main:pebble").digManually(Sound::NONE);
    registerBlock<Glass>(withSlab | withStairs, "main:glass", "Glass", "glass");
    registerBlock<Glass>(withSlab | withStairs, "main:mirror", "Mirror", "mirror");
    registerBlock<Glass>(withSlab | withStairs, "main:temperedGlass", "Tempered Glass", "temperedGlass", 90.0f);
    registerBlock<Dirt>(withSlab | withStairs, "main:dirt");
    registerBlock<Casing>("main:tinCasing", "Tin Casing", "tin").withMetalSounds();
    registerBlock<Casing>("main:steelCasing", "Steel Casing", "steel").withMetalSounds();
    registerBlock<Casing>("main:leadCasing", "Lead Casing", "lead").withMetalSounds();
    registerBlock<GrassBlock>("main:grass");
    registerBlock<LogBlock>("main:oakLog", "Oak Log", "oak", "main:oakSapling");
    registerBlock<LogBlock>("main:birchLog", "Birch Log", "birch", "main:birchSapling");
    registerBlock<LogBlock>("main:rubberLog", "Rubber Log", "rubber", "main:rubberSapling").grindsTo("main:rubberTreeSap");
    registerBlock<LogBlock>("main:cactusLog", "Cactus", "", "main:cactusSapling").grindsTo("main:greenDye");
    registerBlock<LogBlock>("main:pineLog", "Pine Log", "pine");
    registerBlock<LogBlock>("main:mapleLog", "Maple Log", "maple", "main:mapleSapling");
    registerBlock<Sapling>("main:oakSapling", "Oak Sapling", Tree::oak);
    registerBlock<Sapling>("main:birchSapling", "Birch Sapling", Tree::birch);
    registerBlock<Sapling>("main:rubberSapling", "Rubber Sapling", Tree::rubber);
    registerBlock<Sapling>("main:cactusSapling", "Cactus Sapling", Tree::cactus);
    registerBlock<Sapling>("main:mapleSapling", "Maple Sapling", Tree::maple);
    registerBlock<Accumulator>("main:accumulator");
    registerBlock<EngineeringTable>("main:engineeringTable");
    registerBlock<Blob>("main:blob");
    registerBlock<Zype>("main:zype");
    registerBlock<Leaves>("main:leaves", "Leaves", "leaves");
    registerBlock<Leaves>("main:mapleLeaves", "Maple Leaves", "mapleLeaves");
    registerBlock<ConstructTable>("main:constructTable");
    registerBlock<Crate>("main:crate");
    registerBlock<Pot>("main:stonePot", false);
    registerBlock<Pot>("main:ironPot", true);
    registerBlock<FluidBlock>("main:waterBlock", "main:water");
    registerBlock<FluidBlock>("main:oilBlock", "main:oil");
    registerBlock<Barrel>("main:woodenBarrel", "Wooden barrel", 15.0f, BarrelTier::Wooden);
    registerBlock<Barrel>("main:steelBarrel", "Steel barrel", 90.0f, BarrelTier::Steel);
    registerBlock<Barrel>("main:leadBarrel", "Lead barrel", 90.0f, BarrelTier::Lead);
    registerBlock<GrindingBowl>("main:grindingBowl");
    registerBlock<FluidPipe>("main:fluidPipe");
    registerBlock<Wire>("main:copperWire", "Copper wire", "copperWire");
    registerBlock<DataCable>("main:dataCable", "Data cable");
    registerBlock<Pump>("main:pump");
    registerBlock<SteamGenerator>("main:steamGenerator");
    registerBlock<ChemicalReactor>("main:chemicalReactor");
    registerBlock<Torch>("main:torch");
    registerBlock<OilRefinery>("main:oilRefinery");
    registerBlock<CombustionGenerator>("main:combustionGenerator");
    registerBlock<ItemProcessor<MMacerator>>(
        "main:macerator",
        "Macerator",
        "macerator",
        "steel",
        glm::vec3(1.0f, 0.0f, 0.0f),
        Sound::macerator
    );
    registerBlock<ItemProcessor<MElectricFurnace>>(
        "main:electricFurnace",
        "Electric furnace",
        "electricFurnace",
        "tin",
        glm::vec3(1.0f, 0.0f, 0.0f),
        Sound::combustion
    );
    registerBlock<ElectricLamp>("main:electricLamp");
    registerBlock<TallGrass>("main:tallGrass");
    registerBlock<Mushroom>("main:oysterMushroom", "King Oyster Mushroom", "oyster");
    registerBlock<Mushroom>("main:agaricMushroom", "Fly Agaric Mushroom", "agaric");
    registerBlock<Mushroom>("main:mycenaMushroom", "Mycena Mushroom", "mycena");
    registerBlock<WheatPlant>("main:wheatSeeds");
    registerBlock<FarmLand>("main:farmland");
    registerBlock<Tulip>("main:tulip").grindsTo("main:yellowDye");
    registerBlock<Crystal>(
        "main:lapisCrystal",
        "Lapis Lazuli Crystal",
        "crystals/lapis",
        glm::vec3(0.0f, 0.0f, 0.5f),
        "main:lapis"
    );
    registerBlock<Crystal>(
        "main:quartzCrystal",
        "Quartz Crystal",
        "crystals/quartz",
        glm::vec3(0.5f, 0.05f, 0.5f),
        "main:quartz"
    );
    registerBlock<Crystal>(
        "main:emeraldCrystal",
        "Emerald Crystal",
        "crystals/emerald",
        glm::vec3(0.0f, 0.5f, 0.0f),
        "main:emerald"
    );
    registerBlock<IndicatorLights>("main:indicatorLights");
    registerBlock<ConveyorBelt>("main:conveyorBelt");
    registerBlock<BeltMotor>("main:beltMotor");
    registerBlock<ItemExtractor>("main:itemExtractor");
    registerBlock<Dynamo>("main:dynamo");
    registerBlock<MiningRig>("main:miningRig");
    registerBlock<BeltJunction>("main:beltJunction");
    registerBlock<Incinerator>("main:incinerator");
    registerBlock<Chain>("main:chain");
    registerBlock<Door>("main:oakDoor");
    registerBlock<UraniumRefinery>("main:uraniumRefinery");
    registerBlock<Centrifuge>("main:centrifuge");
    registerBlock<NuclearReactor>("main:nuclearReactor");
    registerBlock<Substation>("main:substation");
    registerBlock<SolarPanel>("main:solarPanel");
    registerBlock<BlockPlacer>("main:blockPlacer");
    registerBlock<BlockBreaker>("main:blockBreaker");
    registerBlock<FluidDispenser>("main:fluidDispenser");
    registerBlock<QuartzClock>("main:quartzClock");
    registerBlock<ItemDeployer>("main:itemDeployer");
    registerBlock<LogicBox>("main:logicBox");
}

void BlockRegistry::registerDecorativeUniformStone(std::string name, std::string displayName, float hardness, std::string materialName)
{
    registerBlock<UniformBlock>(withSlab | withStairs, name, displayName, hardness, materialName);
    registerBlock<Pillar>(
        formatString("$+Pillar", name),
        formatString("$+ Pillar", displayName),
        hardness,
        materialName
    );
}

void BlockRegistry::registerDecorativeUniformGlass(std::string name, std::string displayName, float hardness, std::string materialName)
{
    registerBlock<UniformBlock>(withSlab | withStairs, name, displayName, hardness, materialName)
        .withGlassSounds();
    registerBlock<Pillar>(
        formatString("$+Pillar", name),
        formatString("$+ Pillar", displayName),
        hardness,
        materialName
    ).withGlassSounds();
}

BlockRegistry::~BlockRegistry()
{
    for (auto& [name, block] : blocks)
    {
        delete block;
    }
}

Block& BlockRegistry::getAirBlock()
{
    return *blockAir;
}

Block& BlockRegistry::getBlockByName(std::string name)
{
    if (blocks.count(name) == 0)
    {
        return *blockAir;
    }

    Block *block = blocks[name];
    if (block == nullptr)
    {
        return *blockAir;
    }

    return *block;
}

void BlockRegistry::addLoadingTasks(std::vector<std::function<void(std::atomic<unsigned int> &)>> &tasks)
{
    for (Block *block : orderedBlocks)
    {
        tasks.push_back([this, block](std::atomic<unsigned int> &tasksDone)
        {
            Bitmap icon(glm::uvec2(8));
            VoxelSprite defaultSprite(glm::ivec3(8));

            block->loadBlockSprites(atlas, icon, defaultSprite);

            std::shared_ptr<Vk::Device> device = GraphicsContext::instance->getDevice();

            std::shared_ptr<Vk::CommandBuffer> cmdbuf = GraphicsContext::instance->createAsyncCommandBuffer();
            cmdbuf->reset();
            cmdbuf->begin();

            block->iconMaterial.widthValue = block->iconMaterial.heightValue = 8;
            
            block->iconMaterial.addComponent(device, *cmdbuf, icon.data());

            block->iconMaterial.buildMetadata(device, *cmdbuf);
            block->iconMaterial.buildDescriptorSet(device);

            glm::vec3 colorSum = glm::vec3(0.0f);
            glm::ivec2 offset;
            int numPixels = 0;

            for (offset.y = 0; offset.y < 8; offset.y++)
            {
                for (offset.x = 0; offset.x < 8; offset.x++)
                {
                    glm::vec4 linearColor = icon.getLinearColor(offset);
                    if (linearColor.a >= 0.01f)
                    {
                        colorSum += glm::vec3(linearColor);
                        numPixels++;
                    }
                }
            }

            if (numPixels != 0)
            {
                colorSum /= glm::vec3(numPixels);
                block->mapColor = glm::vec4(Bitmap::linearToSRGB(colorSum), 1.0f);
            }

            GraphicsContext::onNextFrame([cmdbuf, block, &tasksDone, defaultSprite](GraphicsContext &)
            {
                std::shared_ptr<Vk::Fence> fence = std::make_shared<Vk::Fence>(GraphicsContext::instance->getDevice());
                cmdbuf->end();
                cmdbuf->submit(fence.get());
                fence->wait();

                block->iconMaterial.finalize();

                VoxelSprite defaultSpriteCopy = defaultSprite;
                VoxelSpriteRenderer renderer(defaultSpriteCopy);
                block->defaultModel = renderer.makeModel();
            
                tasksDone++;
            });
        });
    }

    fluidRegistry.addLoadingTasks(tasks);

    tasks.push_back([this](std::atomic<unsigned int> &tasksDone)
    {
        atlas.loadVoxelPalette(tasksDone);
    });
}

void BlockRegistry::assignBlockIds(std::vector<std::string> &ids)
{
    std::unordered_map<std::string, Block*>::iterator it;
    for (it=blocks.begin(); it!=blocks.end(); ++it)
    {
        if (std::find(ids.begin(), ids.end(), it->first) == ids.end())
        {
            ids.push_back(it->first);
        }
    }
}

bool BlockRegistry::isValidBlock(std::string name)
{
    return blocks.count(name) != 0;
}

void BlockRegistry::getAllStacks(std::vector<ItemStack> &stacks)
{
    for (auto &[name, block] : blocks)
    {
        if (block == nullptr)
        {
            continue;
        }

        stacks.push_back(block->toItemStack());
    }
}

const std::vector<VoxelTree::Node>& BlockRegistry::getAtlasNodes()
{
    return atlas.getAtlasNodes();
}

void BlockRegistry::forEachBlock(std::function<void(Block&)> callback)
{
    for (const auto &block : orderedBlocks)
    {
        if (block == nullptr)
        {
            continue;
        }

        callback(*block);
    }
}

void BlockRegistry::loadBoxesOnServer()
{
    Log::info("Loading block collision boxes...");

    for (Block *block : orderedBlocks)
    {
        Bitmap icon(glm::uvec2(8));
        VoxelSprite defaultSprite(glm::ivec3(8));

        block->loadBlockSprites(atlas, icon, defaultSprite);
    }
}

int BlockRegistry::getTotalBlocks()
{
    return (int) blocks.size();
}

size_t BlockRegistry::getNumVoxels()
{
    return atlas.getNumVoxels();
}

Vk::DescriptorSet& BlockRegistry::getVoxelPaletteDescSet()
{
    return atlas.getVoxelPaletteDescSet();
}