#include <Display/WidgetDisplay/WorldSelection/WorldSelection.hpp>
#include <Display/WidgetDisplay/WorldCreation/WorldCreation.hpp>
#include <Display/WidgetDisplay/WorldTrashing/WorldTrashing.hpp>
#include <Display/WidgetDisplay/WorldCloning/WorldCloning.hpp>
#include <Widget/Label/Label.hpp>
#include <Widget/Layout/Center/Center.hpp>
#include <Widget/Layout/CompoundWidget/IconText/IconText.hpp>
#include <Widget/Layout/Row/Row.hpp>
#include <Widget/Button/Button.hpp>
#include <Log/Log.hpp>
#include <Display/WidgetDisplay/WorldDisplay/WorldDisplay.hpp>
#include <Widget/Panel/Panel.hpp>
#include <Widget/TextField/TextField.hpp>
#include <Widget/Layout/CompoundWidget/ListBox/ListBox.hpp>
#include <Utilities/Utilities.hpp>
#include <World/LocalWorld/SinglePlayerWorld/SinglePlayerWorld.hpp>
#include <Widget/Layout/Column/HorizontalSeparator/HorizontalSeparator.hpp>
#include <Widget/Layout/CompoundWidget/Form/Form.hpp>
#include <Display/WidgetDisplay/MessageDisplay/MessageDisplay.hpp>
#include <ChunkWindow/ChunkWindow.hpp>
#include <Widget/IconLabel/IconLabel.hpp>
#include <Widget/Button/BackButton/BackButton.hpp>
#include <Material/Material.hpp>
#include <algorithm>
#include <cstdio>
#include <filesystem>
#include <Widget/Layout/MenuButton/MenuButton.hpp>

Widget* WorldSelection::WorldListEntry::makeWidget(WorldSelection &display)
{
    Widget *tipRow;

    if (index == nullptr || !error.empty())
    {
        tipRow = new Label(error, Color::error);
    }
    else
    {
        tipRow = index->getGameVersion() == Version::ofThisBuild ?
            new Label(index->getWorldTypeLabel(), Color::faded) :
            new Label(formatString("$+", index->getGameVersion()), Color::warning);

        if (index->areCheatsEnabled())
        {
            tipRow = new Row(
                tipRow,
                new Label("CHEATS", Color::warning)
            );
        }
    }

    std::string name = this->name;

    return new Row(
        new Center(new IconLabel(index == nullptr ? Material::alert : getWorldAvatarMaterial(index->getAvatar()))),
        new Center(
            new Column(
                new Builder<std::string>(
                    display.worldBeingRenamed,
                    [name, &display](std::string) -> Widget*
                    {
                        if (display.worldBeingRenamed != name)
                        {
                            return new Label(name);
                        }

                        display.renameField = new TextField(
                            display.newWorldName,
                            [&display]()
                            {
                                if (display.worldBeingRenamed == display.newWorldName)
                                {
                                    display.renameField = nullptr;
                                    display.worldBeingRenamed.clear();
                                    return;
                                }

                                if (!getWorldNameError(display.newWorldName).empty())
                                {
                                    return;
                                }

                                std::string oldFilename = formatString("worlds/$+.voxelverse", display.worldBeingRenamed);
                                std::string newFilename = formatString("worlds/$+.voxelverse", display.newWorldName);

                                if (std::rename(oldFilename.c_str(), newFilename.c_str()) != 0)
                                {
                                    return;
                                }

                                display.renameField = nullptr;
                                display.worldBeingRenamed.clear();
                                display.refresh();
                            }
                        );

                        display.renameField->focus();
                        display.renameField->selectAll();
                        return display.renameField;
                    }
                ),
                tipRow
            )
        )
    );
}

bool WorldSelection::WorldListEntry::operator==(const WorldListEntry &other) const
{
    return name == other.name;
}

void WorldSelection::refresh()
{
    static std::string filenameSuffix = ".voxelverse";

    lastRefreshTicks = SDL_GetTicks();
    worlds.clear();

    std::error_code ec;
    for (const auto &worldEntry : std::filesystem::directory_iterator("worlds", ec))
    {
        std::string filename = worldEntry.path().filename().string();
        if (filename.size() <= filenameSuffix.size())
        {
            continue;
        }

        if (filename.substr(filename.size() - filenameSuffix.size(), filenameSuffix.size()) != filenameSuffix)
        {
            continue;
        }

        WorldListEntry entry;
        entry.name = filename.substr(0, filename.size() - filenameSuffix.size());

        AtomicDataStore store;
        store.open(formatString("worlds/$+", filename));
        entry.error = store.getError();

        if (entry.error.empty())
        {
            std::vector<uint8_t> indexData;
            if (store.read("windex.dat", indexData))
            {
                entry.index = std::make_shared<WorldIndex>();
                entry.index->deserialize(indexData.data(), indexData.size(), entry.error, true);
            }
            else
            {
                entry.error = "This is not a Voxelverse world.";
            }
        }

        worlds.push_back(entry);
    }

    if (std::find(worlds.begin(), worlds.end(), selectedWorld) == worlds.end())
    {
        // The selected world no longer exists, de-select it.
        selectedWorld = WorldListEntry{};
    }

    std::sort(worlds.begin(), worlds.end(), [](WorldListEntry a, WorldListEntry b)
    {
        return a.name < b.name;
    });
}

WorldSelection::WorldSelection() : selectedWorld{}
{
    refresh();

    setWidget(
        new Center(
            new Form(
                new IconText(Material::singleplayer, "Select world"),
                (Widget::focused = (new ListBox<WorldListEntry>(
                    worlds,
                    selectedWorld,
                    [this](WorldListEntry entry) -> Widget*
                    {
                        return entry.makeWidget(*this);
                    },
                    [](WorldListEntry entry)
                    {
                        if (entry.index != nullptr && entry.index->getGameVersion() != Version::ofThisBuild)
                        {
                            Display::navPush<WorldCloning>(entry.name, *entry.index);
                            return;
                        }

                        std::string path = formatString("worlds/$+.voxelverse", entry.name);
                        std::shared_ptr<SinglePlayerWorld> world = std::make_shared<SinglePlayerWorld>(path);

                        std::string fatalError = world->getFatalError();
                        if (fatalError.size() != 0)
                        {
                            Display::navPush<MessageDisplay>(Material::alert, "Error", fatalError);

                            return;
                        }

                        Display::navPush<WorldDisplay>(world, world->getThePlayer());
                    }
                ))
                    ->withContextMenu([this](WorldListEntry entry) -> Widget*
                    {
                        return new Column(
                            new MenuButton(
                                new Row(
                                    new IconLabel(Material::edit),
                                    new Label("Rename...")
                                ),
                                [this, entry]()
                                {
                                    worldBeingRenamed = newWorldName = entry.name;
                                }
                            ),

                            new MenuButton(
                                new Row(
                                    new IconLabel(Material::clone),
                                    new Label("Clone...")
                                ),
                                [entry]()
                                {
                                    if (entry.index == nullptr)
                                    {
                                        Display::navPush<MessageDisplay>(Material::alert, "Error", entry.error);

                                        return;
                                    }

                                    Display::navPush<WorldCloning>(entry.name, *entry.index);
                                }
                            ),

                            new MenuButton(
                                new Row(
                                    new IconLabel(Material::trash),
                                    new Label("Delete...")
                                ),
                                [entry]()
                                {
                                    Display::navPush<WorldTrashing>(entry.name);
                                }
                            )
                        );
                    })
                ),

                (new Button("", []()
                    {
                        Display::navPush<WorldCreation>();
                    }))
                    ->withIcon(Material::create)
                    ->withToolTip("New world"),

                (new Button("", [this]()
                    {
                        if (selectedWorld.index == nullptr)
                        {
                            Display::navPush<MessageDisplay>(Material::alert, "Error", selectedWorld.error);

                            return;
                        }

                        Display::navPush<WorldCloning>(selectedWorld.name, *selectedWorld.index);
                    }))
                    ->withIcon(Material::clone)
                    ->withToolTip("Clone world")
                    ->withFaceColor([this]()
                        {
                            if (selectedWorld.index == nullptr)
                            {
                                return Color::warning;
                            }

                            return Color::action;
                        })
                    ->enabledIf([this]() { return selectedWorld.name.size() != 0; }),

                (new Button("", [this]()
                    {
                        Display::navPush<WorldTrashing>(selectedWorld.name);
                    }))
                    ->withToolTip("Delete world")
                    ->withFaceColor(Color::danger)
                    ->withIcon(Material::trash)
                    ->enabledIf([this]() { return selectedWorld.name.size() != 0; }),

                Layout::WithProportion<1>(),
                new Panel(),

                new BackButton()
            )
        )
    );
}

void WorldSelection::update()
{
    WidgetDisplay::update();

    if (renameField != nullptr && !renameField->isFocused())
    {
        renameField = nullptr;
        worldBeingRenamed.clear();
    }

    if (SDL_GetTicks() - lastRefreshTicks > ticksBetweenRefreshes)
    {
        refresh();
    }
}