#include "card_menu.h"

#include "../../../client/settings/card_counter_settings.h"
#include "../../../interface/widgets/tabs/tab_game.h"
#include "../../board/card_item.h"
#include "../../zones/logic/view_zone_logic.h"
#include "../card_menu_action_type.h"
#include "../player.h"
#include "../player_actions.h"
#include "move_menu.h"
#include "pt_menu.h"

#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/card/relation/card_relation.h>
#include <libcockatrice/utility/zone_names.h>

CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive)
    : player(_player), card(_card), shortcutsActive(_shortcutsActive)
{
    auto playerActions = player->getPlayerActions();

    const QList<Player *> &players = player->getGame()->getPlayerManager()->getPlayers().values();

    for (auto playerToAdd : players) {
        if (playerToAdd == player) {
            continue;
        }
        playersInfo.append(qMakePair(playerToAdd->getPlayerInfo()->getName(), playerToAdd->getPlayerInfo()->getId()));
    }

    connect(player->getGame()->getPlayerManager(), &PlayerManager::playerRemoved, this, &CardMenu::removePlayer);

    aTap = new QAction(this);
    aTap->setData(cmTap);
    connect(aTap, &QAction::triggered, playerActions, &PlayerActions::cardMenuAction);
    aDoesntUntap = new QAction(this);
    aDoesntUntap->setData(cmDoesntUntap);
    aDoesntUntap->setCheckable(true);
    aDoesntUntap->setChecked(card != nullptr && card->getDoesntUntap());
    connect(aDoesntUntap, &QAction::triggered, playerActions, &PlayerActions::cardMenuAction);
    aAttach = new QAction(this);
    connect(aAttach, &QAction::triggered, playerActions, &PlayerActions::actAttach);
    aUnattach = new QAction(this);
    connect(aUnattach, &QAction::triggered, playerActions, &PlayerActions::actUnattach);
    aDrawArrow = new QAction(this);
    connect(aDrawArrow, &QAction::triggered, playerActions, &PlayerActions::actDrawArrow);
    aSetAnnotation = new QAction(this);
    connect(aSetAnnotation, &QAction::triggered, playerActions, &PlayerActions::actSetAnnotation);
    aFlip = new QAction(this);
    aFlip->setData(cmFlip);
    connect(aFlip, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction);
    aPeek = new QAction(this);
    aPeek->setData(cmPeek);
    connect(aPeek, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction);
    aClone = new QAction(this);
    aClone->setData(cmClone);
    connect(aClone, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction);
    aSelectAll = new QAction(this);
    connect(aSelectAll, &QAction::triggered, playerActions, &PlayerActions::actSelectAll);
    aSelectRow = new QAction(this);
    connect(aSelectRow, &QAction::triggered, playerActions, &PlayerActions::actSelectRow);
    aSelectColumn = new QAction(this);
    connect(aSelectColumn, &QAction::triggered, playerActions, &PlayerActions::actSelectColumn);

    aPlay = new QAction(this);
    connect(aPlay, &QAction::triggered, playerActions, &PlayerActions::actPlay);
    aHide = new QAction(this);
    connect(aHide, &QAction::triggered, playerActions, &PlayerActions::actHide);
    aPlayFacedown = new QAction(this);
    connect(aPlayFacedown, &QAction::triggered, playerActions, &PlayerActions::actPlayFacedown);

    aRevealToAll = new QAction(this);

    mCardCounters = new QMenu;

    for (int i = 0; i < 6; ++i) {
        auto *tempAddCounter = new QAction(this);
        tempAddCounter->setData(9 + i * 1000);
        auto *tempRemoveCounter = new QAction(this);
        tempRemoveCounter->setData(10 + i * 1000);
        auto *tempSetCounter = new QAction(this);
        tempSetCounter->setData(11 + i * 1000);
        aAddCounter.append(tempAddCounter);
        aRemoveCounter.append(tempRemoveCounter);
        aSetCounter.append(tempSetCounter);
        connect(tempAddCounter, &QAction::triggered, playerActions, &PlayerActions::actCardCounterTrigger);
        connect(tempRemoveCounter, &QAction::triggered, playerActions, &PlayerActions::actCardCounterTrigger);
        connect(tempSetCounter, &QAction::triggered, playerActions, &PlayerActions::actCardCounterTrigger);
    }

    setShortcutsActive();

    retranslateUi();

    if (card == nullptr) {
        return;
    }

    bool revealedCard = false;
    bool writeableCard = player->getPlayerInfo()->getLocalOrJudge();
    if (auto *view = qobject_cast<ZoneViewZoneLogic *>(card->getZone())) {
        if (view->getRevealZone()) {
            if (view->getWriteableRevealZone()) {
                writeableCard = true;
            } else {
                revealedCard = true;
            }
        }
    }

    if (revealedCard) {
        addAction(aHide);
        addSeparator();
        addAction(aClone);
        addSeparator();
        addAction(aSelectAll);
        addAction(aSelectColumn);
        addRelatedCardView();
    } else {
        if (card->getZone()) {
            if (card->getZone()->getName() == ZoneNames::TABLE) {
                createTableMenu(writeableCard);
            } else if (card->getZone()->getName() == ZoneNames::STACK) {
                createStackMenu(writeableCard);
            } else if (card->getZone()->getName() == ZoneNames::EXILE ||
                       card->getZone()->getName() == ZoneNames::GRAVE) {
                createGraveyardOrExileMenu(writeableCard);
            } else {
                createHandOrCustomZoneMenu(writeableCard);
            }
        } else {
            createZonelessMenu(writeableCard);
        }
    }
}

void CardMenu::removePlayer(Player *playerToRemove)
{
    for (auto it = playersInfo.begin(); it != playersInfo.end();) {
        if (it->second == playerToRemove->getPlayerInfo()->getId()) {
            it = playersInfo.erase(it);
        } else {
            ++it;
        }
    }
}

void CardMenu::createTableMenu(bool canModifyCard)
{
    // Card is on the battlefield
    if (!canModifyCard) {
        addAction(aDrawArrow);
        addSeparator();
        addAction(aClone);
        addSeparator();
        addAction(aSelectAll);
        addAction(aSelectRow);
        addRelatedCardView();
        addRelatedCardActions();
        return;
    }

    addAction(aTap);
    addAction(aDoesntUntap);
    addAction(aFlip);
    if (card->getFaceDown()) {
        addAction(aPeek);
    }
    addSeparator();
    addAction(aClone);
    addMenu(new MoveMenu(player));
    addSeparator();
    addAction(aAttach);
    if (card->getAttachedTo()) {
        addAction(aUnattach);
    }
    addAction(aDrawArrow);
    addSeparator();
    addMenu(new PtMenu(player));
    addAction(aSetAnnotation);
    addSeparator();
    addAction(aSelectAll);
    addAction(aSelectRow);

    addSeparator();
    mCardCounters->clear();
    for (int i = 0; i < aAddCounter.size(); ++i) {
        mCardCounters->addSeparator();
        mCardCounters->addAction(aAddCounter[i]);
        if (card->getCounters().contains(i)) {
            mCardCounters->addAction(aRemoveCounter[i]);
        }
        mCardCounters->addAction(aSetCounter[i]);
    }
    addSeparator();
    addMenu(mCardCounters);
    addRelatedCardView();
    addRelatedCardActions();
}

void CardMenu::createStackMenu(bool canModifyCard)
{
    // Card is on the stack
    if (!canModifyCard) {
        addAction(aDrawArrow);
        addSeparator();
        addAction(aClone);
        addSeparator();
        addAction(aSelectAll);
        addRelatedCardView();
        addRelatedCardActions();
        return;
    }

    addAction(aPlay);
    addAction(aPlayFacedown);
    addSeparator();
    addAction(aClone);
    addMenu(new MoveMenu(player));
    addSeparator();
    addAction(aAttach);
    addAction(aDrawArrow);
    addSeparator();
    addAction(aSelectAll);
    addRelatedCardView();
    addRelatedCardActions();
}

void CardMenu::createGraveyardOrExileMenu(bool canModifyCard)
{
    // Card is in the graveyard or exile
    if (!canModifyCard) {
        addAction(aDrawArrow);
        addSeparator();
        addAction(aClone);
        addSeparator();
        addAction(aSelectAll);
        addAction(aSelectColumn);
        addRelatedCardView();
        addRelatedCardActions();
        return;
    }

    addAction(aPlay);
    addAction(aPlayFacedown);
    addSeparator();
    addAction(aClone);
    addMenu(new MoveMenu(player));
    addSeparator();
    addAction(aAttach);
    addAction(aDrawArrow);
    addSeparator();
    addAction(aSelectAll);
    addAction(aSelectColumn);
    addRelatedCardView();
    addRelatedCardActions();
}

void CardMenu::createHandOrCustomZoneMenu(bool canModifyCard)
{
    if (!canModifyCard) {
        addAction(aDrawArrow);
        addSeparator();
        addAction(aClone);
        addSeparator();
        addAction(aSelectAll);
        addRelatedCardView();
        addRelatedCardActions();
        return;
    }

    // Card is in hand or a custom zone specified by server
    addAction(aPlay);
    addAction(aPlayFacedown);

    QMenu *revealMenu = addMenu(tr("Re&veal to..."));

    initContextualPlayersMenu(revealMenu, aRevealToAll);

    connect(revealMenu, &QMenu::triggered, player->getPlayerActions(), &PlayerActions::actReveal);

    addSeparator();
    addAction(aClone);
    addMenu(new MoveMenu(player));

    // actions that are really wonky when done from deck or sideboard
    if (card->getZone()->getName() == ZoneNames::HAND) {
        addSeparator();
        addAction(aAttach);
        addAction(aDrawArrow);
    }

    addSeparator();
    addAction(aSelectAll);
    if (qobject_cast<ZoneViewZoneLogic *>(card->getZone())) {
        addAction(aSelectColumn);
    }

    addRelatedCardView();
    if (card->getZone()->getName() == ZoneNames::HAND) {
        addRelatedCardActions();
    }
}

void CardMenu::createZonelessMenu(bool canModifyCard)
{
    if (canModifyCard) {
        addMenu(new MoveMenu(player));
    }
}

/**
 * @brief Populates the menu with an action for each active player.
 *
 * The "all players" action is created separately, so it has to be passed into this function.
 * It will be put at the top of the menu.
 *
 * @param menu The menu to add the player actions to.
 * @param allPlayersAction The action for "all players".
 */
void CardMenu::initContextualPlayersMenu(QMenu *menu, QAction *allPlayersAction)
{
    allPlayersAction->setData(-1);
    menu->addAction(allPlayersAction);
    menu->addSeparator();

    for (const auto &playerInfo : playersInfo) {
        menu->addAction(playerInfo.first)->setData(playerInfo.second);
    }
}

void CardMenu::addRelatedCardView()
{
    if (!card) {
        return;
    }
    auto exactCard = card->getCard();
    if (!exactCard) {
        return;
    }

    bool atLeastOneGoodRelationFound = false;
    QList<CardRelation *> relatedCards = exactCard.getInfo().getAllRelatedCards();
    for (const CardRelation *cardRelation : relatedCards) {
        CardInfoPtr relatedCard = CardDatabaseManager::query()->getCardInfo(cardRelation->getName());
        if (relatedCard != nullptr) {
            atLeastOneGoodRelationFound = true;
            break;
        }
    }

    if (!atLeastOneGoodRelationFound) {
        return;
    }

    addSeparator();
    auto viewRelatedCards = new QMenu(tr("View related cards"));
    addMenu(viewRelatedCards);
    for (const CardRelation *relatedCard : relatedCards) {
        QString relatedCardName = relatedCard->getName();
        CardRef cardRef = {relatedCardName, exactCard.getPrinting().getUuid()};
        QAction *viewCard = viewRelatedCards->addAction(relatedCardName);
        Q_UNUSED(viewCard);

        connect(viewCard, &QAction::triggered, player->getGame(),
                [this, cardRef] { player->getGame()->getTab()->viewCardInfo(cardRef); });
    }
}

void CardMenu::addRelatedCardActions()
{
    if (!card) {
        return;
    }
    auto exactCard = card->getCard();
    if (!exactCard) {
        return;
    }

    QList<CardRelation *> relatedCards = exactCard.getInfo().getAllRelatedCards();
    if (relatedCards.isEmpty()) {
        return;
    }

    addSeparator();
    int index = 0;
    QAction *createRelatedCards = nullptr;
    for (const CardRelation *cardRelation : relatedCards) {
        ExactCard relatedCard =
            CardDatabaseManager::query()->getCardFromSameSet(cardRelation->getName(), card->getCard().getPrinting());
        if (!relatedCard) {
            relatedCard = CardDatabaseManager::query()->getCard({cardRelation->getName()});
        }
        if (!relatedCard) {
            continue;
        }

        QString relatedCardName;
        if (relatedCard.getInfo().getPowTough().size() > 0) {
            relatedCardName = relatedCard.getInfo().getPowTough() + " " + relatedCard.getName(); // "n/n name"
        } else {
            relatedCardName = relatedCard.getName(); // "name"
        }

        QString text = tr("Token: ");
        if (cardRelation->getDoesAttach()) {
            text +=
                tr(cardRelation->getDoesTransform() ? "Transform into " : "Attach to ") + "\"" + relatedCardName + "\"";
        } else if (cardRelation->getIsVariable()) {
            text += "X " + relatedCardName;
        } else if (cardRelation->getDefaultCount() != 1) {
            text += QString::number(cardRelation->getDefaultCount()) + "x " + relatedCardName;
        } else {
            text += relatedCardName;
        }

        if (createRelatedCards == nullptr) {
            if (relatedCards.length() == 1) {
                createRelatedCards = new QAction(text, this); // set actCreateAllRelatedCards with this text
                break; // do not set an individual entry as there is only one entry
            } else {
                createRelatedCards = new QAction(tr("All tokens"), this);
            }
        }

        auto *createRelated = new QAction(text, this);
        createRelated->setData(QVariant(index++));
        connect(createRelated, &QAction::triggered, player->getPlayerActions(), &PlayerActions::actCreateRelatedCard);
        addAction(createRelated);
    }

    if (createRelatedCards) {
        if (shortcutsActive) {
            createRelatedCards->setShortcuts(
                SettingsCache::instance().shortcuts().getShortcut("Player/aCreateRelatedTokens"));
        }
        connect(createRelatedCards, &QAction::triggered, player->getPlayerActions(),
                &PlayerActions::actCreateAllRelatedCards);
        addAction(createRelatedCards);
    }
}

void CardMenu::retranslateUi()
{
    aSelectAll->setText(tr("&Select All"));
    aSelectRow->setText(tr("S&elect Row"));
    aSelectColumn->setText(tr("S&elect Column"));

    aPlay->setText(tr("&Play"));
    aHide->setText(tr("&Hide"));
    aPlayFacedown->setText(tr("Play &Face Down"));
    aRevealToAll->setText(tr("&All players"));
    //: Turn sideways or back again
    aTap->setText(tr("&Tap / Untap"));
    aDoesntUntap->setText(tr("Skip &untapping"));
    //: Turn face up/face down
    aFlip->setText(tr("T&urn Over")); // Only the user facing names in client got renamed to "turn over"
    // All code and proto bits are still unchanged (flip) for compatibility reasons
    // A protocol rewrite with v3 could incorporate that, see #3100
    aPeek->setText(tr("&Peek at card face"));
    aClone->setText(tr("&Clone"));
    aAttach->setText(tr("Attac&h to card..."));
    aUnattach->setText(tr("Unattac&h"));
    aDrawArrow->setText(tr("&Draw arrow..."));
    aSetAnnotation->setText(tr("&Set annotation..."));

    mCardCounters->setTitle(tr("Ca&rd counters"));

    auto &cardCounterSettings = SettingsCache::instance().cardCounters();

    for (int i = 0; i < aAddCounter.size(); ++i) {
        aAddCounter[i]->setText(tr("&Add counter (%1)").arg(cardCounterSettings.displayName(i)));
    }
    for (int i = 0; i < aRemoveCounter.size(); ++i) {
        aRemoveCounter[i]->setText(tr("&Remove counter (%1)").arg(cardCounterSettings.displayName(i)));
    }
    for (int i = 0; i < aSetCounter.size(); ++i) {
        aSetCounter[i]->setText(tr("&Set counters (%1)...").arg(cardCounterSettings.displayName(i)));
    }
}

void CardMenu::setShortcutsActive()
{
    ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();

    aHide->setShortcuts(shortcuts.getShortcut("Player/aHide"));
    aPlay->setShortcuts(shortcuts.getShortcut("Player/aPlay"));
    aPlayFacedown->setShortcuts(shortcuts.getShortcut("Player/aPlayFacedown"));
    aRevealToAll->setShortcuts(shortcuts.getShortcut("Player/aRevealToAll"));

    aTap->setShortcuts(shortcuts.getShortcut("Player/aTap"));
    aDoesntUntap->setShortcuts(shortcuts.getShortcut("Player/aDoesntUntap"));
    aFlip->setShortcuts(shortcuts.getShortcut("Player/aFlip"));
    aPeek->setShortcuts(shortcuts.getShortcut("Player/aPeek"));
    aClone->setShortcuts(shortcuts.getShortcut("Player/aClone"));
    aAttach->setShortcuts(shortcuts.getShortcut("Player/aAttach"));
    aUnattach->setShortcuts(shortcuts.getShortcut("Player/aUnattach"));
    aDrawArrow->setShortcuts(shortcuts.getShortcut("Player/aDrawArrow"));
    aSetAnnotation->setShortcuts(shortcuts.getShortcut("Player/aSetAnnotation"));

    aSelectAll->setShortcuts(shortcuts.getShortcut("Player/aSelectAll"));
    aSelectRow->setShortcuts(shortcuts.getShortcut("Player/aSelectRow"));
    aSelectColumn->setShortcuts(shortcuts.getShortcut("Player/aSelectColumn"));

    static const QStringList colorWords = {"Red", "Yellow", "Green", "Cyan", "Purple", "Magenta"};
    for (int i = 0; i < aAddCounter.size(); i++) {
        aAddCounter[i]->setShortcuts(shortcuts.getShortcut("Player/aCC" + colorWords[i]));
        aRemoveCounter[i]->setShortcuts(shortcuts.getShortcut("Player/aRC" + colorWords[i]));
        aSetCounter[i]->setShortcuts(shortcuts.getShortcut("Player/aSC" + colorWords[i]));
    }
}