#include "RecipeLineRow.h"

#include <QFont>
#include <QHBoxLayout>
#include <QLabel>
#include <QLayoutItem>
#include <QMargins>
#include <QPixmap>
#include <QVBoxLayout>

#include "BuildingIconCache.h"
#include "ItemIconCache.h"
#include "ItemTooltip.h"

namespace
{

// Size the items' colored squares and the building's chip are drawn at on a line, in
// device-independent pixels. Larger than the artwork they carry, which the square
// insets (REQ-UI-ITEM-ICON).
const int kIconSizePx = 18;

// Adds a freshly built label to the line and shows it.
//
// The show is what makes it count: a widget created under an already-visible parent
// starts hidden, and a layout treats a hidden item as empty, so an unshown label would
// add nothing to the size hint the surrounding widget measures itself against as soon
// as this returns.
void addAndShow(QHBoxLayout* layout, QLabel* label)
{
    layout->addWidget(label);
    label->show();
}

// Empties one of the two rows, leaving the row widget and its layout in place.
void clearRow(QHBoxLayout* layout)
{
    while (QLayoutItem* item = layout->takeAt(0))
    {
        if (item->widget())
        {
            item->widget()->deleteLater();
        }
        delete item;
    }
}

}  // namespace


namespace
{

std::vector<RecipeLineRow::Amount> toAmounts(const RecipeOutputGroup& group)
{
    std::vector<RecipeLineRow::Amount> amounts;
    amounts.reserve(group.items.size());
    for (const RecipeOutput& out : group.items)
    {
        amounts.push_back(RecipeLineRow::Amount{ out.item, out.amount });
    }
    return amounts;
}

}  // namespace

std::vector<std::vector<RecipeLineRow::Amount>> RecipeLineRow::toOutputGroups(
    const RecipeDef& recipe, const std::vector<int>& groupIndices)
{
    std::vector<std::vector<Amount>> groups;
    groups.reserve(groupIndices.size());
    for (int index : groupIndices)
    {
        groups.push_back(toAmounts(recipe.outputGroups[static_cast<std::size_t>(index)]));
    }
    return groups;
}

std::vector<std::vector<RecipeLineRow::Amount>> RecipeLineRow::toUnlockedOutputGroups(
    const RecipeDef& recipe,
    const std::function<bool(const std::string&)>& isItemUnlocked)
{
    std::vector<std::vector<Amount>> groups;
    groups.reserve(recipe.outputGroups.size());
    for (const RecipeOutputGroup& group : recipe.outputGroups)
    {
        if (!isOutputGroupUnlocked(group, isItemUnlocked)) { continue; }

        groups.push_back(toAmounts(group));
    }
    return groups;
}

RecipeLineRow::RecipeLineRow(ItemIconCache* itemIcons, BuildingIconCache* buildingIcons,
                             QWidget* parent)
    : QWidget(parent)
    , m_itemIcons(itemIcons)
    , m_buildingIcons(buildingIcons)
{
    m_outerLayout = new QVBoxLayout(this);
    m_outerLayout->setContentsMargins(0, 0, 0, 0);
    m_outerLayout->setSpacing(1);

    m_headerRow    = new QWidget(this);
    m_headerLayout = new QHBoxLayout(m_headerRow);
    m_headerLayout->setContentsMargins(0, 0, 0, 0);
    m_headerLayout->setSpacing(4);
    m_headerRow->hide();
    m_outerLayout->addWidget(m_headerRow);

    m_amountsRow    = new QWidget(this);
    m_amountsLayout = new QHBoxLayout(m_amountsRow);
    m_amountsLayout->setContentsMargins(0, 0, 0, 0);
    m_amountsLayout->setSpacing(4);
    m_outerLayout->addWidget(m_amountsRow);

    hide();
}

void RecipeLineRow::setCardChrome(bool enabled)
{
    // Palette colors like the item chip's chrome (REQ-UI-SELECTION-CARD), not
    // visuals.toml, which is for world rendering. The selector names this class alone, so
    // nothing inside the card inherits the border.
    setAttribute(Qt::WA_StyledBackground, enabled);
    setStyleSheet(enabled
        ? QStringLiteral("RecipeLineRow { border: 1px solid palette(mid); "
                         "border-radius: 4px; }")
        : QString());
    m_outerLayout->setContentsMargins(enabled ? QMargins(6, 4, 6, 4)
                                              : QMargins(0, 0, 0, 0));
}

void RecipeLineRow::setItemTooltips(const ItemTooltipContext& context,
                                    TooltipTrigger::Trigger trigger)
{
    m_tooltipContext = context;
    m_tooltipTrigger = trigger;
}

void RecipeLineRow::setLine(const Spec& spec)
{
    if (spec.isEmpty())
    {
        // Forgotten as well as hidden, so re-selecting the same recipe later is seen as
        // a change and shows the row again.
        m_spec = Spec();
        hide();
        return;
    }
    if (spec == m_spec)
    {
        return;
    }

    m_spec = spec;
    rebuild(spec);
    show();
}

void RecipeLineRow::rebuild(const Spec& spec)
{
    clearRow(m_headerLayout);
    clearRow(m_amountsLayout);

    // First line: which building runs the recipe and which recipe it is, where the line
    // has to distinguish one producer from another (REQ-UI-ITEM-TOOLTIP). A building with
    // no chip file leaves the icon off, as everywhere else (REQ-UI-BUILD-ICON).
    if (spec.building.has_value() && m_buildingIcons != nullptr)
    {
        const QPixmap chip =
            m_buildingIcons->getChip(buildingTypeId(*spec.building), kIconSizePx);
        if (!chip.isNull())
        {
            QLabel* chipLabel = new QLabel(m_headerRow);
            chipLabel->setPixmap(chip);
            addAndShow(m_headerLayout, chipLabel);
        }
    }
    if (!spec.name.isEmpty())
    {
        QLabel* nameLabel = new QLabel(spec.name, m_headerRow);
        QFont nameFont = nameLabel->font();
        nameFont.setBold(true);
        nameLabel->setFont(nameFont);
        addAndShow(m_headerLayout, nameLabel);
    }
    const bool hasHeader = m_headerLayout->count() > 0;
    if (hasHeader)
    {
        m_headerLayout->addStretch(1);
    }
    m_headerRow->setVisible(hasHeader);

    // Second line: what the cycle costs, makes and takes.
    addAmounts(spec.inputs);
    if (!spec.inputs.empty() && !spec.outputGroups.empty())
    {
        const QChar rightArrow(0x2192);  // U+2192 RIGHTWARDS ARROW
        addAndShow(m_amountsLayout, new QLabel(QString(rightArrow), m_amountsRow));
    }
    for (std::size_t i = 0; i < spec.outputGroups.size(); ++i)
    {
        // Between one group and the next, so alternatives read as a choice rather than as
        // one combined yield -- which is what a run of `+` would say (REQ-UI-RECIPE-SUMMARY).
        if (i > 0)
        {
            addAndShow(m_amountsLayout, new QLabel(QStringLiteral("/"), m_amountsRow));
        }
        addAmounts(spec.outputGroups[i]);
    }

    if (spec.durationSeconds.has_value() && *spec.durationSeconds > 0.0)
    {
        const QChar middleDot(0x00B7);  // U+00B7 MIDDLE DOT
        const QString time = spec.durationIsAddition
            ? tr("+%1 s").arg(*spec.durationSeconds, 0, 'f', 1)
            : tr("%1 s").arg(*spec.durationSeconds, 0, 'f', 1);
        addAndShow(m_amountsLayout, new QLabel(
            QStringLiteral("%1 %2").arg(middleDot).arg(time), m_amountsRow));
    }
    m_amountsLayout->addStretch(1);
}

void RecipeLineRow::addAmounts(const std::vector<Amount>& amounts)
{
    for (const Amount& entry : amounts)
    {
        // Between one item and the next, so a run of icons and numbers reads as a sum
        // rather than as a list.
        if (&entry != &amounts.front())
        {
            addAndShow(m_amountsLayout, new QLabel(QStringLiteral("+"), m_amountsRow));
        }
        addAmount(entry);
    }
}

void RecipeLineRow::addAmount(const Amount& entry)
{
    // A widget of its own around the pair, so what the player points at is the whole
    // statement -- this many of that item -- rather than an icon the height of a line of
    // text (REQ-UI-ITEM-VALUE-TOOLTIP). Its spacing is the row's own, so the line reads
    // exactly as it did when the two labels sat in the row directly.
    QWidget* pair = new QWidget(m_amountsRow);
    QHBoxLayout* pairLayout = new QHBoxLayout(pair);
    pairLayout->setContentsMargins(0, 0, 0, 0);
    pairLayout->setSpacing(m_amountsLayout->spacing());

    // The item's icon on its colored square (REQ-UI-ITEM-ICON). A missing icon file
    // is not an error: the square stands alone then, and only an item with no square
    // either falls back to its id in text.
    const QPixmap icon = (m_itemIcons != nullptr)
        ? m_itemIcons->getSquarePixmap(entry.itemId, kIconSizePx)
        : QPixmap();
    if (!icon.isNull())
    {
        QLabel* iconLabel = new QLabel(pair);
        iconLabel->setPixmap(icon);
        addAndShow(pairLayout, iconLabel);
    }
    else
    {
        addAndShow(pairLayout,
                   new QLabel(QString::fromStdString(entry.itemId), pair));
    }
    addAndShow(pairLayout, new QLabel(QString::number(entry.amount), pair));

    if (m_tooltipContext.has_value())
    {
        ItemTooltip::attachTo(*pair, *m_tooltipContext, entry.itemId, m_tooltipTrigger);
    }

    m_amountsLayout->addWidget(pair);
    pair->show();
}
