/*
 *  The Mana Client
 *  Copyright (C) 2004-2009  The Mana World Development Team
 *  Copyright (C) 2009-2010  The Mana Developers
 *
 *  This file is part of The Mana Client.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "gui/popupmenu.h"

#include "actorspritemanager.h"
#include "being.h"
#include "dropshortcut.h"
#include "flooritem.h"
#include "graphics.h"
#include "item.h"
#include "itemshortcut.h"
#include "localplayer.h"
#include "log.h"
#include "map.h"
#include "party.h"
#include "playerrelations.h"
#include "spellmanager.h"

#include "gui/buy.h"
#include "gui/chat.h"
#include "gui/inventorywindow.h"
#include "gui/itemamount.h"
#include "gui/outfitwindow.h"
#include "gui/sell.h"
#include "gui/socialwindow.h"
#include "gui/textcommandeditor.h"
#include "gui/textdialog.h"
#include "gui/trade.h"
#include "gui/viewport.h"

#include "gui/widgets/browserbox.h"
#include "gui/widgets/chattab.h"
#include "gui/widgets/whispertab.h"

#include "net/adminhandler.h"
#include "net/buysellhandler.h"
#include "net/inventoryhandler.h"
#include "net/net.h"
#include "net/npchandler.h"
#include "net/partyhandler.h"
#include "net/tradehandler.h"

#include "resources/itemdb.h"
#include "resources/iteminfo.h"

#include "utils/gettext.h"
#include "utils/stringutils.h"

#include <cassert>

std::string tradePartnerName("");

PopupMenu::PopupMenu():
    Popup("PopupMenu"),
    mBeingId(0),
    mFloorItem(0),
    mItem(0),
    mMapItem(0),
    mTab(0),
    mSpell(0),
    mDialog(0)
{
    mBrowserBox = new BrowserBox;
    mBrowserBox->setPosition(4, 4);
    mBrowserBox->setHighlightMode(BrowserBox::BACKGROUND);
    mBrowserBox->setOpaque(false);
    mBrowserBox->setLinkHandler(this);
    add(mBrowserBox);
}

void PopupMenu::showPopup(int x, int y, Being *being)
{
    if (!being)
        return;

    mBeingId = being->getId();
    mBrowserBox->clearRows();

    const std::string &name = being->getName();

    mBrowserBox->addRow(name);

    switch (being->getType())
    {
        case ActorSprite::PLAYER:
            {
                // Players can be traded with.
                mBrowserBox->addRow(_("@@trade|Trade@@"));
                // TRANSLATORS: Attacking a player.
                mBrowserBox->addRow(_("@@attack|Attack@@"));
                // TRANSLATORS: Whispering a player.
                mBrowserBox->addRow(_("@@whisper|Whisper@@"));

                mBrowserBox->addRow("##3---");

                mBrowserBox->addRow(_("@@heal|Heal@@"));
                mBrowserBox->addRow("##3---");

                switch (player_relations.getRelation(name))
                {
                    case PlayerRelation::NEUTRAL:
                        mBrowserBox->addRow(_("@@friend|Befriend@@"));
                        mBrowserBox->addRow(_("@@disregard|Disregard@@"));
                        mBrowserBox->addRow(_("@@ignore|Ignore@@"));
                        mBrowserBox->addRow(_("@@erase|Erase@@"));

                    case PlayerRelation::FRIEND:
                        mBrowserBox->addRow(_("@@disregard|Disregard@@"));
                        mBrowserBox->addRow(_("@@ignore|Ignore@@"));
                        mBrowserBox->addRow(_("@@erase|Erase@@"));
                        break;

                    case PlayerRelation::DISREGARDED:
                        mBrowserBox->addRow(_("@@unignore|Unignore@@"));
                        mBrowserBox->addRow(_("@@ignore|Completely ignore@@"));
                        mBrowserBox->addRow(_("@@erase|Erase@@"));
                        break;

                    case PlayerRelation::IGNORED:
                        mBrowserBox->addRow(_("@@unignore|Unignore@@"));
                        mBrowserBox->addRow(_("@@erase|Erase@@"));
                        break;

                    case PlayerRelation::ERASED:
                        mBrowserBox->addRow(_("@@unignore|Unignore@@"));
                        mBrowserBox->addRow(_("@@disregard|Disregard@@"));
                        mBrowserBox->addRow(_("@@ignore|Completely ignore@@"));
                        break;

                    default:
                        break;
                }

                mBrowserBox->addRow("##3---");
                mBrowserBox->addRow(_("@@follow|Follow@@"));
                mBrowserBox->addRow(_("@@imitation|Imitation@@"));

                if (player_node->isInParty())
                {
                    mBrowserBox->addRow(_("@@party|Invite to party@@"));
                    mBrowserBox->addRow(_("@@kick party|Kick from party@@"));
                }

                if (player_node->isGM())
                {
                    mBrowserBox->addRow("##3---");
                    mBrowserBox->addRow(_("@@admin-kick|Kick player@@"));
                }
                mBrowserBox->addRow("@@nuke|Nuke@@");

                mBrowserBox->addRow("##3---");
                mBrowserBox->addRow(_("@@buy|Buy@@"));
                mBrowserBox->addRow(_("@@sell|Sell@@"));
            }
            break;

        case ActorSprite::NPC:
            // NPCs can be talked to (single option, candidate for removal
            // unless more options would be added)
            mBrowserBox->addRow(_("@@talk|Talk@@"));

            mBrowserBox->addRow(_("@@buy|Buy@@"));
            mBrowserBox->addRow(_("@@sell|Sell@@"));
            break;

        case ActorSprite::MONSTER:
            {
                // Monsters can be attacked
                mBrowserBox->addRow(_("@@attack|Attack@@"));

                if (player_node->isGM())
                    mBrowserBox->addRow(_("@@admin-kick|Kick@@"));
            }
            break;

        default:
            /* Other beings aren't interesting... */
            return;
    }
    mBrowserBox->addRow(_("@@name|Add name to chat@@"));

    //mBrowserBox->addRow(strprintf("@@look|%s@@", _("Look To")));
    mBrowserBox->addRow("##3---");
    mBrowserBox->addRow(_("@@cancel|Cancel@@"));

    showPopup(x, y);
}

void PopupMenu::showPopup(int x, int y, FloorItem *floorItem)
{
    if (!floorItem)
        return;

    mFloorItem = floorItem;
    ItemInfo info = floorItem->getInfo();
    mBrowserBox->clearRows();

    // Floor item can be picked up (single option, candidate for removal)
    std::string name = info.getName();
    mBrowserBox->addRow(name);
    mBrowserBox->addRow(_("@@pickup|Pick up@@"));
    mBrowserBox->addRow(_("@@chat|Add to chat@@"));

    //mBrowserBox->addRow(strprintf("@@look|%s@@", _("Look To")));
    mBrowserBox->addRow("##3---");
    mBrowserBox->addRow(_("@@cancel|Cancel@@"));

    showPopup(x, y);
}

void PopupMenu::showPopup(int x, int y, MapItem *mapItem)
{
    if (!mapItem)
        return;

    mMapItem = mapItem;

    mBrowserBox->clearRows();

    mBrowserBox->addRow(_("Map Item"));
    mBrowserBox->addRow(_("@@rename map|Rename@@"));
    mBrowserBox->addRow(_("@@remove map|Remove@@"));

    mBrowserBox->addRow("##3---");
    mBrowserBox->addRow(_("@@cancel|Cancel@@"));

    showPopup(x, y);
}

void PopupMenu::showOutfitsPopup(int x, int y)
{
    mBrowserBox->clearRows();

    mBrowserBox->addRow(_("Outfits"));
    mBrowserBox->addRow(_("@@load old outfits|Load old outfits@@"));

    mBrowserBox->addRow("##3---");
    mBrowserBox->addRow(_("@@cancel|Cancel@@"));

    showPopup(x, y);
}

void PopupMenu::showSpellPopup(int x, int y, TextCommand *cmd)
{
    mBrowserBox->clearRows();

    mSpell = cmd;
    mBrowserBox->addRow(_("Spells"));
    mBrowserBox->addRow(_("@@load old spells|Load old spells@@"));
    mBrowserBox->addRow(_("@@edit spell|Edit spell@@"));

    mBrowserBox->addRow("##3---");
    mBrowserBox->addRow(_("@@cancel|Cancel@@"));

    showPopup(x, y);
}

void PopupMenu::showChatPopup(int x, int y, ChatTab *tab)
{
    if (!tab)
        return;

    mTab = tab;

    mBrowserBox->clearRows();

    if (tab->getType() == ChatTab::TAB_WHISPER)
        mBrowserBox->addRow(_("@@chat close|Close@@"));
    if (tab->getType() == ChatTab::TAB_PARTY)
        mBrowserBox->addRow(_("@@leave party|Leave@@"));
    mBrowserBox->addRow(strprintf("@@chat clear|%s@@", _("Clear")));
    mBrowserBox->addRow("##3---");

    if (tab->getType() == ChatTab::TAB_WHISPER)
    {
        WhisperTab *wTab = static_cast<WhisperTab*>(tab);
        std::string name = wTab->getNick();

        Being* being  = actorSpriteManager->findBeingByName(
            name, Being::PLAYER);

        if (being)
        {
            mBeingId = being->getId();

            mBrowserBox->addRow(_("@@trade|Trade@@"));
            mBrowserBox->addRow(_("@@attack|Attack@@"));

            mBrowserBox->addRow("##3---");

            mBrowserBox->addRow(_("@@heal|Heal@@"));
            mBrowserBox->addRow("##3---");

            switch (player_relations.getRelation(name))
            {
                case PlayerRelation::NEUTRAL:
                    mBrowserBox->addRow(_("@@friend|Befriend@@"));
                    mBrowserBox->addRow(_("@@disregard|Disregard@@"));
                    mBrowserBox->addRow(_("@@ignore|Ignore@@"));
                    mBrowserBox->addRow(_("@@erase|Erase@@"));

                case PlayerRelation::FRIEND:
                    mBrowserBox->addRow(_("@@disregard|Disregard@@"));
                    mBrowserBox->addRow(_("@@ignore|Ignore@@"));
                    mBrowserBox->addRow(_("@@erase|Erase@@"));
                    break;

                case PlayerRelation::DISREGARDED:
                    mBrowserBox->addRow(_("@@unignore|Unignore@@"));
                    mBrowserBox->addRow(_("@@ignore|Completely ignore@@"));
                    mBrowserBox->addRow(_("@@erase|Erase@@"));
                    break;

                case PlayerRelation::IGNORED:
                    mBrowserBox->addRow(_("@@unignore|Unignore@@"));
                    mBrowserBox->addRow(_("@@erase|Erase@@"));
                    break;

                case PlayerRelation::ERASED:
                    mBrowserBox->addRow(_("@@unignore|Unignore@@"));
                    mBrowserBox->addRow(_("@@disregard|Disregard@@"));
                    mBrowserBox->addRow(_("@@ignore|Completely ignore@@"));
                    break;

                default:
                    break;
            }

            mBrowserBox->addRow("##3---");
            mBrowserBox->addRow(_("@@follow|Follow@@"));
            mBrowserBox->addRow(_("@@imitation|Imitation@@"));

            mBrowserBox->addRow("##3---");

            if (player_node->isInParty())
            {
                if (player_node->getParty())
                {
                    if (!player_node->getParty()->isMember(wTab->getNick()))
                    {
                        mBrowserBox->addRow(_("@@party|Invite to party@@"));
                    }
                    else
                    {
                        mBrowserBox->addRow(
                            _("@@kick party|Kick from party@@"));
                    }
                    mBrowserBox->addRow("##3---");
                }
            }
        }
    }
    mBrowserBox->addRow(strprintf(_("@@cancel|Cancel@@")));

    showPopup(x, y);
}

void PopupMenu::handleLink(const std::string &link,
                           gcn::MouseEvent *event _UNUSED_)
{
    if (!actorSpriteManager)
        return;

    Being *being = actorSpriteManager->findBeing(mBeingId);

    // Talk To action
    if (link == "talk" && being && being->canTalk())
    {
        being->talkTo();
    }
    // Trade action
    else if (link == "trade" && being &&
             being->getType() == ActorSprite::PLAYER)
    {
        Net::getTradeHandler()->request(being);
        tradePartnerName = being->getName();
        tradeWindow->clear();
    }
    else if (link == "buy" &&
             being && mBeingId != 0)
    {
        if (being->getType() == Being::NPC)
            Net::getNpcHandler()->buy(mBeingId);
        else if (being->getType() == Being::PLAYER)
            Net::getBuySellHandler()->requestSellList(being->getName());
    }
    else if (link == "sell" &&
             being && mBeingId != 0)
    {
        if (being->getType() == Being::NPC)
            Net::getNpcHandler()->sell(mBeingId);
        else if (being->getType() == Being::PLAYER)
            Net::getBuySellHandler()->requestBuyList(being->getName());
    }
    // Attack action
    else if (link == "attack" && being)
    {
        player_node->attack(being, true);
    }
    else if (link == "heal" && being && being->getType() != Being::MONSTER)
    {
        actorSpriteManager->heal(player_node, being);
    }
    else if (link == "unignore" && being &&
             being->getType() == ActorSprite::PLAYER)
    {
        player_relations.setRelation(being->getName(),
                                     PlayerRelation::NEUTRAL);
    }
    else if (link == "ignore" && being &&
             being->getType() == ActorSprite::PLAYER)
    {
        player_relations.setRelation(being->getName(),
                                     PlayerRelation::IGNORED);
    }
    else if (link == "erase" && being &&
             being->getType() == ActorSprite::PLAYER)
    {
        player_relations.setRelation(being->getName(), PlayerRelation::ERASED);
    }
    else if (link == "disregard" && being &&
             being->getType() == ActorSprite::PLAYER)
    {
        player_relations.setRelation(being->getName(),
                                     PlayerRelation::DISREGARDED);
    }
    else if (link == "friend" && being &&
             being->getType() == ActorSprite::PLAYER)
    {
        player_relations.setRelation(being->getName(), PlayerRelation::FRIEND);
    }
    // Guild action
    else if (link == "guild" && being &&
             being->getType() == ActorSprite::PLAYER)
    {
        player_node->inviteToGuild(being);
    }
    else if (link == "nuke" && being)
    {
        actorSpriteManager->addBlock(being->getId());
        actorSpriteManager->destroy(being);
    }
    // Follow Player action
    else if (link == "follow" && being)
    {
        player_node->setFollow(being->getName());
    }
    else if (link == "imitation" && being)
    {
        player_node->setImitate(being->getName());
    }
    // Pick Up Floor Item action
    else if ((link == "pickup") && mFloorItem)
    {
        player_node->pickUp(mFloorItem);
    }
    // Look To action
    else if (link == "look")
    {
    }
    else if (link == "use" && mItem)
    {
        if (mItem->isEquipment())
        {
            if (mItem->isEquipped())
                Net::getInventoryHandler()->unequipItem(mItem);
            else
                Net::getInventoryHandler()->equipItem(mItem);
        }
        else
        {
            Net::getInventoryHandler()->useItem(mItem);
        }
    }
    else if (link == "chat" && mItem)
    {
        if (mItem)
            chatWindow->addItemText(mItem->getInfo().getName());
        else if (mFloorItem)
            chatWindow->addItemText(mFloorItem->getInfo().getName());
    }
    else if (link == "whisper" && being)
    {
        if (config.getValueBool("whispertab", false))
            chatWindow->localChatInput("/q " + being->getName());
        else
            chatWindow->addInputText("/w \"" + being->getName() + "\" ");
    }
    else if (link == "split" && mItem)
    {
        ItemAmountWindow::showWindow(ItemAmountWindow::ItemSplit,
                             inventoryWindow, mItem);
    }
    else if (link == "drop" && mItem)
    {
        ItemAmountWindow::showWindow(ItemAmountWindow::ItemDrop,
                             inventoryWindow, mItem);
    }
    else if (link == "store" && mItem)
    {
        ItemAmountWindow::showWindow(ItemAmountWindow::StoreAdd,
                             inventoryWindow, mItem);
    }
    else if (link == "retrieve" && mItem)
    {
        ItemAmountWindow::showWindow(ItemAmountWindow::StoreRemove, mWindow,
                                     mItem);
    }
    else if (link == "party" && being &&
             being->getType() == ActorSprite::PLAYER)
    {
        Net::getPartyHandler()->invite(being);
    }
    else if (link == "kick party" && being
             && being->getType() == Being::PLAYER)
    {
        Net::getPartyHandler()->kick(being);
    }
    else if (link == "name" && being)
    {
        const std::string &name = being->getName();
        chatWindow->addInputText(name);
    }
    else if (link == "admin-kick" && being &&
             (being->getType() == ActorSprite::PLAYER ||
             being->getType() == ActorSprite::MONSTER))
    {
        Net::getAdminHandler()->kick(being->getId());
    }
    else if (link == "chat close" && mTab)
    {
        mTab->handleCommand("close", "");
    }
    else if (link == "leave party" && mTab)
    {
        Net::getPartyHandler()->leave();
    }
    else if (link == "chat clear" && mTab && chatWindow)
    {
        chatWindow->clearTab();
    }
    else if (link == "remove map" && mMapItem)
    {
        Map *map = viewport->getCurrentMap();
        if (map)
        {
            SpecialLayer *specialLayer = map->getSpecialLayer();
            if (specialLayer)
            {
                specialLayer->setTile(mMapItem->getX(), mMapItem->getY(),
                                      MapItem::EMPTY);
            }
        }
    }
    else if (link == "rename map" && mMapItem)
    {
        mRenameListener.setMapItem(mMapItem);
        mDialog = new TextDialog(_("Rename map sign          "),
                                 _("Name:                    "));
        mRenameListener.setDialog(mDialog);
        mDialog->setText(mMapItem->getComment());
        mDialog->setActionEventId("ok");
        mDialog->addActionListener(&mRenameListener);
    }
    else if (link == "load old outfits" && outfitWindow)
    {
        outfitWindow->load(true);
    }
    else if (link == "load old spells" && spellManager)
    {
        spellManager->load(true);
        spellManager->save();
    }
    else if (link == "load old item shortcuts" && itemShortcut)
    {
        itemShortcut->load(true);
        itemShortcut->save();
    }
    else if (link == "load old drop shortcuts" && itemShortcut)
    {
        dropShortcut->load(true);
        dropShortcut->save();
    }
    else if (link == "edit spell" && mSpell)
    {
        new TextCommandEditor(mSpell);
    }
    // Unknown actions
    else if (link != "cancel")
    {
        logger->log("PopupMenu: Warning, unknown action '%s'", link.c_str());
    }

    setVisible(false);

    mBeingId = 0;
    mFloorItem = 0;
    mItem = 0;
    mMapItem = 0;
}

void PopupMenu::showPopup(Window *parent, int x, int y, Item *item,
                          bool isInventory)
{
    if (!item)
        return;

    mItem = item;
    mWindow = parent;
    mBrowserBox->clearRows();

    if (isInventory)
    {
        if (item->isEquipment())
        {
            if (item->isEquipped())
                mBrowserBox->addRow(strprintf("@@use|%s@@", _("Unequip")));
            else
                mBrowserBox->addRow(strprintf("@@use|%s@@", _("Equip")));
        }
        else
            mBrowserBox->addRow(strprintf("@@use|%s@@", _("Use")));

        if (item->getQuantity() > 1)
            mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop...")));
        else
            mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop")));

        if (Net::getInventoryHandler()->canSplit(item))
            mBrowserBox->addRow(strprintf("@@split|%s@@", _("Split")));

        if (InventoryWindow::isStorageActive())
            mBrowserBox->addRow(strprintf("@@store|%s@@", _("Store")));
    }
    // Assume in storage for now
    // TODO: make this whole system more flexible, if needed
    else
    {
        mBrowserBox->addRow(strprintf("@@retrieve|%s@@", _("Retrieve")));
    }
    mBrowserBox->addRow(strprintf("@@chat|%s@@", _("Add to chat")));
    mBrowserBox->addRow("##3---");
    mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel")));

    showPopup(x, y);
}

void PopupMenu::showItemPopup(int x, int y, Item *item)
{
    mItem = item;
    mBrowserBox->clearRows();

    if (item)
    {
        if (item->isEquipment())
        {
            if (item->isEquipped())
                mBrowserBox->addRow(strprintf("@@use|%s@@", _("Unequip")));
            else
                mBrowserBox->addRow(strprintf("@@use|%s@@", _("Equip")));
        }
        else
            mBrowserBox->addRow(strprintf("@@use|%s@@", _("Use")));

        if (item->getQuantity() > 1)
            mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop...")));
        else
            mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop")));

        if (Net::getInventoryHandler()->canSplit(item))
            mBrowserBox->addRow(strprintf("@@split|%s@@", _("Split")));

        if (InventoryWindow::isStorageActive())
            mBrowserBox->addRow(strprintf("@@store|%s@@", _("Store")));
        mBrowserBox->addRow(strprintf("@@chat|%s@@", _("Add to chat")));
        mBrowserBox->addRow("##3---");
    }
    mBrowserBox->addRow(strprintf("@@load old item shortcuts|%s@@",
                        _("Load old item shortcuts")));
    mBrowserBox->addRow("##3---");
    mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel")));

    showPopup(x, y);
}

void PopupMenu::showDropPopup(int x, int y, Item *item)
{
    mItem = item;
    mBrowserBox->clearRows();

    if (item)
    {
        if (item->isEquipment())
        {
            if (item->isEquipped())
                mBrowserBox->addRow(strprintf("@@use|%s@@", _("Unequip")));
            else
                mBrowserBox->addRow(strprintf("@@use|%s@@", _("Equip")));
        }
        else
            mBrowserBox->addRow(strprintf("@@use|%s@@", _("Use")));

        if (item->getQuantity() > 1)
            mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop...")));
        else
            mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop")));

        if (Net::getInventoryHandler()->canSplit(item))
            mBrowserBox->addRow(strprintf("@@split|%s@@", _("Split")));

        if (InventoryWindow::isStorageActive())
            mBrowserBox->addRow(strprintf("@@store|%s@@", _("Store")));
        mBrowserBox->addRow(strprintf("@@chat|%s@@", _("Add to chat")));
        mBrowserBox->addRow("##3---");
    }
    mBrowserBox->addRow(strprintf("@@load old drop shortcuts|%s@@",
                        _("Load old drop shortcuts")));
    mBrowserBox->addRow("##3---");
    mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel")));

    showPopup(x, y);
}

void PopupMenu::showPopup(int x, int y)
{
    setContentSize(mBrowserBox->getWidth() + 8, mBrowserBox->getHeight() + 8);
    if (graphics->getWidth() < (x + getWidth() + 5))
        x = graphics->getWidth() - getWidth();
    if (graphics->getHeight() < (y + getHeight() + 5))
        y = graphics->getHeight() - getHeight();
    setPosition(x, y);
    setVisible(true);
    requestMoveToTop();
}

void RenameListener::action(const gcn::ActionEvent &event)
{
    if (event.getId() == "ok" && mMapItem && viewport && mDialog)
    {
        mMapItem->setComment(mDialog->getText());
    }
    mDialog = 0;
}
