#include "SplitterContent.h"

#include <algorithm>
#include <optional>
#include <set>
#include <string>
#include <vector>

#include <QLabel>
#include <QListWidget>
#include <QVBoxLayout>

#include "BeltItemList.h"
#include "BeltSystem.h"
#include "BuildingTarget.h"
#include "ClearBeltControl.h"
#include "Command.h"
#include "CommandRequestedEvent.h"
#include "EventManager.h"
#include "FactoryQueries.h"
#include "GameConfig.h"
#include "ItemType.h"
#include "Rotation.h"
#include "SelectionNames.h"
#include "Simulation.h"

namespace
{

// Height cap on a filter list, so two of them plus the rest of the card still fit.
const int kFilterListHeightPx = 100;

QString getRotationLabel(Rotation rotation)
{
    // Written as code points because the sources are read as ASCII by the compiler.
    const QChar upArrow(0x2191);     // U+2191 UPWARDS ARROW
    const QChar rightArrow(0x2192);  // U+2192 RIGHTWARDS ARROW
    const QChar downArrow(0x2193);   // U+2193 DOWNWARDS ARROW
    const QChar leftArrow(0x2190);   // U+2190 LEFTWARDS ARROW

    switch (rotation)
    {
        case Rotation::North: return QObject::tr("North (%1)").arg(upArrow);
        case Rotation::East:  return QObject::tr("East (%1)").arg(rightArrow);
        case Rotation::South: return QObject::tr("South (%1)").arg(downArrow);
        case Rotation::West:  return QObject::tr("West (%1)").arg(leftArrow);
    }
    return QString();
}

// Every item type the economy knows, from both sides of every recipe.
std::vector<std::string> getAllItemIds(const RecipesConfig& recipes)
{
    std::set<std::string> seen;
    for (const RecipeDef& recipe : recipes.recipes)
    {
        for (const RecipeIngredient& ingredient : recipe.inputs)
        {
            seen.insert(ingredient.item);
        }
        for (const std::string& item : getProducibleItems(recipe))
        {
            seen.insert(item);
        }
    }
    return std::vector<std::string>(seen.begin(), seen.end());
}

std::vector<ItemType> collectCheckedItems(const QListWidget* list)
{
    std::vector<ItemType> filter;
    for (int row = 0; row < list->count(); ++row)
    {
        const QListWidgetItem* item = list->item(row);
        if (item->checkState() == Qt::Checked)
        {
            filter.push_back(ItemType{ item->text().toStdString() });
        }
    }
    return filter;
}

}  // namespace


SplitterContent::SplitterContent(const SelectionContext& context,
                                 const SelectionRequest& request, QWidget* parent)
    : SelectionContent(context, asConstructionSite(context, request.buildings.front()),
                       parent)
    , m_id(request.buildings.front())
    , m_isSite(asConstructionSite(context, m_id).has_value())
    , m_tile(0, 0)
{
    m_filterALabel = new QLabel(this);
    m_filterAList  = new QListWidget(this);
    m_filterBLabel = new QLabel(this);
    m_filterBList  = new QListWidget(this);
    m_filterAList->setMaximumHeight(kFilterListHeightPx);
    m_filterBList->setMaximumHeight(kFilterListHeightPx);

    getConfigurationLayout()->addWidget(m_filterALabel);
    getConfigurationLayout()->addWidget(m_filterAList);
    getConfigurationLayout()->addWidget(m_filterBLabel);
    getConfigurationLayout()->addWidget(m_filterBList);

    // What the splitter is holding, then the button that removes it (REQ-UI-BELT-ITEMS,
    // REQ-UI-BELT-CLEAR) -- the same pair the belt card shows, over one tile.
    m_itemList = new BeltItemList(context, request.buildings, this);
    getRuntimeLayout()->addWidget(m_itemList);
    getRuntimeLayout()->addWidget(
        new ClearBeltControl(context, request.buildings, this));

    // Populated once, here, rather than on every refresh: re-checking the boxes at 30 Hz
    // would fight the player's clicks.
    populateFilters();

    connect(m_filterAList, &QListWidget::itemChanged,
            this, [this]() { applyFilters(); });
    connect(m_filterBList, &QListWidget::itemChanged,
            this, [this]() { applyFilters(); });
}

void SplitterContent::refreshConfiguration()
{
    const BuildingTarget target = resolveBuildingTarget(getContext(), m_id);
    if (!target.isValid())
    {
        return;
    }
    setBuildingIdentity(target.type, getBuildingTypeName(target.type));
}

void SplitterContent::refreshRuntime()
{
    m_itemList->refresh();
}

void SplitterContent::populateFilters()
{
    const BuildingTarget target = resolveBuildingTarget(getContext(), m_id);
    if (!target.isValid())
    {
        return;
    }

    // An operational splitter's outputs and filters live in the belt subsystem, keyed by
    // tile; a site's are stored on the site itself (REQ-BLD-SITE-CONFIG).
    std::optional<BeltSystem::SplitterInfo> info;
    if (m_isSite)
    {
        info = getSiteSplitterInfo(getContext().sim->getFactoryState(),
                                   *getContext().config, m_id);
    }
    else
    {
        m_tile = target.anchor;
        info = getContext().sim->getBelts().getSplitterInfo(m_tile);
    }
    if (!info.has_value())
    {
        m_filterALabel->hide();
        m_filterAList->hide();
        m_filterBLabel->hide();
        m_filterBList->hide();
        return;
    }

    const std::vector<std::string> itemIds = getAllItemIds(getContext().config->recipes);

    auto fillList = [&](QListWidget* list, QLabel* label, const QString& directionLabel,
                        const std::vector<ItemType>& filter)
    {
        label->setText(tr("%1 filter (empty = all):").arg(directionLabel));
        list->blockSignals(true);
        list->clear();
        for (const std::string& itemId : itemIds)
        {
            // Only implicitly unlocked item types are offered (REQ-LOCK-UI-SPLITTER).
            if (!getContext().sim->isItemUnlocked(itemId)) { continue; }

            QListWidgetItem* row =
                new QListWidgetItem(QString::fromStdString(itemId), list);
            const bool checked = !filter.empty()
                && std::find(filter.begin(), filter.end(), ItemType{ itemId })
                       != filter.end();
            row->setCheckState(checked ? Qt::Checked : Qt::Unchecked);
            row->setFlags(row->flags() | Qt::ItemIsUserCheckable);
        }
        list->blockSignals(false);
        label->show();
        list->show();
    };

    fillList(m_filterAList, m_filterALabel, getRotationLabel(info->outputA),
             info->filterA);
    fillList(m_filterBList, m_filterBLabel, getRotationLabel(info->outputB),
             info->filterB);
}

void SplitterContent::applyFilters()
{
    if (m_isSite)
    {
        std::shared_ptr<SetSiteSplitterFiltersCommand> command =
            std::make_shared<SetSiteSplitterFiltersCommand>();
        command->id      = m_id;
        command->filterA = collectCheckedItems(m_filterAList);
        command->filterB = collectCheckedItems(m_filterBList);
        EventManager::getInstance()->sendEventImmediately(
            std::make_shared<CommandRequestedEvent>(command));
        return;
    }

    std::shared_ptr<SetSplitterFiltersCommand> command =
        std::make_shared<SetSplitterFiltersCommand>();
    command->tile    = m_tile;
    command->filterA = collectCheckedItems(m_filterAList);
    command->filterB = collectCheckedItems(m_filterBList);
    EventManager::getInstance()->sendEventImmediately(
        std::make_shared<CommandRequestedEvent>(command));
}
