/*
 *  The Mana Client
 *  Copyright (C) 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/socialwindow.h"

#include "beingmanager.h"
#include "guild.h"
#include "localplayer.h"
#include "log.h"
#include "map.h"
#include "party.h"
#include "player.h"

#include "gui/confirmdialog.h"
#include "gui/okdialog.h"
#include "gui/setup.h"
#include "gui/textdialog.h"
#include "gui/theme.h"

#include "gui/widgets/avatarlistbox.h"
#include "gui/widgets/browserbox.h"
#include "gui/widgets/button.h"
#include "gui/widgets/chattab.h"
#include "gui/widgets/container.h"
#include "gui/widgets/label.h"
#include "gui/widgets/layouthelper.h"
#include "gui/widgets/linkhandler.h"
#include "gui/widgets/popup.h"
#include "gui/widgets/scrollarea.h"
#include "gui/widgets/tab.h"
#include "gui/widgets/tabbedarea.h"

#include "net/net.h"
#include "net/guildhandler.h"
#include "net/partyhandler.h"

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

class SocialTab : public Tab
{
protected:
    friend class SocialWindow;

    SocialTab():
            mInviteDialog(NULL),
            mConfirmDialog(NULL)
    {}

    ~SocialTab()
    {
        // Cleanup dialogs
        if (mInviteDialog)
        {
            mInviteDialog->close();
            mInviteDialog->scheduleDelete();
            mInviteDialog = NULL;
        }

        if (mConfirmDialog)
        {
            mConfirmDialog->close();
            mConfirmDialog->scheduleDelete();
            mConfirmDialog = NULL;
        }
    }

    virtual void invite() = 0;

    virtual void leave() = 0;

    virtual void updateList() = 0;

    virtual void updateAvatar(std::string name) = 0;

    virtual void resetDamage(std::string name) = 0;

    TextDialog *mInviteDialog;
    ConfirmDialog *mConfirmDialog;
    ScrollArea *mScroll;
    AvatarListBox *mList;
};

class GuildTab : public SocialTab, public gcn::ActionListener
{
public:
    GuildTab(Guild *guild):
            mGuild(guild)
    {
        setCaption(guild->getName());

        setTabColor(&Theme::getThemeColor(Theme::GUILD));

        mList = new AvatarListBox(guild);
        mScroll = new ScrollArea(mList);

        mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO);
        mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);
    }

    void action(const gcn::ActionEvent &event)
    {
        if (event.getId() == "do invite")
        {
            std::string name = mInviteDialog->getText();
            Net::getGuildHandler()->invite(mGuild->getId(), name);

            localChatTab->chatLog(strprintf(_("Invited user %s to guild %s."),
                                            name.c_str(),
                                            mGuild->getName().c_str()),
                                  ChatTab::BY_SERVER);
            mInviteDialog = NULL;
        }
        else if (event.getId() == "~do invite")
        {
            mInviteDialog = NULL;
        }
        else if (event.getId() == "yes")
        {
            Net::getGuildHandler()->leave(mGuild->getId());
            localChatTab->chatLog(strprintf(_("Guild %s quit requested."),
                                        mGuild->getName().c_str()), ChatTab::BY_SERVER);
            mConfirmDialog = NULL;
        }
        else if (event.getId() == "~yes")
        {
            mConfirmDialog = NULL;
        }
    }

    void updateList()
    {
    }

    void updateAvatar(std::string name _UNUSED_)
    {
    }

    void resetDamage(std::string name _UNUSED_)
    {
    }

protected:
    void invite()
    {
        // TODO - Give feedback on whether the invite succeeded
        mInviteDialog = new TextDialog(_("Member Invite to Guild"),
                     strprintf(_("Who would you like to invite to guild %s?"),
                               mGuild->getName().c_str()),
                     socialWindow);
        mInviteDialog->setActionEventId("do invite");
        mInviteDialog->addActionListener(this);
    }

    void leave()
    {
        mConfirmDialog = new ConfirmDialog(_("Leave Guild?"),
                       strprintf(_("Are you sure you want to leave guild %s?"),
                                 mGuild->getName().c_str()),
                       socialWindow);

        mConfirmDialog->addActionListener(this);
    }

private:
    Guild *mGuild;
};

class PartyTab : public SocialTab, public gcn::ActionListener
{
public:
    PartyTab(Party *party):
            mParty(party)
    {
        setCaption(party->getName());

        setTabColor(&Theme::getThemeColor(Theme::PARTY));

        mList = new AvatarListBox(party);
        mScroll = new ScrollArea(mList);

        mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO);
        mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);
    }

    void action(const gcn::ActionEvent &event)
    {
        if (event.getId() == "do invite")
        {
            std::string name = mInviteDialog->getText();
            Net::getPartyHandler()->invite(name);

            localChatTab->chatLog(strprintf(_("Invited user %s to party."),
                                            name.c_str()), ChatTab::BY_SERVER);
            mInviteDialog = NULL;
        }
        else if (event.getId() == "~do invite")
        {
            mInviteDialog = NULL;
        }
        else if (event.getId() == "yes")
        {
            Net::getPartyHandler()->leave();
            localChatTab->chatLog(strprintf(_("Party %s quit requested."),
                                        mParty->getName().c_str()), ChatTab::BY_SERVER);
            mConfirmDialog = NULL;
        }
        else if (event.getId() == "~yes")
        {
            mConfirmDialog = NULL;
        }
    }

    void updateList()
    {
    }

    void updateAvatar(std::string name _UNUSED_)
    {
    }

    void resetDamage(std::string name _UNUSED_)
    {
    }

protected:
    void invite()
    {
        // TODO - Give feedback on whether the invite succeeded
        mInviteDialog = new TextDialog(_("Member Invite to Party"),
                      strprintf(_("Who would you like to invite to party %s?"),
                                mParty->getName().c_str()),
                      socialWindow);
        mInviteDialog->setActionEventId("do invite");
        mInviteDialog->addActionListener(this);
    }

    void leave()
    {
        mConfirmDialog = new ConfirmDialog(_("Leave Party?"),
                       strprintf(_("Are you sure you want to leave party %s?"),
                                 mParty->getName().c_str()),
                       socialWindow);

        mConfirmDialog->addActionListener(this);
    }

private:
    Party *mParty;
};

/*class BuddyTab : public SocialTab
{
    // TODO?
};*/


class BeingsListModal : public AvatarListModel
{
public:
    BeingsListModal()
    {
    }

    std::vector<Avatar*> *getMembers()
    {
        return &mMembers;
    }

    virtual Avatar *getAvatarAt(int index)
    {
        return mMembers[index];
    }

    int getNumberOfElements()
    {
        return mMembers.size();
    }

    std::vector<Avatar*> mMembers;
};

class PlayersTab : public SocialTab
{
public:
    PlayersTab(std::string name)
    {
        mBeings = new BeingsListModal();

        mList = new AvatarListBox(mBeings);
        mScroll = new ScrollArea(mList);

        mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO);
        mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);

//        mBeings->getMembers().push_back(new Avatar("test"));
        updateList();
        setCaption(name);
    }

    void updateList()
    {
        getPlayersAvatars();
    }

    void updateAvatar(std::string name)
    {
        Avatar *avatar = findAvatarbyName(name);
        if (!avatar)
            return;
        if (Party::getParty(1))
        {
            PartyMember *pm = Party::getParty(1)->getMember(name);
            if (pm && pm->getMaxHp() > 0)
            {
                avatar->setMaxHp(pm->getMaxHp());
                avatar->setHp(pm->getHp());
            }
        }
        Being* being = beingManager->findBeingByName(name, Being::PLAYER);
        if (being)
        {
            avatar->setDamageHp(being->getDamageTaken());
            avatar->setLevel(being->getLevel());
        }
    }

    void resetDamage(std::string name)
    {
        Avatar *avatar = findAvatarbyName(name);
        if (!avatar)
            return;
        avatar->setDamageHp(0);
        Being* being = beingManager->findBeingByName(name, Being::PLAYER);
        if (being)
            being->setDamageTaken(0);
    }

    Avatar* findAvatarbyName(std::string name)
    {
        std::vector<Avatar*> *avatars = mBeings->getMembers();
        if (!avatars)
            return 0;

        Avatar *ava = 0;
        std::vector<Avatar*>::iterator i = avatars->begin();
        while (i != avatars->end())
        {
            ava = (*i);
            if (ava->getName() == name)
                return ava;
            ++i;
        }
        ava = new Avatar(name);
        ava->setOnline(true);
        avatars->push_back(ava);
        return ava;
    }

    void getPlayersAvatars()
    {
        std::vector<Avatar*> *avatars = mBeings->getMembers();
        if (!avatars)
            return;

        if (beingManager)
        {
//            std::list<Being*> beings = beingManager->getAll();
            std::vector<std::string> names;
            beingManager->getPlayerNames(names, false);

            std::vector<Avatar*>::iterator ai = avatars->begin();
            while (ai != avatars->end())
            {
                bool finded = false;
                Avatar *ava = (*ai);

                std::vector<std::string>::iterator i = names.begin();
                while (i != names.end())
                {
                    if (ava->getName() == (*i) && (*i) != "")
                    {
                        finded = true;
                        break;
                    }
                    ++i;
                }

                if (!finded)
                    avatars->erase(ai);
                else
                    ++ai;
            }

            std::vector<std::string>::iterator i = names.begin();

            while (i != names.end())
            {
                if ((*i) != "")
                    updateAvatar(*i);
                ++i;
            }
        }
    }

protected:
    void invite()
    {
    }

    void leave()
    {
    }

private:
    BeingsListModal *mBeings;
};



/*
class NavigationTab : public SocialTab
{

public:
    NavigationTab()
    {
        mBeings = new BeingsListModal();

        mList = new AvatarListBox(mBeings);
        mScroll = new ScrollArea(mList);

        mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO);
        mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS);

        setCaption(_("Nav"));

    }

    void invite()
    {
    }

    void leave()
    {
    }

    void updateList()
    {
        if (!socialWindow || !player_node || socialWindow->getProcessedPortals())
            return;

        Map* map = socialWindow->getMap();
        if (!map)
            return;

        std::vector<Avatar*> *avatars = mBeings->getMembers();
        std::list<MapBox> portals = map->getPortals();

        std::list<MapBox>::iterator i = portals.begin();
        avatars->clear();
//        logger->log("try find");
        while (i != portals.end())
        {
            MapBox portal = *i;
            int x = portal.x + (int)(portal.dx / 2);
            int y = portal.y + (int)(portal.dy / 2);

//            logger->log(strprintf("test: %d,%d", x, y));

            if (player_node->isReachable(x, y, 0))
            {
                std::string name = strprintf("%d,%d", x, y);

                Avatar *ava = new Avatar(name);
                ava->setOnline(true);
                ava->setLevel(-1);
                avatars->push_back(ava);
            }

            i++;
        }
//        logger->log("end find");
        socialWindow->setProcessedPortals(true);
//        player_node->navigateTo(65, 73);
    }

    void updateAvatar(std::string)
    {
    }

    void resetDamage(std::string)
    {
    }

private:
    BeingsListModal *mBeings;

protected:
//    friend class SocialWindow;
};
*/

class CreatePopup : public Popup, public LinkHandler
{
public:
    CreatePopup():
            Popup("SocialCreatePopup")
    {
        mBrowserBox = new BrowserBox;
        mBrowserBox->setPosition(4, 4);
        mBrowserBox->setHighlightMode(BrowserBox::BACKGROUND);
        mBrowserBox->setOpaque(false);
        mBrowserBox->setLinkHandler(this);

        if (Net::getGuildHandler()->isSupported())
            mBrowserBox->addRow(strprintf("@@guild|%s@@", _("Create Guild")));
        mBrowserBox->addRow(strprintf("@@party|%s@@", _("Create Party")));
        mBrowserBox->addRow("##3---");
        mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel")));

        add(mBrowserBox);

        setContentSize(mBrowserBox->getWidth() + 8,
                       mBrowserBox->getHeight() + 8);
    }

    void handleLink(const std::string &link)
    {
        if (link == "guild" && socialWindow)
        {
            socialWindow->showGuildCreate();
        }
        else if (link == "party" && socialWindow)
        {
            socialWindow->showPartyCreate();
        }

        setVisible(false);
    }

    void show(gcn::Widget *parent)
    {
        int x, y;
        parent->getAbsolutePosition(x, y);
        y += parent->getHeight();
        setPosition(x, y);
        setVisible(true);
        requestMoveToTop();
    }

private:
    BrowserBox* mBrowserBox;
};

SocialWindow::SocialWindow() :
    Window(_("Social")),
    mGuildAcceptDialog(0),
    mPartyAcceptDialog(0),
    mMap(0),
    mLastUpdateTime(0),
    mNeedUpdate(false)
{
    setWindowName("Social");
    setVisible(false);
    setSaveVisible(true);
    setResizable(true);
    setSaveVisible(true);
    setCloseButton(true);
    setMinWidth(120);
    setMinHeight(55);
    setDefaultSize(590, 200, 150, 60);
    setupWindow->registerWindowForReset(this);


    mCreateButton = new Button(_("Create"), "create", this);
    mInviteButton = new Button(_("Invite"), "invite", this);
    mLeaveButton = new Button(_("Leave"), "leave", this);
    mTabs = new TabbedArea;

    place(0, 0, mCreateButton);
    place(1, 0, mInviteButton);
    place(2, 0, mLeaveButton);
    place(0, 1, mTabs, 4, 4);

    widgetResized(NULL);

    mCreatePopup = new CreatePopup();

    mPlayers = new PlayersTab("P");
    mTabs->addTab(mPlayers, mPlayers->mScroll);

//    mNavigation = new NavigationTab();
//    mTabs->addTab(mNavigation, mNavigation->mScroll);

    loadWindowState();

    if (player_node && player_node->getParty())
    {
        addTab(player_node->getParty());
    }
    else
        updateButtons();
}

SocialWindow::~SocialWindow()
{
    // Cleanup invites
    if (mGuildAcceptDialog)
    {
        mGuildAcceptDialog->close();
        mGuildAcceptDialog->scheduleDelete();
        mGuildAcceptDialog = NULL;

        mGuildInvited = 0;
    }

    if (mPartyAcceptDialog)
    {
        mPartyAcceptDialog->close();
        mPartyAcceptDialog->scheduleDelete();
        mPartyAcceptDialog = NULL;

        mPartyInviter = "";
    }
}

bool SocialWindow::addTab(Guild *guild)
{
    if (mGuilds.find(guild) != mGuilds.end())
        return false;

    GuildTab *tab = new GuildTab(guild);
    mGuilds[guild] = tab;

    mTabs->addTab(tab, tab->mScroll);

    updateButtons();

    return true;
}

bool SocialWindow::removeTab(Guild *guild)
{
    GuildMap::iterator it = mGuilds.find(guild);
    if (it == mGuilds.end())
        return false;

    mTabs->removeTab(it->second);
    delete it->second;
    mGuilds.erase(it);

    updateButtons();

    return true;
}

bool SocialWindow::addTab(Party *party)
{
    if (mParties.find(party) != mParties.end())
        return false;

    PartyTab *tab = new PartyTab(party);
    mParties[party] = tab;

    mTabs->addTab(tab, tab->mScroll);

    updateButtons();

    return true;
}

bool SocialWindow::removeTab(Party *party)
{
    PartyMap::iterator it = mParties.find(party);
    if (it == mParties.end())
        return false;

    mTabs->removeTab(it->second);
    delete it->second;
    mParties.erase(it);

    updateButtons();

    return true;
}

void SocialWindow::action(const gcn::ActionEvent &event)
{
    const std::string &eventId = event.getId();

    if (event.getSource() == mPartyAcceptDialog)
    {
        // check if they accepted the invite
        if (eventId == "yes")
        {
            localChatTab->chatLog(strprintf(_("Accepted party invite from %s."),
                    mPartyInviter.c_str()));
            Net::getPartyHandler()->inviteResponse(mPartyInviter, true);
        }
        else if (eventId == "no")
        {
            localChatTab->chatLog(strprintf(_("Rejected party invite from %s."),
                    mPartyInviter.c_str()));
            Net::getPartyHandler()->inviteResponse(mPartyInviter, false);
        }

        mPartyInviter = "";
        mPartyAcceptDialog = NULL;
    }
    else if (event.getSource() == mGuildAcceptDialog)
    {
        // check if they accepted the invite
        if (eventId == "yes")
        {
            localChatTab->chatLog(strprintf(_("Accepted guild invite from %s."),
                    mPartyInviter.c_str()));
            Net::getGuildHandler()->inviteResponse(mGuildInvited, true);
        }
        else if (eventId == "no")
        {
            localChatTab->chatLog(strprintf(_("Rejected guild invite from %s."),
                    mPartyInviter.c_str()));
            Net::getGuildHandler()->inviteResponse(mGuildInvited, false);
        }

        mGuildInvited = 0;
        mGuildAcceptDialog = NULL;
    }
    else if (event.getId() == "create")
    {
        if (Net::getGuildHandler()->isSupported())
            mCreatePopup->show(mCreateButton);
        else
            showPartyCreate();
    }
    else if (event.getId() == "invite" && mTabs->getSelectedTabIndex() > -1)
    {
        static_cast<SocialTab*>(mTabs->getSelectedTab())->invite();
    }
    else if (event.getId() == "leave" && mTabs->getSelectedTabIndex() > -1)
    {
        static_cast<SocialTab*>(mTabs->getSelectedTab())->leave();
    }
    else if (event.getId() == "create guild")
    {
        std::string name = mGuildCreateDialog->getText();

        if (name.size() > 16)
        {
            // TODO : State too many characters in input.
            return;
        }

        Net::getGuildHandler()->create(name);
        localChatTab->chatLog(strprintf(_("Creating guild called %s."),
                                            name.c_str()), ChatTab::BY_SERVER);

        mGuildCreateDialog = NULL;
    }
    else if (event.getId() == "~create guild")
    {
        mGuildCreateDialog = NULL;
    }
    else if (event.getId() == "create party")
    {
        std::string name = mPartyCreateDialog->getText();

        if (name.size() > 16)
        {
            // TODO : State too many characters in input.
            return;
        }

        Net::getPartyHandler()->create(name);
        localChatTab->chatLog(strprintf(_("Creating party called %s."),
                                            name.c_str()), ChatTab::BY_SERVER);

        mPartyCreateDialog = NULL;
    }
    else if (event.getId() == "~create party")
    {
        mPartyCreateDialog = NULL;
    }
}

void SocialWindow::showGuildCreate()
{
    mGuildCreateDialog = new TextDialog(_("Guild Name"),
                                        _("Choose your guild's name."), this);
    mGuildCreateDialog->setActionEventId("create guild");
    mGuildCreateDialog->addActionListener(this);
}

void SocialWindow::showGuildInvite(const std::string &guildName,
                                   const int guildId,
                                   const std::string &inviterName)
{
    // check there isnt already an invite showing
    if (mGuildInvited != 0)
    {
        localChatTab->chatLog(_("Received guild request, but one already "
                "exists."), ChatTab::BY_SERVER);
        return;
    }

    std::string msg = strprintf(_("%s has invited you to join the guild %s."),
                                inviterName.c_str(), guildName.c_str());
    localChatTab->chatLog(msg, ChatTab::BY_SERVER);

    // show invite
    mGuildAcceptDialog = new ConfirmDialog(_("Accept Guild Invite"), msg, this);
    mGuildAcceptDialog->addActionListener(this);

    mGuildInvited = guildId;
}

void SocialWindow::showPartyInvite(const std::string &partyName,
                                   const std::string &inviter)
{
    // check there isnt already an invite showing
    if (mPartyInviter != "")
    {
        localChatTab->chatLog(_("Received party request, but one already "
                "exists."), ChatTab::BY_SERVER);
        return;
    }

    std::string msg;
    if (inviter.empty())
    {
        if (partyName.empty())
        {
            msg = _("You have been invited you to join a party.");
        }
        else
        {
            msg = strprintf(_("You have been invited to join the %s party."),
                                        partyName.c_str());
        }
    }
    else
    {
        if (partyName.empty())
        {
            msg = strprintf(_("%s has invited you to join their party."),
                                        inviter.c_str());
        }
        else
        {
            msg = strprintf(_("%s has invited you to join the %s party."),
                                        inviter.c_str(), partyName.c_str());
        }
    }

    localChatTab->chatLog(msg, ChatTab::BY_SERVER);

    // show invite
    mPartyAcceptDialog = new ConfirmDialog(_("Accept Party Invite"), msg, this);
    mPartyAcceptDialog->addActionListener(this);

    mPartyInviter = inviter;
}

void SocialWindow::showPartyCreate()
{
    if (player_node->getParty())
    {
        new OkDialog(_("Create Party"),
                     _("Cannot create party. You are already in a party"),
                     this);
        return;
    }

    mPartyCreateDialog = new TextDialog(_("Party Name"),
                                        _("Choose your party's name."), this);
    mPartyCreateDialog->setActionEventId("create party");
    mPartyCreateDialog->addActionListener(this);
}

void SocialWindow::updateActiveList()
{
    mNeedUpdate = true;
}

void SocialWindow::logic()
{
    unsigned int nowTime = cur_time;
    if (nowTime - mLastUpdateTime > 1 && mNeedUpdate)
    {
        mPlayers->updateList();
        mNeedUpdate = false;
        mLastUpdateTime = nowTime;
    }
    else if (nowTime - mLastUpdateTime > 5)
    {
        mPlayers->updateList();
        mNeedUpdate = false;
        mLastUpdateTime = nowTime;
    }

    Window::logic();
}

void SocialWindow::updateAvatar(std::string name)
{
    mPlayers->updateAvatar(name);
}

void SocialWindow::resetDamage(std::string name)
{
    mPlayers->resetDamage(name);
}

void SocialWindow::updateButtons()
{
    bool hasTabs = mTabs->getNumberOfTabs() > 0;
    mInviteButton->setEnabled(hasTabs);
    mLeaveButton->setEnabled(hasTabs);
}

void SocialWindow::updatePortals()
{
//    if (mNavigation)
//        mNavigation->updateList();
}