#include "ItemTooltip.h"

#include <QFont>
#include <QGuiApplication>
#include <QLabel>
#include <QLayoutItem>
#include <QPalette>
#include <QRect>
#include <QScreen>
#include <QVBoxLayout>

#include "DisplayName.h"
#include "GameConfig.h"
#include "ItemProducers.h"
#include "RecipeLineRow.h"
#include "RecipesConfig.h"
#include "Simulation.h"

namespace
{

// Gap between the cursor and the tooltip's corner, in device-independent pixels. Wide
// enough that the cursor never covers the heading.
const int kCursorOffsetPx = 16;

std::vector<RecipeLineRow::Amount> toAmounts(
    const std::vector<RecipeIngredient>& ingredients)
{
    std::vector<RecipeLineRow::Amount> amounts;
    amounts.reserve(ingredients.size());
    for (const RecipeIngredient& ingredient : ingredients)
    {
        amounts.push_back(RecipeLineRow::Amount{ ingredient.item, ingredient.amount });
    }
    return amounts;
}

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

// The screen the cursor is on, falling back to the primary screen when the position is
// on none of them (a cursor between two screens of different heights).
QRect availableScreenRect(const QPoint& globalPos)
{
    const QScreen* screen = QGuiApplication::screenAt(globalPos);
    if (screen == nullptr)
    {
        screen = QGuiApplication::primaryScreen();
    }
    return (screen != nullptr) ? screen->availableGeometry() : QRect();
}

}  // namespace


ItemTooltip::ItemTooltip(const SelectionContext& context, const std::string& itemId,
                         QWidget* parent)
    : QFrame(parent, Qt::ToolTip)
    , m_context(context)
    , m_itemId(itemId)
{
    // The palette's tooltip colors behind a themed border, so it reads as a tooltip
    // rather than as a stray window.
    setFrameShape(QFrame::StyledPanel);
    setAutoFillBackground(true);
    QPalette tooltipPalette = palette();
    tooltipPalette.setColor(QPalette::Window,
                            tooltipPalette.color(QPalette::ToolTipBase));
    tooltipPalette.setColor(QPalette::WindowText,
                            tooltipPalette.color(QPalette::ToolTipText));
    setPalette(tooltipPalette);

    m_layout = new QVBoxLayout(this);
    m_layout->setContentsMargins(6, 4, 6, 4);
    m_layout->setSpacing(2);
}

void ItemTooltip::showAt(const QPoint& globalPos)
{
    rebuild();
    adjustSize();

    // Kept on the screen the cursor is on: a tooltip for a chip near the right or
    // bottom edge would otherwise be cut off.
    const QRect screen = availableScreenRect(globalPos);
    QPoint topLeft(globalPos.x() + kCursorOffsetPx, globalPos.y() + kCursorOffsetPx);
    if (screen.isValid())
    {
        topLeft.setX(qMax(screen.left(),
                          qMin(topLeft.x(), screen.right() - width() + 1)));
        topLeft.setY(qMax(screen.top(),
                          qMin(topLeft.y(), screen.bottom() - height() + 1)));
    }

    move(topLeft);
    show();
}

void ItemTooltip::rebuild()
{
    while (QLayoutItem* item = m_layout->takeAt(0))
    {
        if (item->widget())
        {
            item->widget()->deleteLater();
        }
        delete item;
    }

    // The heading names the item. For an input chip this is the only place it is named
    // at all, since such a chip carries a count and no name (REQ-UI-SINGLE-SELECTION).
    QLabel* heading =
        new QLabel(QString::fromStdString(toDisplayName(m_itemId)), this);
    QFont headingFont = heading->font();
    headingFont.setBold(true);
    heading->setFont(headingFont);
    m_layout->addWidget(heading);
    heading->show();

    const ItemProduction production =
        findItemProduction(m_itemId, *m_context.sim, *m_context.config);

    // Scrap is salvaged from debris rather than crafted (REQ-RES-DEBRIS-DROP), so it
    // says where it comes from in place of the caption and lists nothing.
    QLabel* caption = new QLabel(production.origin == ItemOrigin::Salvaged
        ? tr("Salvaged from debris")
        : tr("Produced by"), this);
    m_layout->addWidget(caption);
    caption->show();

    if (production.origin == ItemOrigin::Undiscovered)
    {
        // Made somehow, but by nothing the player has unlocked -- named without being
        // shown a path they cannot take yet (REQ-UI-ITEM-TOOLTIP).
        QLabel* undiscovered = new QLabel(tr("Undiscovered"), this);
        m_layout->addWidget(undiscovered);
        undiscovered->show();
        return;
    }

    for (const RecipeDef* recipe : production.recipes)
    {
        RecipeLineRow::Spec spec;
        spec.building        = recipe->building;
        spec.name            = QString::fromStdString(toDisplayName(recipe->id));
        spec.inputs          = toAmounts(recipe->inputs);
        spec.outputs         = toAmounts(recipe->outputs);
        spec.durationSeconds = recipe->durationSeconds;

        // Boxed, because an item with several producers stacks several of these and a run
        // of bare lines reads as one field of icons (REQ-UI-ITEM-TOOLTIP).
        RecipeLineRow* line =
            new RecipeLineRow(m_context.itemIcons, m_context.buildingIcons, this);
        line->setCardChrome(true);
        m_layout->addWidget(line);
        line->setLine(spec);
    }
}
