/*
 * pawsskillwindow.cpp 
 *
 * Copyright (C) 2003 Atomic Blue (info@planeshift.it, http://www.atomicblue.org)
 *
 *
 * 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 (version 2 of the License)
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

#include <psconfig.h>
#include <imesh/spritecal3d.h>

#include "globals.h"
#include "pscelclient.h"
#include "clientvitals.h"
#include "net/messages.h"
#include "net/msghandler.h"
#include "util/skillcache.h"
#include "util/strutil.h"
#include "util/psxmlparser.h"

// PAWS INCLUDES
#include "pawsskillwindow.h"
#include "pawschardescription.h"
#include "paws/pawstextbox.h"
#include "paws/pawslistbox.h"
#include "paws/pawsmanager.h"
#include "paws/pawsobjectview.h"
#include "paws/pawsprogressbar.h"
#include "gui/pawscontrolwindow.h"


#define BTN_BUY     100
#define BTN_FILTER  101
//#define BTN_QUIT    102
#define BTN_EDIT    102 //Edit description
#define BTN_STATS    1000 //Stats button for the tab panel
#define BTN_COMBAT   1001 //Combat button for the tab panel
#define BTN_MAGIC    1002 //Magic button for the tab panel
#define BTN_JOBS     1003 //Jobs button for the tab panel
#define BTN_VARIOUS  1004 //Various button for the tab panel
#define MAX_CAT         4

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

pawsSkillWindow::pawsSkillWindow()
{
    selectedSkill.Clear();
    skillString.Clear();
    skillDescriptions.DeleteAll();
    filter = false;

    hitpointsMax = 0;
    manaMax = 0;
    physStaminaMax = 0;
    menStaminaMax = 0;
}

pawsSkillWindow::~pawsSkillWindow()
{
    // Delete cached skill description strings
    csHash<psSkillDescription *>::GlobalIterator i = skillDescriptions.GetIterator();
    while (i.HasNext())
    {
        delete i.Next();
    }
    skillDescriptions.DeleteAll();
}

bool pawsSkillWindow::PostSetup()
{
    msgHandler = psengine->GetMsgHandler();
    if ( !msgHandler )
        return false;

    // Setup the Doll
    if ( !SetupDoll() )
        return false;

    msgHandler = psengine->GetMsgHandler();
    if ( !msgHandler->Subscribe( this, MSGTYPE_GUISKILL ) )
        return false;

    xml = CS_QUERY_REGISTRY( PawsManager::GetSingleton().GetObjectRegistry(), iDocumentSystem );

    statsSkillList        = (pawsListBox*)FindWidget("StatsSkillList");
    statsSkillDescription = (pawsMultiLineTextBox*)FindWidget("StatsDescription");

    combatSkillList        = (pawsListBox*)FindWidget("CombatSkillList");
    combatSkillDescription = (pawsMultiLineTextBox*)FindWidget("CombatDescription");

    magicSkillList        = (pawsListBox*)FindWidget("MagicSkillList");
    magicSkillDescription = (pawsMultiLineTextBox*)FindWidget("MagicDescription");

    jobsSkillList        = (pawsListBox*)FindWidget("JobsSkillList");
    jobsSkillDescription = (pawsMultiLineTextBox*)FindWidget("JobsDescription");

    variousSkillList        = (pawsListBox*)FindWidget("VariousSkillList");
    variousSkillDescription = (pawsMultiLineTextBox*)FindWidget("VariousDescription");
    
    hpBar = dynamic_cast <pawsProgressBar*> (FindWidget("HPBar"));
    manaBar = dynamic_cast <pawsProgressBar*> (FindWidget("ManaBar"));
    pysStaminaBar = dynamic_cast <pawsProgressBar*> (FindWidget("PysStaminaBar"));
    menStaminaBar = dynamic_cast <pawsProgressBar*> (FindWidget("MenStaminaBar"));
    experienceBar = dynamic_cast <pawsProgressBar*> (FindWidget("ExperienceBar"));

    // Shaded text displaying the players current Duel Points
    pawsTextBox* dpLabel = dynamic_cast <pawsTextBox*> (FindWidget("DPLabel"));
    dpTotal = dynamic_cast <pawsTextBox*> (FindWidget("DPTotal"));
        
    hpFrac = dynamic_cast <pawsTextBox*> (FindWidget("HPFrac"));
    manaFrac = dynamic_cast <pawsTextBox*> (FindWidget("ManaFrac"));
    pysStaminaFrac = dynamic_cast <pawsTextBox*> (FindWidget("PysStaminaFrac"));
    menStaminaFrac = dynamic_cast <pawsTextBox*> (FindWidget("MenStaminaFrac"));
    experiencePerc = dynamic_cast <pawsTextBox*> (FindWidget("ExperiencePerc"));

    if ( !statsSkillList || !statsSkillDescription || !combatSkillList
         || !combatSkillDescription || !magicSkillList || !magicSkillDescription
         || !jobsSkillList || !jobsSkillDescription || !variousSkillList
         || !variousSkillDescription || !hpBar || !manaBar || !pysStaminaBar || !menStaminaBar || !experienceBar
         || !hpFrac || !manaFrac || !pysStaminaFrac || !menStaminaFrac || !experiencePerc
         || !dpLabel || !dpTotal ) { return false; }

    hpBar->SetTotalValue(1);
    manaBar->SetTotalValue(1);
    pysStaminaBar->SetTotalValue(1);
    menStaminaBar->SetTotalValue(1);
    experienceBar->SetTotalValue(1);
    
    currentTab =0;
    previousTab =0;

    return true;
}

bool pawsSkillWindow::SetupDoll()
{
    pawsObjectView* widget = dynamic_cast<pawsObjectView*>(FindWidget("Doll"));
    GEMClientActor* actor = psengine->GetCelClient()->GetMainPlayer();
    if (!widget || !actor)
        return false;

    csRef<iPcMesh> mesh = actor->pcmesh;
    if (!mesh) return false;

    // Set the doll view
    widget->View( mesh->GetMesh() );
    
    // Register this doll for updates
    widget->SetID( actor->GetEntity()->GetID() );

    csRef<iSpriteCal3DState> spstate = SCF_QUERY_INTERFACE(widget->GetObject()->GetMeshObject(),iSpriteCal3DState);
    if (spstate)
    {
        // Setup cal3d to select random 0 velocity anims
        spstate->SetVelocity(0.0,&psengine->GetRandomGen());
    }

    // Build doll appearance and equipment
    bool a = psengine->BuildAppearance( widget->GetObject(), actor->traits );
    
    csPDelArray<Trait> dummy;
    bool e = psengine->BuildEquipment( widget->GetObject(), actor->equipment, dummy );
    
    widget->EnableMouseControl(true);

    return (a && e);
}

void pawsSkillWindow::HandleMessage( MsgEntry* me )
{
    psGUISkillMessage incoming(me);
       
    switch (incoming.command)
    {
        case psGUISkillMessage::SKILL_LIST:
        {
            skillString = "no";
            if ( !IsVisible() && incoming.openWindow == true ) 
                Show();
            skillString = incoming.commandData;
            skillCache.apply(&incoming.skillCache);

            train=incoming.trainingWindow;

            int selectedRowIdx = -1;
            HandleSkillList(&skillCache, incoming.focusSkill, &selectedRowIdx);

            SelectSkill(selectedRowIdx, incoming.skillCat);
            
            dpTotal->FormatText( "%1.2f", incoming.dpTotal );

            hitpointsMax = incoming.hitpointsMax;
            manaMax = incoming.manaMax;
            physStaminaMax = incoming.physStaminaMax;
            menStaminaMax = incoming.menStaminaMax;
            
            break;
        }
        
        case psGUISkillMessage::DESCRIPTION:
        {
            HandleSkillDescription(incoming.commandData);
            break;
        }
    }
}

void pawsSkillWindow::Close()
{
    Hide();

    combatSkillList->Clear();
    statsSkillList->Clear();
    magicSkillList->Clear();
    jobsSkillList->Clear();
    variousSkillList->Clear();
    skillString.Clear();
    selectedSkill.Clear();
}

bool pawsSkillWindow::OnButtonPressed( int mouseButton, int keyModifier, pawsWidget* widget )
{       
    switch ( widget->GetID() )
    {
        case BTN_FILTER:
        {
            filter = !filter;
            HandleSkillList(&skillCache);
            return true;
        }
        
        case BTN_BUY:
        {
            BuySkill();
            return true;
        }

        case BTN_EDIT:
        {
            pawsCharDescription* descWnd = (pawsCharDescription*)PawsManager::GetSingleton().FindWidget("DescriptionEdit");
            descWnd->PostSetup();
            descWnd->Show();
            return true;
        }
        case BTN_STATS:
        case BTN_COMBAT:
        case BTN_MAGIC:
        case BTN_JOBS:
        case BTN_VARIOUS:
        {   
            previousTab = currentTab; //Keep the selection if we are hitting the same tab where we are.
            currentTab = widget->GetID() - 1000;
            if (previousTab != currentTab)
                selectedSkill.Clear();
            break;
        }

    }
    return false;
}

void pawsSkillWindow::SelectSkill(int skill, int cat)
{
    if(skill < 0 || skill > (int)unsortedSkills.Length()|| cat< 0 || cat > MAX_CAT)
        return;
 
    //Let's see which category the skill belongs to
    switch(cat)
    {
    case 0: //Stats
        {
            statsSkillList->Select(unsortedSkills[skill]);
            break;
        }
    case 1: //Combat skills
        {
            combatSkillList->Select(unsortedSkills[skill]);
            break;
        }
    case 2: //Magic Skills
        {
            magicSkillList->Select(unsortedSkills[skill]);
            break;
        }
    case 3://Jobs Skills
        { 
            jobsSkillList->Select(unsortedSkills[skill]);
            break;
        }
    case 4://Various Skills
        {
            variousSkillList->Select(unsortedSkills[skill]);
            break;
        }
    }
}

void pawsSkillWindow::HandleSkillList(psSkillCache *skills, int selectedNameId, int *rowIdx)
{
    combatSkillList->Clear();//Clear everything each time we filter out.
    statsSkillList->Clear();
    magicSkillList->Clear();
    jobsSkillList->Clear();
    variousSkillList->Clear();
    selectedSkill.Clear();

    // Clear descriptions
    statsSkillDescription->SetText("");
    combatSkillDescription->SetText("");
    magicSkillDescription->SetText("");
    jobsSkillDescription->SetText("");
    variousSkillDescription->SetText("");

    unsortedSkills.DeleteAll();
    x = skills->getProgressionPoints();

    foundSelected = false;

    int idx = 0; // Row index counter
    psSkillCacheIter p = skills->iterBegin();
    while (p.HasNext())
    {
        psSkillCacheItem *skill = p.Next();

        if (rowIdx && (int)skill->getNameId() == selectedNameId)
            *rowIdx = idx; // Row index of the selected skill
        ++idx;

        switch (skill->getCategory())
        {
        case 0://Stats
            {
                HandleSkillCategory(statsSkillList, "StatsIndicator", "Stats Button", skill);
                break;
            }
        case 1://Combat skills
            {        
                HandleSkillCategory(combatSkillList, "CombatIndicator", "Combat Button", skill);
                break;
            }
        case 2://Magic skills
            {
                HandleSkillCategory(magicSkillList, "MagicIndicator", "Magic Button", skill);
                break;
            }
        case 3://Jobs skills
            {
                HandleSkillCategory(jobsSkillList, "JobsIndicator", "Jobs Button", skill);
                break;
            }
        case 4://Various skills
            {
                HandleSkillCategory(variousSkillList, "VariousIndicator", "Various Button", skill);
                break;
            }
        }
      /*  if(stat)
        {
            combatSkillList->MoveRow(combatSkillList->GetRowCount()-1,stati);
            stati++;
        }*/      
    }

    if (!foundSelected)
    {
        selectedSkill.Clear();       
    }
}

void pawsSkillWindow::HandleSkillDescription( csString& description )
{   
    /* Example message:
    <DESCRIPTION NAME="Climbing" DESC="This skill enable user to climb." />
    */
    csRef<iDocument> descDoc = xml->CreateDocument();

    const char* error = descDoc->Parse( description );
    if ( error )
    {
        Error2( "Error Parsing Skill Description: %s\n", error );
        return;
    }

    csRef<iDocumentNode> root = descDoc->GetRoot();
    if (!root)
    {
        Error1("No XML root in Skill Description");
        return;
    }
    csRef<iDocumentNode> desc = root->GetNode("DESCRIPTION");
    if(!desc)
    {
        Error1("No <DESCRIPTION> tag in Skill Description");
        return;
    }

    const char *nameStr = "";
    nameStr = desc->GetAttributeValue("NAME");

    const char* descStr = "";
    descStr = desc->GetAttributeValue("DESC");
    if((!descStr) || (!strcmp(descStr,""))|| (!strcmp(descStr,"(null)")))
        descStr = "No description available";
    int skillCategory = desc->GetAttributeValueAsInt("CAT");

    // Add to the cache
    if (nameStr && strcmp(nameStr, "") && strcmp(nameStr, "(null)"))
    {
        csStringID skillNameId = (csStringID)psengine->FindCommonStringId(nameStr);
        if (skillNameId != csInvalidStringID)
        {
            if (skillDescriptions.Contains(skillNameId))
            {
                psSkillDescription *p = skillDescriptions.Get(skillNameId, NULL);
                if (p)
                    p->update(skillCategory, descStr);
            }
            else
            {
                psSkillDescription *item = new psSkillDescription(skillCategory, descStr);
                skillDescriptions.Put(skillNameId, item);
            }
        }
    }

    switch (skillCategory)
    {
    case 0://Stats
        {
             statsSkillDescription->SetText(descStr);
             break;
        }
    case 1://Combat skills
        {
            combatSkillDescription->SetText( descStr );
            break;
        }
    case 2://Magic skills
        {
            magicSkillDescription->SetText(descStr);
            break;
        }
    case 3://Jobs skills
        {
            jobsSkillDescription->SetText(descStr);
            break;
        }
    case 4://Various skills
        {
            variousSkillDescription->SetText(descStr);
            break;
        }
    }
}
void pawsSkillWindow::BuySkill()
{
    char commandData[256];
    csString escpxml = "";
    escpxml = EscpXML(selectedSkill);
    if (escpxml== "")
    {
        csString warning="You have to select a skill before.";
        PawsManager::GetSingleton().CreateWarningBox(warning); 
        return;
    }
    sprintf( commandData, "<B NAME=\"%s\" />", escpxml.GetData() );
    psGUISkillMessage outgoing( psGUISkillMessage::BUY_SKILL, commandData);

    msgHandler->SendMessage( outgoing.msg );
}

void pawsSkillWindow::Show()
{
    pawsControlledWindow::Show();

    // Hack set to no when show called because of an incoming skill table.
    if (skillString != "no")
    {
        skillCache.clear(); // Clear the skill cache before the new request
        psGUISkillMessage outgoing(psGUISkillMessage::REQUEST, "");
        msgHandler->SendMessage(outgoing.msg);
    }
}

void pawsSkillWindow::Hide()
{
    if (msgHandler)
    {
        psGUISkillMessage outgoing(psGUISkillMessage::QUIT, "");
        msgHandler->SendMessage(outgoing.msg);
    }
    skillCache.clear();
    pawsControlledWindow::Hide();
}


void pawsSkillWindow::OnListAction( pawsListBox* widget, int status )
{
    if (status==LISTBOX_HIGHLIGHTED)
    {
        pawsListBoxRow* row = widget->GetSelectedRow();

        pawsTextBox* skillName = (pawsTextBox*)(row->GetColumn(0));

        selectedSkill.Replace( skillName->GetText() );

        // Try to find cached copy of the description string */
        csStringID skillNameId = (csStringID)psengine->FindCommonStringId(selectedSkill);

        psSkillDescription *desc = NULL;
        if (skillNameId != csInvalidStringID)
            desc = skillDescriptions.Get(skillNameId, NULL);

        if (!desc)
        {
            // Request from the server
            char commandData[256];
            csString escpxml = EscpXML(selectedSkill);
            sprintf( commandData, "<S NAME=\"%s\" />", escpxml.GetData() );
            psGUISkillMessage outgoing( psGUISkillMessage::SKILL_SELECTED, commandData);

            msgHandler->SendMessage( outgoing.msg );
        }
        else
        {
            // Use the cached version
            switch (desc->getCategory())
            {
                case 0://Stats
                {
                    statsSkillDescription->SetText(desc->getDescription());
                    break;
                }
                case 1://Combat skills
                {
                    combatSkillDescription->SetText(desc->getDescription());
                    break;
                }
                case 2://Magic skills
                {
                    magicSkillDescription->SetText(desc->getDescription());
                    break;
                }
                case 3://Jobs skills
                {
                    jobsSkillDescription->SetText(desc->getDescription());
                    break;
                }
                case 4://Various skills
                {
                    variousSkillDescription->SetText(desc->getDescription());
                    break;
                }
            }
        }
    }
}

void pawsSkillWindow::Draw()
{
    static psClientVitals* vitals = NULL;
    if ( !vitals && psengine->GetCelClient() && psengine->GetCelClient()->GetMainPlayer() )
        vitals = psengine->GetCelClient()->GetMainPlayer()->GetVitalMgr();

    if (vitals)
    {
        static char buff[12];
        sprintf(buff,"%1.0f/%i", vitals->GetHP()*hitpointsMax, hitpointsMax );
        hpFrac->SetText(buff);
        sprintf(buff,"%1.0f/%i", vitals->GetMana()*manaMax, manaMax );
        manaFrac->SetText(buff);
        sprintf(buff,"%1.0f/%i", vitals->GetStamina(true)*physStaminaMax, physStaminaMax );
        pysStaminaFrac->SetText(buff);
		sprintf(buff,"%1.0f/%i", vitals->GetStamina(false)*menStaminaMax, menStaminaMax );
        menStaminaFrac->SetText(buff);
    }

    pawsWidget::Draw();
}

void pawsSkillWindow::FlashTabButton(const char* buttonName)
{
    if (buttonName != NULL && FindWidget(buttonName))
        ((pawsButton *) FindWidget(buttonName))->Flash(true);
}

void pawsSkillWindow::HandleSkillCategory(pawsListBox* tabNameSkillList,
                                          const char* indWidget,
                                          const char* tabName,
                                          psSkillCacheItem *skillInfo)
{

    int R = skillInfo->getRank();
    int Y = skillInfo->getKnowledge();
    int Z = skillInfo->getPractice();
    bool stat = skillInfo->isStat();

     // filter out untrained skills
    if (filter  &&  R==0  &&  Y==0  &&  Z==0)
        return;

    // Get the skill name string from common strings
    csString skillName = psengine->FindCommonString(skillInfo->getNameId());
    if (skillName == "")
    {
        Error2("Invalid skill name with Id %d", skillInfo->getNameId());
        return;
    }

    pawsListBoxRow* row = tabNameSkillList->NewRow();
                
    pawsTextBox* name = dynamic_cast <pawsTextBox*> (row->GetColumn(0));
    if (name == NULL) return;

    name->SetText( skillName );

    pawsTextBox* rank = dynamic_cast <pawsTextBox*> (row->GetColumn(1));
    if (rank == NULL) return;
    if (stat)
        rank->FormatText("%i(%i)", skillInfo->getRank(), skillInfo->getActualStat());
    else
        rank->FormatText("%i", skillInfo->getRank());

    pawsWidget * indCol = row->GetColumn(2);
    if (indCol == NULL) return;
    pawsSkillIndicator * indicator = dynamic_cast <pawsSkillIndicator*> (indCol->FindWidget(indWidget));
    if (indicator == NULL) return;
    indicator->Set(x, R, Y, skillInfo->getKnowledgeCost(),
                   Z, skillInfo->getPracticeCost());

    unsortedSkills.Push(row);
    if (skillName == selectedSkill)
    {
        statsSkillList->Select(row);
        foundSelected = true;
    }

    if (train) //If we are training, flash the tab button
        FlashTabButton(tabName);

}

/************************************************************************************
*
*                           graphical skill indicator
*
**************************************************************************************/

pawsSkillIndicator::pawsSkillIndicator()
{
    x = 0;
    rank = 0;
    y = 0;
    yCost = 0;
    z = 0;
    zCost = 0;

    g2d = PawsManager::GetSingleton().GetGraphics2D();
}

int pawsSkillIndicator::GetRelCoord(int pt)
{
    if (yCost + zCost == 0) // divide-by-zero guard
        return 0;
    else
    {
        int returnv = int(screenFrame.Width() * pt / float(yCost+zCost));
        if(returnv > screenFrame.Width())
            returnv = screenFrame.Width();

        return returnv;
    }
}

void pawsSkillIndicator::DrawSkillProgressBar(int x, int y, int width, int height, 
                                int start_r, int start_g, int start_b)
{
    pawsProgressBar::DrawProgressBar(
        csRect(x, y, x+width-1, y+height-1), PawsManager::GetSingleton().GetGraphics3D(), 1,
        start_r, start_g, start_b, 100, 100, 100 );
}

void pawsSkillIndicator::Draw()
{
    csRect sf = screenFrame;

    if (y < yCost)
    {
        int split = GetRelCoord(yCost);
        DrawSkillProgressBar(sf.xmin, sf.ymin, split, sf.Height(), 180, 180, 30);
        DrawSkillProgressBar(sf.xmin+split, sf.ymin, sf.Width()-split, sf.Height(), 180, 30, 30);
        DrawSkillProgressBar(sf.xmin, sf.ymin, GetRelCoord(y), sf.Height(), 0, 180, 0);
        DrawSkillProgressBar(sf.xmin, sf.ymin, GetRelCoord(x), sf.Height()/2, 30, 30, 180);
    }
    else
    {
        int split = GetRelCoord(y+z);
        DrawSkillProgressBar(sf.xmin, sf.ymin, split, sf.Height(), 0, 180, 0);
        DrawSkillProgressBar(sf.xmin+split, sf.ymin, sf.Width()-split, sf.Height(), 180, 30, 30);
    }
    g2d->DrawLine(sf.xmin, sf.ymin, sf.xmax, sf.ymin, 0);
    g2d->DrawLine(sf.xmax, sf.ymin, sf.xmax, sf.ymax, 0);
    g2d->DrawLine(sf.xmin, sf.ymax, sf.xmax, sf.ymax, 0);
    g2d->DrawLine(sf.xmin, sf.ymin, sf.xmin, sf.ymax, 0);
}

void pawsSkillIndicator::Set(int x, int rank, int y, int yCost, int z, int zCost)
{
    this->x = x;
    this->rank = rank;
    this->y = y;
    this->yCost = yCost;
    this->z = z;
    this->zCost = zCost;
}
