/*
 *  The ManaPlus Client
 *  Copyright (C) 2004-2009  The Mana World Development Team
 *  Copyright (C) 2009-2010  The Mana Developers
 *  Copyright (C) 2011-2013  The ManaPlus Developers
 *
 *  This file is part of The ManaPlus 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/shopwindow.h"

#include "gui/buydialog.h"
#include "gui/itemamountwindow.h"
#include "gui/selldialog.h"
#include "gui/setup.h"
#include "gui/tradewindow.h"

#include "gui/widgets/chattab.h"
#include "gui/widgets/checkbox.h"
#include "gui/widgets/label.h"
#include "gui/widgets/layout.h"
#include "gui/widgets/scrollarea.h"
#include "gui/widgets/shopitems.h"
#include "gui/widgets/shoplistbox.h"
#include "gui/widgets/slider.h"
#include "gui/widgets/tradetab.h"

#include "actorspritemanager.h"
#include "auctionmanager.h"
#include "client.h"
#include "configuration.h"
#include "confirmdialog.h"
#include "inventory.h"
#include "item.h"
#include "localplayer.h"
#include "playerinfo.h"
#include "playerrelations.h"
#include "shopitem.h"
#include "soundconsts.h"
#include "soundmanager.h"
#include "units.h"

#include "net/net.h"
#include "net/chathandler.h"
#include "net/npchandler.h"
#include "net/playerhandler.h"
#include "net/tradehandler.h"

#include "resources/iteminfo.h"

#include "utils/gettext.h"

#include <sstream>

#include <sys/stat.h>

#include "debug.h"

extern std::string tradePartnerName;
ShopWindow::DialogList ShopWindow::instances;

ShopWindow::ShopWindow():
    // TRANSLATORS: shop window name
    Window(_("Personal Shop"), false, nullptr, "shop.xml"),
    gcn::ActionListener(),
    gcn::SelectionListener(),
    // TRANSLATORS: shop window button
    mCloseButton(new Button(this, _("Close"), "close", this)),
    mBuyShopItems(new ShopItems),
    mSellShopItems(new ShopItems),
    mBuyShopItemList(new ShopListBox(this, mBuyShopItems, mBuyShopItems)),
    mSellShopItemList(new ShopListBox(this, mSellShopItems, mSellShopItems)),
    mBuyScrollArea(new ScrollArea(mBuyShopItemList,
        getOptionBool("showbuybackground"), "shop_buy_background.xml")),
    mSellScrollArea(new ScrollArea(mSellShopItemList,
        getOptionBool("showsellbackground"), "shop_sell_background.xml")),
    // TRANSLATORS: shop window label
    mBuyLabel(new Label(this, _("Buy items"))),
    // TRANSLATORS: shop window label
    mSellLabel(new Label(this, _("Sell items"))),
    // TRANSLATORS: shop window label
    mBuyAddButton(new Button(this, _("Add"), "add buy", this)),
    // TRANSLATORS: shop window label
    mBuyDeleteButton(new Button(this, _("Delete"), "delete buy", this)),
    // TRANSLATORS: shop window label
    mBuyAnnounceButton(new Button(this, _("Announce"), "announce buy", this)),
    mBuyAuctionButton(nullptr),
    // TRANSLATORS: shop window button
    mSellAddButton(new Button(this, _("Add"), "add sell", this)),
    // TRANSLATORS: shop window button
    mSellDeleteButton(new Button(this, _("Delete"), "delete sell", this)),
    // TRANSLATORS: shop window button
    mSellAnnounceButton(new Button(this, _("Announce"),
        "announce sell", this)),
    mSellAuctionButton(nullptr),
    // TRANSLATORS: shop window checkbox
    mAnnounceLinks(new CheckBox(this, _("Show links in announce"), false,
        this, "link announce")),
    mSelectedItem(-1),
    mAnnonceTime(0),
    mLastRequestTimeList(0),
    mLastRequestTimeItem(0),
    mRandCounter(0),
    mAcceptPlayer(""),
    mTradeItem(nullptr),
    mTradeNick(""),
    mTradeMoney(0)
{
    setWindowName("Personal Shop");
    setResizable(true);
    setCloseButton(true);
    setStickyButtonLock(true);
    setMinWidth(260);
    setMinHeight(220);
    setDefaultSize(380, 300, ImageRect::CENTER);

    mAnnounceCounter[BUY] = 0;
    mAnnounceCounter[SELL] = 0;

    loadList();

    mBuyShopItemList->setPriceCheck(false);
    mSellShopItemList->setPriceCheck(false);

    mBuyScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
    mSellScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);

    mBuyShopItemList->addSelectionListener(this);
    mSellShopItemList->addSelectionListener(this);

    ContainerPlacer placer;
    placer = getPlacer(0, 0);

    placer(0, 0, mBuyLabel, 8).setPadding(3);
    placer(8, 0, mSellLabel, 8).setPadding(3);
    placer(0, 1, mBuyScrollArea, 8, 5).setPadding(3);
    placer(8, 1, mSellScrollArea, 8, 5).setPadding(3);
    placer(0, 6, mBuyAddButton);
    placer(1, 6, mBuyDeleteButton);
    placer(3, 6, mBuyAnnounceButton);
    placer(8, 6, mSellAddButton);
    placer(9, 6, mSellDeleteButton);
    placer(11, 6, mSellAnnounceButton);
    placer(0, 7, mAnnounceLinks, 8);
    placer(15, 7, mCloseButton);

    if (auctionManager && auctionManager->getEnableAuctionBot())
    {
        mBuyAuctionButton = new Button(this,
            // TRANSLATORS: shop window button
            _("Auction"), "auction buy", this);
        mSellAuctionButton = new Button(this,
            // TRANSLATORS: shop window button
            _("Auction"), "auction sell", this);
        placer(4, 6, mBuyAuctionButton);
        placer(12, 6, mSellAuctionButton);
    }
    else
    {
        mBuyAuctionButton = nullptr;
        mSellAuctionButton = nullptr;
    }

    Layout &layout = getLayout();
    layout.setRowHeight(0, Layout::AUTO_SET);

    center();
    loadWindowState();

    instances.push_back(this);
    setVisible(false);
    enableVisibleSound(true);

    updateButtonsAndLabels();
}

ShopWindow::~ShopWindow()
{
    saveList();

    delete mBuyShopItems;
    mBuyShopItems = nullptr;

    delete mSellShopItems;
    mSellShopItems = nullptr;

    instances.remove(this);
}

void ShopWindow::action(const gcn::ActionEvent &event)
{
    const std::string &eventId = event.getId();
    if (eventId == "close")
    {
        close();
        return;
    }
    else if (eventId == "yes")
    {
        startTrade();
    }
    else if (eventId == "no")
    {
        mTradeNick.clear();
    }
    else if (eventId == "ignore")
    {
        player_relations.ignoreTrade(mTradeNick);
        mTradeNick.clear();
    }
    else if (eventId == "delete buy" && mBuyShopItemList
             && mBuyShopItemList->getSelected() >= 0)
    {
        mBuyShopItems->del(mBuyShopItemList->getSelected());
        if (isShopEmpty() && player_node)
            player_node->updateStatus();
    }
    else if (eventId == "delete sell" && mSellShopItemList
             && mSellShopItemList->getSelected() >= 0)
    {
        mSellShopItems->del(mSellShopItemList->getSelected());
        if (isShopEmpty() && player_node)
            player_node->updateStatus();
    }
    else if (eventId == "announce buy" && mBuyShopItems
             && mBuyShopItems->getNumberOfElements() > 0)
    {
        announce(mBuyShopItems, BUY);
    }
    else if (eventId == "announce sell" && mSellShopItems
             && mSellShopItems->getNumberOfElements() > 0)
    {
        announce(mSellShopItems, SELL);
    }
    else if (eventId == "auction buy" && mBuyShopItems
             && mBuyShopItems->getNumberOfElements() > 0)
    {
        Net::getChatHandler()->privateMessage("AuctionBot", "!pull4144 seek");
    }
    else if (eventId == "auction sell" && mSellShopItems
             && mSellShopItems->getNumberOfElements() > 0)
    {
        Net::getChatHandler()->privateMessage("AuctionBot", "!pull4144 offer");
    }

    if (mSelectedItem < 1)
        return;

    const Inventory *const inv = PlayerInfo::getInventory();
    if (!inv)
        return;

    // +++ need support for colors
    Item *const item = inv->findItem(mSelectedItem, 0);
    if (item)
    {
        if (eventId == "add buy")
        {
            ItemAmountWindow::showWindow(ItemAmountWindow::ShopBuyAdd,
                                         this, item, sumAmount(item));
        }
        else if (eventId == "add sell")
        {
            ItemAmountWindow::showWindow(ItemAmountWindow::ShopSellAdd,
                                         this, item, sumAmount(item));
        }
    }
}

void ShopWindow::startTrade()
{
    if (!actorSpriteManager || !tradeWindow)
        return;

    const Being *const being = actorSpriteManager->findBeingByName(
        mTradeNick, Being::PLAYER);
    tradeWindow->clear();
    if (mTradeMoney)
    {
        tradeWindow->addAutoMoney(mTradeNick, mTradeMoney);
    }
    else
    {
        tradeWindow->addAutoItem(mTradeNick, mTradeItem,
                                 mTradeItem->getQuantity());
    }
    Net::getTradeHandler()->request(being);
    tradePartnerName = mTradeNick;
    mTradeNick.clear();
}

void ShopWindow::valueChanged(const gcn::SelectionEvent &event A_UNUSED)
{
    updateButtonsAndLabels();
}

void ShopWindow::updateButtonsAndLabels()
{
    mBuyAddButton->setEnabled(mSelectedItem != -1);
    mSellAddButton->setEnabled(mSelectedItem != -1);
    mBuyDeleteButton->setEnabled(
            mBuyShopItemList->getSelected() != -1
            && mBuyShopItems->getNumberOfElements() > 0);
    mSellDeleteButton->setEnabled(
            mSellShopItemList->getSelected() != -1
            && mSellShopItems->getNumberOfElements() > 0);
}

void ShopWindow::setVisible(bool visible)
{
    Window::setVisible(visible);
}

void ShopWindow::addBuyItem(const Item *const item, const int amount,
                            const int price)
{
    if (!mBuyShopItems || !item)
        return;
    const bool emp = isShopEmpty();
    mBuyShopItems->addItemNoDup(item->getId(),
        item->getColor(), amount, price);
    if (emp && player_node)
        player_node->updateStatus();

    updateButtonsAndLabels();
}

void ShopWindow::addSellItem(const Item *const item, const int amount,
                             const int price)
{
    if (!mBuyShopItems || !item)
        return;
    const bool emp = isShopEmpty();
    mSellShopItems->addItemNoDup(item->getId(),
        item->getColor(), amount, price);
    if (emp && player_node)
        player_node->updateStatus();

    updateButtonsAndLabels();
}

void ShopWindow::loadList()
{
    if (!mBuyShopItems || !mSellShopItems)
        return;

    std::ifstream shopFile;
    struct stat statbuf;

    mBuyShopItems->clear();
    mSellShopItems->clear();

    const std::string shopListName = Client::getServerConfigDirectory()
        + "/shoplist.txt";

    if (!stat(shopListName.c_str(), &statbuf) && S_ISREG(statbuf.st_mode))
    {
        shopFile.open(shopListName.c_str(), std::ios::in);
        if (!shopFile.is_open())
        {
            shopFile.close();
            return;
        }
        char line[101];
        while (shopFile.getline(line, 100))
        {
            std::string buf;
            const std::string str = line;
            if (!str.empty())
            {
                std::vector<int> tokens;
                std::stringstream ss(str);

                while (ss >> buf)
                    tokens.push_back(atoi(buf.c_str()));

                if (tokens.size() == 5 && tokens[0])
                {
                    // +++ need impliment colors?
                    if (tokens[1] && tokens[2] && mBuyShopItems)
                    {
                        mBuyShopItems->addItem(
                            tokens[0], 1, tokens[1], tokens[2]);
                    }
                    if (tokens[3] && tokens[4] && mSellShopItems)
                    {
                        mSellShopItems->addItem(
                            tokens[0], 1, tokens[3], tokens[4]);
                    }
                }
            }
        }
        shopFile.close();
    }
}

void ShopWindow::saveList() const
{
    if (!mBuyShopItems || !mSellShopItems)
        return;

    std::ofstream shopFile;
    const std::string shopListName = Client::getServerConfigDirectory()
        + "/shoplist.txt";
    std::map<int, ShopItem*> mapItems;

    shopFile.open(shopListName.c_str(), std::ios::binary);
    if (!shopFile.is_open())
    {
        logger->log1("Unable to open shoplist.txt for writing");
        return;
    }

    std::vector<ShopItem*> items = mBuyShopItems->items();
    FOR_EACH (std::vector<ShopItem*>::const_iterator, it, items)
    {
        ShopItem *const item = *(it);
        if (item)
            mapItems[item->getId()] = item;
    }

    items = mSellShopItems->items();
    FOR_EACH (std::vector<ShopItem*>::const_iterator, it, items)
    {
        if (!(*it))
            continue;
        const ShopItem *const sellItem = *(it);
        const ShopItem *const buyItem = mapItems[sellItem->getId()];

        shopFile << sellItem->getId();
        if (buyItem)
        {
            shopFile << strprintf(" %d %d ", buyItem->getQuantity(),
                buyItem->getPrice());
            mapItems.erase(sellItem->getId());
        }
        else
        {
            shopFile << " 0 0 ";
        }

        shopFile << strprintf("%d %d", sellItem->getQuantity(),
            sellItem->getPrice()) << std::endl;
    }

    for (std::map<int, ShopItem*>::const_iterator mapIt = mapItems.begin(),
         mapIt_end = mapItems.end(); mapIt != mapIt_end; ++mapIt)
    {
        const ShopItem *const buyItem = (*mapIt).second;
        if (buyItem)
        {
            shopFile << buyItem->getId();
            shopFile << strprintf(" %d %d ", buyItem->getQuantity(),
                                  buyItem->getPrice());
            shopFile << "0 0" << std::endl;
        }
    }

    shopFile.close();
}

void ShopWindow::announce(ShopItems *const list, const int mode)
{
    if (!list)
        return;

    std::string data = "\302\202";
    if (mode == BUY)
        data.append("Buy ");
    else
        data.append("Sell ");

    if (mAnnonceTime && (mAnnonceTime + (2 * 60) > cur_time
        || mAnnonceTime > cur_time))
    {
        return;
    }

    mAnnonceTime = cur_time;
    if (mBuyAnnounceButton)
        mBuyAnnounceButton->setEnabled(false);
    if (mSellAnnounceButton)
        mSellAnnounceButton->setEnabled(false);

    std::vector<ShopItem*> items = list->items();

    FOR_EACH (std::vector<ShopItem*>::const_iterator, it, items)
    {
        const ShopItem *const item = *(it);
        if (item->getQuantity() > 1)
        {
            if (mAnnounceLinks->isSelected())
            {
                data.append(strprintf("[@@%d|%s@@] (%dGP) %d, ", item->getId(),
                    item->getInfo().getName().c_str(),
                    item->getPrice(), item->getQuantity()));
            }
            else
            {
                data.append(strprintf("%s (%dGP) %d, ",
                    item->getInfo().getName().c_str(),
                    item->getPrice(), item->getQuantity()));
            }
        }
        else
        {
            if (mAnnounceLinks->isSelected())
            {
                data.append(strprintf("[@@%d|%s@@] (%dGP), ", item->getId(),
                    item->getInfo().getName().c_str(), item->getPrice()));
            }
            else
            {
                data.append(strprintf("%s (%dGP), ",
                    item->getInfo().getName().c_str(), item->getPrice()));
            }
        }
    }

    Net::getChatHandler()->talk(data, GENERAL_CHANNEL);
}

void ShopWindow::giveList(const std::string &nick, const int mode)
{
    if (!checkFloodCounter(mLastRequestTimeList))
        return;

    std::string data = "\302\202";

    ShopItems *list;
    if (mode == BUY)
    {
        list = mBuyShopItems;
        data.append("S1");
    }
    else
    {
        list = mSellShopItems;
        data.append("B1");
    }
    if (!list)
        return;

    const Inventory *const inv = PlayerInfo::getInventory();
    if (!inv)
        return;

    std::vector<ShopItem*> items = list->items();

    FOR_EACH (std::vector<ShopItem*>::const_iterator, it, items)
    {
        const ShopItem *const item = *(it);
        if (!item)
            continue;

        if (mode == SELL)
        {
            // +++ need support for colors
            const Item *const item2 = inv->findItem(item->getId(), 0);
            if (item2)
            {
                int amount = item->getQuantity();
                if (item2->getQuantity() < amount)
                    amount = item2->getQuantity();

                if (amount)
                {
                    data.append(strprintf("%s%s%s",
                        encodeStr(item->getId(), 2).c_str(),
                        encodeStr(item->getPrice(), 4).c_str(),
                        encodeStr(amount, 3).c_str()));
                }
            }
        }
        else
        {
            int amount = item->getQuantity();
            if (item->getPrice() * amount > PlayerInfo::getAttribute(
                PlayerInfo::MONEY))
            {
                amount = PlayerInfo::getAttribute(PlayerInfo::MONEY)
                    / item->getPrice();
            }

            if (amount > 0)
            {
                data.append(strprintf("%s%s%s",
                    encodeStr(item->getId(), 2).c_str(),
                    encodeStr(item->getPrice(), 4).c_str(),
                    encodeStr(amount, 3).c_str()));
            }
        }
    }
    sendMessage(nick, data, true);
}

void ShopWindow::sendMessage(const std::string &nick,
                             std::string data, const bool random)
{
    if (!chatWindow)
        return;

    if (random)
    {
        mRandCounter ++;
        if (mRandCounter > 200)
            mRandCounter = 0;
        data.append(encodeStr(mRandCounter, 2));
    }

    if (config.getBoolValue("hideShopMessages"))
        Net::getChatHandler()->privateMessage(nick, data);
    else if (chatWindow)
        chatWindow->addWhisper(nick, data, BY_PLAYER);
}

void ShopWindow::showList(const std::string &nick, std::string data) const
{
    BuyDialog *buyDialog = nullptr;
    SellDialog *sellDialog = nullptr;
    if (data.find("B1") == 0)
    {
        data = data.substr(2);
        buyDialog = new BuyDialog(nick);
    }
    else if (data.find("S1") == 0)
    {
        data = data.substr(2);
        sellDialog = new SellDialog(nick);
    }
    else
    {
        return;
    }

    const Inventory *const inv = PlayerInfo::getInventory();
    if (!inv)
        return;

    if (buyDialog)
        buyDialog->setMoney(PlayerInfo::getAttribute(PlayerInfo::MONEY));
    if (sellDialog)
        sellDialog->setMoney(PlayerInfo::getAttribute(PlayerInfo::MONEY));

    for (unsigned f = 0; f < data.length(); f += 9)
    {
        if (f + 9 > data.length())
            break;

        const int id = decodeStr(data.substr(f, 2));
        const int price = decodeStr(data.substr(f + 2, 4));
        int amount = decodeStr(data.substr(f + 6, 3));
        // +++ need impliment colors?
        if (buyDialog && amount > 0)
            buyDialog->addItem(id, 1, amount, price);
        if (sellDialog)
        {
            // +++ need support for colors
            const Item *const item = inv->findItem(id, 0);
            if (item)
            {
                if (item->getQuantity() < amount)
                    amount = item->getQuantity();
                if (amount > 0)
                    sellDialog->addItem(id, 1, amount, price);
                else
                    sellDialog->addItem(id, 1, -1, price);
            }
        }
    }
}

void ShopWindow::processRequest(const std::string &nick, std::string data,
                                const int mode)
{
    if (!player_node || !mTradeNick.empty() || PlayerInfo::isTrading()
        || !actorSpriteManager
        || !actorSpriteManager->findBeingByName(nick, Being::PLAYER))
    {
        return;
    }

    const Inventory *const inv = PlayerInfo::getInventory();
    if (!inv)
        return;

    const size_t idx = data.find(" ");
    if (idx == std::string::npos)
        return;

    if (!checkFloodCounter(mLastRequestTimeItem))
        return;

    if (!mTradeNick.empty())
    {
        sendMessage(nick, "error: player busy ", true);
        return;
    }

    data = data.substr(idx + 1);

    std::string part1;
    std::string part2;
    std::string part3;
    std::stringstream ss(data);
    std::string msg;
    int id;
    int price;
    int amount;

    if (!(ss >> part1))
        return;

    if (!(ss >> part2))
        return;

    if (!(ss >> part3))
        return;

    id = atoi(part1.c_str());
    price = atoi(part2.c_str());
    amount = atoi(part3.c_str());

    delete mTradeItem;
    // +++ need impliment colors?
    mTradeItem = new ShopItem(-1, id, 1, amount, price);

    if (mode == BUY)
    {
        // +++ need support for colors
        const Item *const item2 = inv->findItem(mTradeItem->getId(), 0);
        if (!item2 || item2->getQuantity() < amount
            || !findShopItem(mTradeItem, SELL))
        {
            sendMessage(nick, "error: Can't sell this item ", true);
            return;
        }
        msg = "buy";
        mTradeMoney = 0;
    }
    else
    {
        if (!findShopItem(mTradeItem, BUY))
        {
            sendMessage(nick, "error: Can't buy this item ", true);
            return;
        }
        msg = "sell";
        mTradeMoney = mTradeItem->getPrice() * mTradeItem->getQuantity();
    }

    mTradeNick = nick;

    if (config.getBoolValue("autoShop"))
    {
        soundManager.playGuiSound(SOUND_TRADE);
        startTrade();
    }
    else
    {
        ConfirmDialog *const confirmDlg = new ConfirmDialog
            // TRANSLATORS: shop window dialog
            (_("Request for Trade"), strprintf(_("%s wants to %s %s do you "
            "accept?"), nick.c_str(), msg.c_str(),
            mTradeItem->getInfo().getName().c_str()), SOUND_REQUEST, true);
        confirmDlg->addActionListener(this);
    }
}

void ShopWindow::updateTimes()
{
    BLOCK_START("ShopWindow::updateTimes")
    if (mAnnonceTime + (2 * 60) < cur_time
        || mAnnonceTime > cur_time)
    {
        mBuyAnnounceButton->setEnabled(true);
        mSellAnnounceButton->setEnabled(true);
    }
    BLOCK_END("ShopWindow::updateTimes")
}

bool ShopWindow::checkFloodCounter(int &counterTime)
{
    if (!counterTime || counterTime > cur_time)
        counterTime = cur_time;
    else if (counterTime + 10 > cur_time)
        return false;

    counterTime = cur_time;
    return true;
}

bool ShopWindow::findShopItem(const ShopItem *const shopItem,
                              const int mode) const
{
    if (!shopItem)
        return false;

    std::vector<ShopItem*> items;
    if (mode == SELL)
    {
        if (!mSellShopItems)
            return false;
        items = mSellShopItems->items();
    }
    else
    {
        if (!mBuyShopItems)
            return false;
        items = mBuyShopItems->items();
    }

    FOR_EACH (std::vector<ShopItem*>::const_iterator, it, items)
    {
        const ShopItem *const item = *(it);
        if (!item)
            continue;

        if (item->getId() == shopItem->getId()
            && item->getPrice() == shopItem->getPrice()
            && item->getQuantity() >= shopItem->getQuantity())
        {
            return true;
        }
    }
    return false;
}

int ShopWindow::sumAmount(const Item *const shopItem)
{
    if (!player_node || !shopItem)
        return 0;

    const Inventory *const inv = PlayerInfo::getInventory();
    if (!inv)
        return 0;
    int sum = 0;

    for (unsigned f = 0; f < inv->getSize(); f ++)
    {
        const Item *const item = inv->getItem(f);
        if (item && item->getId() == shopItem->getId())
            sum += item->getQuantity();
    }
    return sum;
}

bool ShopWindow::isShopEmpty() const
{
    if (!mBuyShopItems || !mSellShopItems)
        return true;
    if (mBuyShopItems->empty() && mSellShopItems->empty())
        return true;
    return false;
}