#include <DecorationRegistry/DecorationRegistry.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <Decoration/SingleBlockDecoration/SingleBlockDecoration.hpp>
#include <Decoration/Tree/Tree.hpp>
#include <Decoration/Boulder/Boulder.hpp>
#include <Decoration/OilReservoir/OilReservoir.hpp>
#include <Decoration/PineTree/PineTree.hpp>

void DecorationRegistry::registerDecoration(uint64_t key, std::shared_ptr<Decoration> decoration)
{
    if (mapper[key] != nullptr)
    {
        crash(formatString("Attempting to register a decoration with duplicate key $+", key));
    }

    mapper[key] = decoration;
}

void DecorationRegistry::registerAllDecorations(LocalWorld &world)
{
    int grassID = world.getBlockId("main:grass");
    int sandID = world.getBlockId("main:sand");
    int clayID = world.getBlockId("main:clay");

    registerDecoration<SingleBlockDecoration>(
        0xef80ca6e15810ce9,
        [grassID](const TerrainGenerator::Cell &cell, GeneratorNode node)
        {
            if (cell.topID != grassID)
            {
                return 0.0;
            }

            return 100.0 * cell.fertility * cell.fertility;
        },
        "main:tallGrass"
    );

    registerDecoration<SingleBlockDecoration>(
        0x5f5ae262c36b5f81,
        [grassID](const TerrainGenerator::Cell &cell, GeneratorNode node)
        {
            if (cell.topID != grassID)
            {
                return 0.0;
            }

            if (cell.temperature < 0.2 || cell.temperature > 0.7)
            {
                return 0.0;
            }

            return 25.0 * cell.fertility * cell.fertility * cell.fertility * cell.fertility;
        },
        "main:tulip"
    );

    registerDecoration<SingleBlockDecoration>(
        0x6a79d4f4eb51ed22,
        [grassID](const TerrainGenerator::Cell &cell, GeneratorNode node)
        {
            if (cell.topID != grassID)
            {
                return 0.0;
            }

            return 0.3;
        },
        "main:pebble"
    );

    registerDecoration<SingleBlockDecoration>(
        0x0a464ad3e20d7185,
        [grassID](const TerrainGenerator::Cell &cell, GeneratorNode node)
        {
            if (cell.topID != grassID)
            {
                return 0.0;
            }

            return 8.0 * cell.fertility * cell.fertility;
        },
        "main:leaves"
    );

    registerDecoration<SingleBlockDecoration>(
        0x58741eba8acc27e4,
        [sandID, clayID, grassID](const TerrainGenerator::Cell &cell, GeneratorNode node)
        {
            if (cell.topID == sandID || cell.topID == clayID)
            {
                return 0.0;
            }

            double density = 2.5 * cell.humidity;
            if (cell.topID != grassID)
            {
                density /= 10.0;
            }

            return density;
        },
        "main:oysterMushroom"
    );

    registerDecoration<SingleBlockDecoration>(
        0xf84681b5e3bb4238,
        [sandID, clayID, grassID](const TerrainGenerator::Cell &cell, GeneratorNode node)
        {
            if (cell.topID == sandID || cell.topID == clayID)
            {
                return 0.0;
            }

            double density = 2.5 * cell.humidity;
            if (cell.topID != grassID)
            {
                density /= 10.0;
            }
            
            return density;
        },
        "main:agaricMushroom"
    );

    registerDecoration<SingleBlockDecoration>(
        0x97c25c7b3551ad3a,
        [sandID, clayID](const TerrainGenerator::Cell &cell, GeneratorNode node)
        {
            if (cell.topID == sandID || cell.topID == clayID)
            {
                return 0.0;
            }

            if (cell.height > 16)
            {
                return 0.0;
            }

            if (cell.temperature < 0.6)
            {
                return 0.0;
            }

            return 2.5 * cell.humidity * cell.temperature;
        },
        "main:mycenaMushroom"
    );

    registerDecoration<SingleBlockDecoration>(
        0x4f7f57b58652e27e,
        [grassID, clayID](const TerrainGenerator::Cell &cell, GeneratorNode node)
        {
            if (cell.topID == grassID || cell.topID == clayID)
            {
                return 0.0;
            }

            if (cell.height < 200)
            {
                return 0.0;
            }

            return 0.2 * std::exp((double) (cell.height - 200) / 240.0) * cell.temperature;
        },
        "main:leadOre"
    );

    registerDecoration<SingleBlockDecoration>(
        0x86bd0c736f253c10,
        [grassID, clayID](const TerrainGenerator::Cell &cell, GeneratorNode node)
        {
            if (cell.topID == grassID || cell.topID == clayID)
            {
                return 0.0;
            }

            if (cell.height < 300)
            {
                return 0.0;
            }

            return 0.1 * std::exp((double) (cell.height - 300) / 300.0) * cell.humidity;
        },
        "main:silverOre"
    );

    registerDecoration<Tree>(
        0x3119f804c5efa667,
        [grassID](const TerrainGenerator::Cell &cell, GeneratorNode node)
        {
            if (cell.topID != grassID)
            {
                return 0.0;
            }

            if (cell.temperature < 0.2 || cell.temperature > 0.5)
            {
                return 0.0;
            }

            double density = 2.0 + 3.0 * cell.fertility * cell.humidity;
            if (cell.height > 24)
            {
                density /= 20.0;
            }

            return density;
        },
        Tree::oak
    );

    registerDecoration<Tree>(
        0x48c5c273c97c1a8c,
        [grassID](const TerrainGenerator::Cell &cell, GeneratorNode node)
        {
            if (cell.topID != grassID)
            {
                return 0.0;
            }

            if (cell.temperature < 0.3 || cell.temperature > 0.6)
            {
                return 0.0;
            }

            if (cell.fertility < 0.33 || cell.humidity < 0.33)
            {
                return 0.0;
            }

            double density = 1.0 + 8.0 * cell.fertility * cell.fertility * cell.humidity * cell.humidity * cell.humidity;
            if (cell.height > 24)
            {
                density /= 10.0;
            }

            return density;
        },
        Tree::birch
    );

    registerDecoration<Tree>(
        0x111a54e3f37dc109,
        [grassID](const TerrainGenerator::Cell &cell, GeneratorNode node)
        {
            if (cell.topID != grassID)
            {
                return 0.0;
            }

            if (cell.height > 16)
            {
                return 0.0;
            }

            if (cell.temperature < 0.4)
            {
                return 0.0;
            }

            if (cell.fertility < 0.11 || cell.humidity > 0.5)
            {
                return 0.0;
            }

            return 1.0 + 1.0 * cell.fertility * cell.fertility;
        },
        Tree::rubber
    );

    registerDecoration<Tree>(
        0x35d4c32f78b090c2,
        [sandID](const TerrainGenerator::Cell &cell, GeneratorNode node)
        {
            if (cell.topID != sandID)
            {
                return 0.0;
            }

            if (cell.temperature < 0.5)
            {
                return 0.0;
            }

            return 2.0 * (1.0 - cell.humidity) * cell.temperature;
        },
        Tree::cactus
    );

    registerDecoration<Tree>(
        0xda4aed4460207acc,
        [grassID](const TerrainGenerator::Cell &cell, GeneratorNode node)
        {
            if (cell.topID != grassID)
            {
                return 0.0;
            }

            if (cell.height > 24)
            {
                return 0.0;
            }

            if (cell.humidity < 0.4)
            {
                return 0.0;
            }

            if (cell.temperature > 0.5)
            {
                return 0.0;
            }

            return 0.7 + 1.0 * cell.fertility;
        },
        Tree::maple
    );

    registerDecoration<PineTree>(0xd3e25993ff58149e, grassID);
    registerDecoration<Boulder>(0x1d64da97cfade46a);
    registerDecoration<OilReservoir>(0x2beb05e6f1fa390e, sandID);

    for (const auto &[_, decoration] : mapper)
    {
        decorations.push_back(decoration);
    }

    mapper = {};
}