#include <BlockRegistry/BlockRegistry.hpp>
#include <Widget/EnumSelector/EnumSelector.hpp>
#include <sstream>
#include <WorldType/FlatWorldType/FlatWorldType.hpp>
#include <Widget/Layout/Column/Column.hpp>
#include <Widget/Label/Label.hpp>
#include <Widget/Layout/CompoundWidget/FormField/FormField.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <WorldGenerator/FlatWorldGenerator/FlatWorldGenerator.hpp>

void FlatWorldType::Settings::serialize(std::vector<uint8_t> &output)
{
    serializeInto(output, layers, bottomBlock);
}

std::unique_ptr<WorldGenerator> FlatWorldType::Settings::makeWorldGenerator(LocalWorld &world)
{
    std::stringstream ss;
    ss << "Initializing flat world generator with the following layers:" << std::endl;
    for (const FlatWorldGenerator::LayerSpec &spec : layers)
    {
        ss << "  " << spec.depth << "x " << blockRegistry.getBlockByName(spec.blockName).displayName << std::endl;
    }

    ss << "  Rest: " << blockRegistry.getBlockByName(bottomBlock).displayName;

    Log::info(ss.str());

    return std::make_unique<FlatWorldGenerator>(world, layers, bottomBlock);
}

std::shared_ptr<FlatWorldType::Settings> FlatWorldType::Settings::deserialize(const uint8_t *data, size_t size)
{
    std::shared_ptr<Settings> settings = std::make_shared<Settings>();
    ::deserialize(data, size, settings->layers, settings->bottomBlock);
    return settings;
}

FlatWorldType::CreationForm::CreationForm(std::function<void()> onAccept) : WorldTypeCreationForm(
    new Column(
        new FormField(
            "Theme:",
            selectedTheme,
            new EnumSelector<Theme>(
                selectedTheme,
                {
                    {Theme::Grasslands, "Grasslands"},
                    {Theme::Desert, "Desert"},
                    {Theme::RockSolid, "Rock solid"},
                    {Theme::Marble, "Marble"},
                    {Theme::Basalt, "Basalt"},
                }
            )
        )
    )
)
{}

std::shared_ptr<WorldTypeSettings> FlatWorldType::CreationForm::makeSettings()
{
    std::shared_ptr<Settings> settings = std::make_shared<Settings>();

    if (selectedTheme == Theme::Grasslands)
    {
        settings->layers.push_back({"main:grass", 1});
        settings->layers.push_back({"main:dirt", 15});
        settings->bottomBlock = "main:stone";
    }
    else if (selectedTheme == Theme::Desert)
    {
        settings->bottomBlock = "main:sand";
    }
    else if (selectedTheme == Theme::RockSolid)
    {
        settings->bottomBlock = "main:stone";
    }
    else if (selectedTheme == Theme::Marble)
    {
        settings->bottomBlock = "main:stoneMarble";
    }
    else if (selectedTheme == Theme::Basalt)
    {
        settings->bottomBlock = "main:stoneBasalt";
    }
    else
    {
        crash("Invalid selectedTheme value in FlatWorldType::CreationForm::makeSettings()");
    }

    return settings;
}

bool FlatWorldType::CreationForm::isAcceptable()
{
    return true;
}

std::string_view FlatWorldType::getName()
{
    return "Flat";
}

WorldTypeCreationForm* FlatWorldType::makeCreationForm(std::function<void()> onAccept)
{
    return new CreationForm(onAccept);
}

std::shared_ptr<WorldTypeSettings> FlatWorldType::deserializeSettings(const uint8_t *data, size_t size)
{
    return Settings::deserialize(data, size);
}

std::shared_ptr<WorldTypeSettings> FlatWorldType::parseSettingsFromCmdArgs(const std::string *args, size_t numArgs)
{
    if (numArgs < 1)
    {
        Log::error("Syntax: mkworld <filename> main:flat [block*N block*N block*N...] <bottomBlock>");
        return nullptr;
    }

    std::shared_ptr<Settings> settings = std::make_shared<Settings>();

    for (size_t i = 0; i < numArgs - 1; ++i)
    {
        std::string arg = args[i];
        size_t pos = arg.find('*');
        if (pos == std::string::npos)
        {
            Log::error("Syntax: mkworld <filename> main:flat [block*N block*N block*N...] <bottomBlock>");
            return nullptr;
        }

        std::string blockName = arg.substr(0, pos);
        uint32_t depth = std::stoull(arg.substr(pos + 1));

        if (!blockRegistry.isValidBlock(blockName))
        {
            Log::error(formatString("Unknown block name: $+", blockName));
            return nullptr;
        }

        settings->layers.push_back({blockName, depth});
    }

    settings->bottomBlock = args[numArgs - 1];

    return settings;
}