/*
 *  The ManaPlus Client
 *  Copyright (C) 2004-2009  The Mana World Development Team
 *  Copyright (C) 2009-2010  The Mana Developers
 *  Copyright (C) 2011-2012  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/skilldialog.h"

#include "logger.h"
#include "playerinfo.h"
#include "configuration.h"

#include "gui/setup.h"
#include "gui/textpopup.h"
#include "gui/theme.h"
#include "gui/viewport.h"

#include "gui/widgets/button.h"
#include "gui/widgets/container.h"
#include "gui/widgets/label.h"
#include "gui/widgets/layouthelper.h"
#include "gui/widgets/listbox.h"
#include "gui/widgets/progressbar.h"
#include "gui/widgets/scrollarea.h"
#include "gui/widgets/tab.h"
#include "gui/widgets/tabbedarea.h"
#include "gui/widgets/windowcontainer.h"

#include "net/net.h"
#include "net/playerhandler.h"

#include "resources/image.h"
#include "resources/resourcemanager.h"

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

#include <guichan/font.hpp>

#include <set>
#include <string>

#include "debug.h"

class SkillModel;
class SkillEntry;

struct SkillInfo
{
    unsigned short id;
    std::string name;
    std::string dispName;
    Image *icon;
    bool modifiable;
    bool visible;
    SkillModel *model;

    std::string skillLevel;
    int skillLevelWidth;

    std::string skillExp;
    float progress;
    gcn::Color color;

    SkillInfo() :
        id(0), name(""), dispName(""), icon(nullptr), modifiable(false),
        visible(false), model(nullptr), skillLevel(""), skillLevelWidth(0),
        skillExp(""), progress(0.0f)
    {
    }

    ~SkillInfo()
    {
        if (icon)
        {
            icon->decRef();
            icon = nullptr;
        }
    }

    void setIcon(const std::string &iconPath)
    {
        ResourceManager *res = ResourceManager::getInstance();
        if (!iconPath.empty())
        {
            icon = res->getImage(iconPath);
        }

        if (!icon)
        {
            icon = Theme::getImageFromTheme(
                paths.getStringValue("unknownItemFile"));
        }
    }

    void update();

    void draw(Graphics *graphics, int y, int width);
};


typedef std::vector<SkillInfo*> SkillList;

class SkillModel : public gcn::ListModel
{
public:
    int getNumberOfElements()
    { return static_cast<int>(mVisibleSkills.size()); }

    SkillInfo *getSkillAt(int i) const
    { return mVisibleSkills.at(i); }

    std::string getElementAt(int i)
    {
        if (getSkillAt(i))
            return getSkillAt(i)->name;
        else
            return "";
    }

    void updateVisibilities();

    void addSkill(SkillInfo *info)
    { mSkills.push_back(info); }

private:
    SkillList mSkills;
    SkillList mVisibleSkills;
};

class SkillListBox : public ListBox
{
public:
    SkillListBox(SkillModel *model):
        ListBox(model),
        mModel(model),
        mPopup(new TextPopup()),
        mHighlightColor(Theme::getThemeColor(Theme::HIGHLIGHT)),
        mTextColor(Theme::getThemeColor(Theme::TEXT))
    {
    }

    ~SkillListBox()
    {
        delete mModel;
        mModel = nullptr;
        delete mPopup;
        mPopup = nullptr;
    }

    SkillInfo *getSelectedInfo()
    {
        const int selected = getSelected();
        if (!mListModel || selected < 0
            || selected > mListModel->getNumberOfElements())
        {
            return nullptr;
        }

        return static_cast<SkillModel*>(mListModel)->getSkillAt(selected);
    }

    void draw(gcn::Graphics *gcnGraphics)
    {
        if (!mListModel)
            return;

        SkillModel* model = static_cast<SkillModel*>(mListModel);

        updateAlpha();

        Graphics *graphics = static_cast<Graphics*>(gcnGraphics);

        mHighlightColor.a = static_cast<int>(mAlpha * 255.0f);
        graphics->setColor(mHighlightColor);
        graphics->setFont(getFont());

        // Draw filled rectangle around the selected list element
        if (mSelected >= 0)
        {
            graphics->fillRectangle(gcn::Rectangle(0, getRowHeight()
                * mSelected, getWidth(), getRowHeight()));
        }

        // Draw the list elements
        graphics->setColor(mTextColor);
        for (int i = 0, y = 1;
             i < model->getNumberOfElements();
             ++i, y += getRowHeight())
        {
            SkillInfo *e = model->getSkillAt(i);

            if (e)
                e->draw(graphics, y, getWidth());
        }
    }

    unsigned int getRowHeight() const
    { return 34; }

    void mouseMoved(gcn::MouseEvent &event)
    {
        ListBox::mouseMoved(event);
        if (!viewport)
            return;

        int y = event.getY() / getRowHeight();
        if (!mModel || y >= mModel->getNumberOfElements())
            return;
        SkillInfo *skill = mModel->getSkillAt(y);
        if (!skill)
            return;

        mPopup->show(viewport->getMouseX(), viewport->getMouseY(),
            skill->dispName);
    }

    void mouseExited(gcn::MouseEvent &event A_UNUSED)
    {
        mPopup->hide();
    }

private:
    SkillModel *mModel;
    TextPopup *mPopup;
    gcn::Color mHighlightColor;
    gcn::Color mTextColor;
};

class SkillTab : public Tab
{
public:
    SkillTab(const std::string &name, SkillListBox *listBox):
            mListBox(listBox)
    {
        setCaption(name);
    }

    ~SkillTab()
    {
        delete mListBox;
        mListBox = nullptr;
    }

    SkillInfo *getSelectedInfo()
    {
        if (mListBox)
            return mListBox->getSelectedInfo();
        else
            return nullptr;
    }

private:
    SkillListBox *mListBox;
};

SkillDialog::SkillDialog():
    Window(_("Skills"), false, nullptr, "skills.xml")
{
    setWindowName("Skills");
    setCloseButton(true);
    setResizable(true);
    setSaveVisible(true);
    setStickyButtonLock(true);
    setDefaultSize(windowContainer->getWidth() - 280, 30, 275, 425);
    setupWindow->registerWindowForReset(this);

    mTabs = new TabbedArea();
    mPointsLabel = new Label("0");
    mIncreaseButton = new Button(_("Up"), "inc", this);
    mDefaultModel = nullptr;

    place(0, 0, mTabs, 5, 5);
    place(0, 5, mPointsLabel, 4);
    place(4, 5, mIncreaseButton);

    setLocationRelativeTo(getParent());
    loadWindowState();
}

SkillDialog::~SkillDialog()
{
    // Clear gui
    loadSkills("");
}

void SkillDialog::action(const gcn::ActionEvent &event)
{
    if (event.getId() == "inc")
    {
        SkillTab *tab = static_cast<SkillTab*>(mTabs->getSelectedTab());
        if (tab)
        {
            if (SkillInfo *info = tab->getSelectedInfo())
                Net::getPlayerHandler()->increaseSkill(info->id);
        }
    }
    else if (event.getId() == "close")
    {
        setVisible(false);
    }
}

std::string SkillDialog::update(int id)
{
    SkillMap::const_iterator i = mSkills.find(id);

    if (i != mSkills.end())
    {
        SkillInfo *info = i->second;
        if (info)
        {
            info->update();
            return info->name;
        }
    }

    return std::string();
}

void SkillDialog::update()
{
    mPointsLabel->setCaption(strprintf(_("Skill points available: %d"),
                             PlayerInfo::getAttribute(SKILL_POINTS)));
    mPointsLabel->adjustSize();

    for (SkillMap::const_iterator it = mSkills.begin();
         it != mSkills.end();
          ++ it)
    {
        if ((*it).second && (*it).second->modifiable)
            (*it).second->update();
    }
}

void SkillDialog::loadSkills(const std::string &file)
{
    mTabs->removeAll();

    delete_all(mSkills);
    mSkills.clear();

    if (file.length() == 0)
        return;

    XML::Document doc(file);
    XmlNodePtr root = doc.rootNode();

    int setCount = 0;
    std::string setName;
    ScrollArea *scroll;
    SkillListBox *listbox;
    SkillTab *tab;

    if (!root || !xmlNameEqual(root, "skills"))
    {
        logger->log("Error loading skills file: %s", file.c_str());

        if (Net::getNetworkType() != ServerInfo::MANASERV)
        {
            SkillModel *model = new SkillModel();
            if (!mDefaultModel)
                mDefaultModel = model;

            SkillInfo *skill = new SkillInfo;
            skill->id = 1;
            skill->name = _("basic");
            skill->dispName = _("Skill: basic, Id: 1");
            skill->setIcon("");
            skill->modifiable = true;
            skill->visible = true;
            skill->model = model;
            skill->update();

            model->addSkill(skill);
            mSkills[1] = skill;

            model->updateVisibilities();

            listbox = new SkillListBox(model);
            scroll = new ScrollArea(listbox);
            scroll->setOpaque(false);
            scroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
            scroll->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS);

            tab = new SkillTab("Skills", listbox);
            mDeleteTabs.push_back(tab);

            mTabs->addTab(tab, scroll);

            update();
        }
        return;
    }

    for_each_xml_child_node(set, root)
    {
        if (xmlNameEqual(set, "set"))
        {
            setCount++;
            setName = XML::getProperty(set, "name",
                strprintf(_("Skill Set %d"), setCount));

            SkillModel *model = new SkillModel();
            if (!mDefaultModel)
                mDefaultModel = model;

            for_each_xml_child_node(node, set)
            {
                if (xmlNameEqual(node, "skill"))
                {
                    int id = atoi(XML::getProperty(node, "id", "-1").c_str());
                    std::string name = XML::langProperty(node, "name",
                        strprintf(_("Skill %d"), id));
                    std::string icon = XML::getProperty(node, "icon", "");

                    SkillInfo *skill = new SkillInfo;
                    skill->id = static_cast<short unsigned>(id);
                    skill->name = name;
                    skill->dispName = strprintf(_("Skill: %s, Id: %d"),
                        name.c_str(), skill->id);
                    skill->setIcon(icon);
                    skill->modifiable = false;
                    skill->visible = false;
                    skill->model = model;
                    skill->update();

                    model->addSkill(skill);

                    mSkills[id] = skill;
                }
            }

            model->updateVisibilities();

            // possible leak listbox, scroll
            listbox = new SkillListBox(model);
            scroll = new ScrollArea(listbox);
            scroll->setOpaque(false);
            scroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
            scroll->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS);

            tab = new SkillTab(setName, listbox);
            mDeleteTabs.push_back(tab);

            mTabs->addTab(tab, scroll);
        }
    }
    update();
}

bool SkillDialog::setModifiable(int id, bool modifiable)
{
    SkillMap::const_iterator it = mSkills.find(id);

    if (it != mSkills.end())
    {
        SkillInfo *info = it->second;
        if (info)
        {
            info->modifiable = modifiable;
            info->update();
        }
        return true;
    }
    return false;
}

void SkillDialog::addSkill(int id, int level, bool modifiable)
{
    if (mDefaultModel)
    {
        SkillInfo *skill = new SkillInfo;
        skill->id = static_cast<short unsigned>(id);
        skill->name = "Unknown skill Id: " + toString(id);
        skill->dispName = "Unknown skill Id: " + toString(id);
        skill->setIcon("");
        skill->modifiable = modifiable;
        skill->visible = false;
        skill->model = mDefaultModel;
        skill->skillLevel = strprintf(_("Lvl: %d"), level);
        skill->update();

        mDefaultModel->addSkill(skill);

        mSkills[id] = skill;
        mDefaultModel->updateVisibilities();
    }
}

void SkillModel::updateVisibilities()
{
    mVisibleSkills.clear();

    for (SkillList::const_iterator it = mSkills.begin();
         it != mSkills.end();
         ++ it)
    {
        if ((*it)->visible)
            mVisibleSkills.push_back((*it));
    }
}

void SkillInfo::update()
{
    int baseLevel = PlayerInfo::getStatBase(id);
    int effLevel = PlayerInfo::getStatEffective(id);

    std::pair<int, int> exp = PlayerInfo::getStatExperience(id);

    if (!modifiable && baseLevel == 0 && effLevel == 0 && exp.second == 0)
    {
        if (visible)
        {
            visible = false;
            if (model)
                model->updateVisibilities();
        }

        return;
    }

    bool updateVisibility = !visible;
    visible = true;

    if (effLevel != baseLevel)
    {
        skillLevel = strprintf(_("Lvl: %d (%+d)"), baseLevel,
                               effLevel - baseLevel);
    }
    else
    {
        if (baseLevel == 0)
            skillLevel.clear();
        else
            skillLevel = strprintf(_("Lvl: %d"), baseLevel);
    }
    skillLevelWidth = -1;

    if (exp.second)
    {
        skillExp = strprintf("%d / %d", exp.first, exp.second);
        progress = static_cast<float>(exp.first)
                   / static_cast<float>(exp.second);
    }
    else
    {
        skillExp.clear();
        progress = 0.0f;
    }

    color = Theme::getProgressColor(Theme::PROG_EXP, progress);

    if (updateVisibility && model)
        model->updateVisibilities();
}

void SkillInfo::draw(Graphics *graphics, int y, int width)
{
    graphics->drawImage(icon, 1, y);
    graphics->drawText(name, 34, y);

    if (skillLevelWidth < 0)
    {
        // Add one for padding
        skillLevelWidth = graphics->getFont()->getWidth(skillLevel) + 1;
    }

    graphics->drawText(skillLevel, width - skillLevelWidth, y);

    if (!skillExp.empty())
    {
        gcn::Rectangle rect(33, y + 15, width - 33, 17);

        ProgressBar::render(graphics, rect, color, progress, skillExp);
    }
}

SkillInfo* SkillDialog::getSkill(int id)
{
    return mSkills[id];
}

void SkillDialog::widgetResized(const gcn::Event &event)
{
    Window::widgetResized(event);

    if (mTabs)
        mTabs->fixSize();
}