/*
 * spellmanager.cpp by Anders Reggestad <andersr@pvv.org>
 *
 * Copyright (C) 2001-2002 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 <iutil/document.h>
#include <csutil/xmltiny.h>
#include <physicallayer/entity.h>
#include <propclass/mesh.h>

#include "globals.h"

#include "spellmanager.h"
#include "clients.h"
#include "playergroup.h"
#include "gem.h"
#include "net/msghandler.h"
#include "net/messages.h"
#include "util/eventmanager.h"
#include "util/psxmlparser.h"
#include "bulkobjects/pscharacterloader.h"
#include "bulkobjects/psspell.h"
#include "bulkobjects/psglyph.h"
#include "psserver.h"
#include "psserverchar.h"
#include "cachemanager.h"
#include "progressionmanager.h"


psSpellManager::psSpellManager(ClientConnectionSet *ccs,
                               EntityManager *celserv,
                               iObjectRegistry * object_reg)
{
    entitymanager    = celserv;
    clients      = ccs;
    this->object_reg = object_reg;

    randomgen = psserver->rng;

    psserver->GetEventManager()->Subscribe(this,MSGTYPE_GLPYH_REQUEST,REQUIRE_READY_CLIENT|REQUIRE_ALIVE);  
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_GLYPH_ASSEMBLE,REQUIRE_READY_CLIENT|REQUIRE_ALIVE);  
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_SPELL_CAST,REQUIRE_READY_CLIENT|REQUIRE_ALIVE);  
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_PURIFY_GLYPH,REQUIRE_READY_CLIENT|REQUIRE_ALIVE);  
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_SAVE_SPELL,REQUIRE_READY_CLIENT|REQUIRE_ALIVE);  
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_SPELL_BOOK,REQUIRE_READY_CLIENT|REQUIRE_ALIVE);  
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_SPELL_CANCEL,REQUIRE_READY_CLIENT|REQUIRE_ALIVE);      
}

psSpellManager::~psSpellManager()
{
    if (psserver->GetEventManager())
    {
        psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_GLPYH_REQUEST);
        psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_GLYPH_ASSEMBLE);
        psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_SPELL_CAST);  
        psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_PURIFY_GLYPH);  
        psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_SAVE_SPELL);
        psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_SPELL_BOOK);
        psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_SPELL_CANCEL);
    }
}

void psSpellManager::HandleMessage(MsgEntry *me,Client *client)
{
    switch( me->GetType())
    {
        case MSGTYPE_SPELL_CAST:
        {
            psSpellCastMessage mesg(me);            
            Cast(client,mesg.spell,mesg.kFactor);
            break;
        }
        case MSGTYPE_PURIFY_GLYPH:
        {
            psPurifyGlyphMessage mesg(me);
            StartPurifying(client, mesg.glyph);
            break;
        }        
        case MSGTYPE_SAVE_SPELL:
        {
            psSaveSpellMessage mesg(me);
            SaveSpell(client, mesg.spell);            
            break;
        }
        case MSGTYPE_SPELL_BOOK:
        {
            SendSpellBook( client );
            break;
        }
        case MSGTYPE_GLPYH_REQUEST:
        {
            SendGlyphs( client );
            break; 
        }
        case MSGTYPE_GLYPH_ASSEMBLE:
        {
            HandleAssembler( client, me );
            break;
        }       
        
        case MSGTYPE_SPELL_CANCEL:
        {
            CancelSpellCasting(client->GetActor());
            break;
        }            
    }
}

//debug:
void DumpAssembler(Client * client, glyphList_t & assembler)
{
    psString msg;
    msg="";
    for (size_t i=0; i < assembler.Length(); i++)
        msg.AppendFmt("S%u ",assembler[i]->GetUID());
    psserver->SendSystemInfo(client->GetClientNum(), msg.GetData());
}

void psSpellManager::HandleAssembler( Client* client, MsgEntry* me )
{
    psGlyphAssembleMessage mesg;
    mesg.FromClient( me );
    
    glyphList_t assembler;
    psSpell * spell;
    psItemStats * stats;
    
    assembler.SetLength(GLYPH_ASSEMBLER_SLOTS);   // set to maximum length so the glyphs will fit in

    size_t numGlyphs = 0;
    for (size_t slotNum=0; slotNum < GLYPH_ASSEMBLER_SLOTS; slotNum++)
    {        
        int statID = mesg.glyphs[slotNum];
        stats = CacheManager::GetSingleton().GetBasicItemStatsByID(statID);
        if (stats != NULL)
        {
            numGlyphs++;
            assembler[numGlyphs-1] = stats;
        }
    }
    assembler.SetLength(numGlyphs);   // set assembler to actual length
    
    //DumpAssembler(client, assembler);

    if ( ! client->GetCharacterData()->HasGlyphs(assembler))
    {
        Error2("Client %i tried to research spell with glyphs he actually doesn't have", client->GetClientNum());
        SendGlyphs(client);
        return;
    }

    csString name(" ");
    csString image(" ");
    csString description(" ");
    
    if ( (spell = FindSpell(client, assembler)) )
    {
        description = spell->GetDescription();
        name = spell->GetName();
        image = spell->GetImage();
    }        
        
    if ( spell )        
    {
        int skill = client->GetCharacterData()->GetSkills()->GetSkillRank( spell->GetWay()->skill );                        
        int maxRealm = 1 + skill/20;
        
        if ( maxRealm >= spell->GetRealm() )
        {        
            psGlyphAssembleMessage newmsg(client->GetClientNum(), name, image, description);
            newmsg.SendMessage();
            return;
        }        
    }

    // clear the description, if this is not valid glyph sequence for our player:
    psGlyphAssembleMessage newmsg(client->GetClientNum(), name, image, description);
    newmsg.SendMessage();
}


void psSpellManager::SaveSpell(Client * client, csString spellName)
{
    psSpell * spell = client->GetCharacterData()->GetSpellByName(spellName);
    if (spell)
    {
        psserver->SendSystemInfo(client->GetClientNum(),
                                 "You know the %s spell already!",spellName.GetData());
        return;
    }

    spell = CacheManager::GetSingleton().GetSpellByName(spellName);
    if (!spell)
    {
        psserver->SendSystemInfo(client->GetClientNum(),
                                 "%s isn't a defined spell!",spellName.GetData());
        return;
    }


    client->GetCharacterData()->AddSpell(spell);

    psServer::CharacterLoader.SaveCharacterData(client->GetCharacterData(),client->GetActor());

    SendSpellBook(client);
    psserver->SendSystemInfo(client->GetClientNum(),
                           "%s added to you spell book!",spellName.GetData());
}

void psSpellManager::Cast(Client * client, csString spellName, float kFactor)
{

    psSpell * spell = CacheManager::GetSingleton().GetSpellByName(spellName);
    if (!spell)
    {
        psserver->SendSystemInfo(client->GetClientNum(),
                                 "%s is a unknown spell for you!",spellName.GetData());
        return;
    }

    client->GetCharacterData()->SetKFactor(kFactor);

    csString  effectName;
    csVector3 offset;
    PS_ID     anchorID;
    PS_ID     targetID;
    unsigned int  castingDuration;
    csString  castingText;
    psSpellGameEvent *event = spell->Cast(this, client, effectName, offset, anchorID, targetID, castingDuration, &castingText);

    //if ( events->Length() > 0 )
    if ( event )
    {
        //event = events->Get( 0 );
        //do
        //{
        //    event = events->Pop();
            event->FireEvent();
    
            //targetID = event->target->GetEntity()->GetID();
            if (effectName && effectName != "")
            {
                psEffectMessage newmsg(0, effectName, offset, anchorID, targetID, castingDuration, 0);
                if (newmsg.valid)
                    newmsg.Multicast(event->caster->GetActor()->GetMulticastClients(),0,PROX_LIST_ANY_RANGE);
                else
                {
                    Bug1("Could not create valid psEffectMessage for broadcast.\n");
                }
            }
        //} while ( events->Length() > 0 );
    }

    // Inform player
    if (castingText != "")
    {
        psserver->SendSystemInfo(client->GetClientNum(),castingText.GetData());
    }
}

void psSpellManager::CancelSpellCasting(gemActor * caster)
{
    caster->GetCharacterData()->InterruptSpellCasting();
}

void psSpellManager::SendSpellBook(Client * client)
{
    psSpellBookMessage mesg( client->GetClientNum() );
    csArray<psSpell*> spells = client->GetCharacterData()->GetSpellList();
    
    for ( size_t i = 0; i < spells.Length(); i++ )
    {
        csString glyph0("");
        csString glyph1("");
        csString glyph2("");
        csString glyph3("");
        csString name( spells[i]->GetName() );
        csString description( spells[i]->GetDescription() );
        csString way(spells[i]->GetWay()->name);
        int realm = spells[i]->GetRealm();
        csArray<psItemStats*> glyphs = spells[i]->GetGlyphList();
        if ( glyphs.Length() > 0 )
            glyph0 = glyphs[0]->GetImageName();
        if ( glyphs.Length() > 1 )
            glyph1 = glyphs[1]->GetImageName();
        if ( glyphs.Length() > 2 )
            glyph1 = glyphs[2]->GetImageName();
        if ( glyphs.Length() > 3 )
            glyph1 = glyphs[3]->GetImageName();
            
        mesg.AddSpell(name,description,way,realm,
                 glyph0,glyph1,glyph2,glyph3 );
    }

    mesg.Construct();
    mesg.SendMessage();
}

void psSpellManager::SendGlyphs( Client * client)
{
    psCharacter * character = client->GetCharacterData();
    csArray <glyphSlot_t> slots;
    size_t slotNum;
    int wayNum;
    
    character->CreateGlyphList(false, slots);
    
    psRequestGlyphsMessage outMessage( client->GetClientNum() );     
    for (slotNum=0; slotNum < slots.Length(); slotNum++)
    {
        csString way;
        PSITEMSTATS_SLOTLIST validSlots;
        int statID;

        validSlots = slots[slotNum].glyphType->GetValidSlots();
        statID = slots[slotNum].glyphType->GetUID();

        if (validSlots & PSITEMSTATS_SLOT_CRYSTAL) wayNum = 0;
        if (validSlots & PSITEMSTATS_SLOT_BLUE) wayNum = 1;
        if (validSlots & PSITEMSTATS_SLOT_AZURE) wayNum = 2;
        if (validSlots & PSITEMSTATS_SLOT_BROWN) wayNum = 3;
        if (validSlots & PSITEMSTATS_SLOT_RED) wayNum = 4;
        if (validSlots & PSITEMSTATS_SLOT_DARK) wayNum = 5;

        outMessage.AddGlyph( slots[slotNum].glyphType->GetName(),
                             slots[slotNum].glyphType->GetImageName(),
                             slots[slotNum].count,
                             slots[slotNum].purifyStatus,
                             wayNum, 
                             statID );
                             
    }
    
    outMessage.Construct();
    outMessage.SendMessage();    
}

psGlyph * FindUnpurifiedGlyph(psCharacter * character, unsigned int statID)
{
    int bulkNum;
    for (bulkNum=0; bulkNum < PSCHARACTER_BULK_COUNT; bulkNum++)
    {
        psGlyph * glyph = dynamic_cast <psGlyph*> (character->Inventory().GetBulkItem(bulkNum));
        if (glyph != NULL)
            if (glyph->GetBaseStats()->GetUID()==statID   &&   glyph->GetPurifyStatus()==0)
                return glyph;
    }
    return NULL;
}

void psSpellManager::StartPurifying(Client * client, int statID)
{
    psCharacter* character = client->GetCharacterData();

    psGlyph* glyph = FindUnpurifiedGlyph(character, statID);

    if (glyph == NULL)
        return;

    if (glyph->GetStackCount() > 1)
    {
        if ( !character->Inventory().CanFit(glyph) || !character->Inventory().HasEmptyBulkSlots() )
        {
            psserver->SendSystemError(client->GetClientNum(), "Your inventory is full!" );
            SendGlyphs(client);
            return;
        }

        glyph = dynamic_cast <psGlyph*> (glyph->SplitStack(1));
        if (glyph == NULL)
            return;

        if ( !character->Inventory().PutInBulk( (psItem*&)glyph, ANY_EMPTY_BULK_SLOT ) )
        {
            Error2("Failed to move purifying glyph to bulk slot for %s", client->GetName() );
            CacheManager::GetSingleton().RemoveInstance( (psItem*&)glyph );
            return;
        }
    }

    glyph->PurifyingStarted();
    
    // If the glyph has no ID, we must save it now because we need to have an ID for the purification script
    glyph->ForceSaveIfNew();

    // Use the progression manager to purify so that the
    // purify event will continue when player reconnect if
    // not finished before disconnect.
    psserver->SendSystemInfo(client->GetClientNum(), "You start to purify %s", glyph->GetName() );
    csString event;
    event.Format("<evt><script delay=\"%d\" persistent=\"yes\"><purify glyph=\"%u\"/></script></evt>",20000,glyph->GetUID());
    psserver->GetProgressionManager()->ProcessScript(event.GetData(),client->GetActor(),client->GetActor());

    SendGlyphs(client);
    psserver->GetCharManager()->SendInventory(client->GetClientNum());
}

void psSpellManager::EndPurifying(psCharacter * character, uint32 glyphUID)
{
    Client * client = clients->FindPlayer(character->GetCharacterID());
    if (!client)
    {
        Error1("No purifyer!");
        return;
    }

    for (int bulkNum=0; bulkNum < PSCHARACTER_BULK_COUNT; bulkNum++)
    {
        psItem * item = character->Inventory().GetBulkItem(bulkNum);
        if (item!=NULL  &&  item->GetUID()==glyphUID)
        {
            psGlyph * glyph = dynamic_cast <psGlyph*> (item);
            if (glyph != NULL)
            {
                glyph->PurifyingFinished();
                glyph->Save();
                psserver->SendSystemInfo(client->GetClientNum(), "The glyph %s is now purified", glyph->GetName());
                SendGlyphs(client);
                psserver->GetCharManager()->SendInventory(client->GetClientNum());
                return;
            }
        }
    }
}

/**
 * Verify if the combination in the assembler slots
 * form a spell.
 * @return Return the spell created by the assembler glyphs.
 */
psSpell* psSpellManager::FindSpell(Client * client, const glyphList_t & assembler)
{
    CacheManager::SpellIterator loop = CacheManager::GetSingleton().GetSpellIterator();
    
    psSpell *p;
    
    while (loop.HasNext())
    {
        p = loop.Next();

        if (p->MatchGlyphs(assembler))
        {
            // Combination of glyphs correct
            return p;
        }
    }

    return NULL;
}

psSpell* psSpellManager::FindSpell(csString& name)
{
    return CacheManager::GetSingleton().GetSpellByName(name);
}

psSpell* psSpellManager::FindSpell(int spellID)
{
    return CacheManager::GetSingleton().GetSpellByID(spellID);
}

/**
 * This is the meat and potatoes of the spell engine here.
 */
void psSpellManager::HandleSpellEvent(psSpellGameEvent *event)
{
    csString  responseEffectName;
    csVector3 offset;
    PS_ID     anchorID;
    PS_ID     targetID;
    csString  effectText;
             
    // Start the effect if spell is successfully casted
    if (event->spell->Effect(this, event, responseEffectName, offset, anchorID, targetID, &effectText))
    {
        // Only gain practice if the spell was effective
        event->caster->GetCharacterData()->GetSkills()->AddSkillPractice(event->spell->GetSkill(),1);
    }
    
    event->caster->GetActor()->SetMode( PSCHARACTER_MODE_PEACE );
    event->caster->GetCharacterData()->SetSpellCasting(NULL);
    
    // If there is some sort of visual/particle/audio effect for the target then fire it out.
    if (responseEffectName && responseEffectName != "")
    {
        psEffectMessage newmsg(0, responseEffectName, offset, anchorID, targetID, 0, 0);
        if (newmsg.valid)
            newmsg.Multicast(event->target->GetMulticastClients(),0,PROX_LIST_ANY_RANGE);
        else
        {
            Bug1("Could not create valid psEffectMessage for broadcast.\n");
        }
    }
    
    // Inform player
    if (effectText != "")
    {
        psserver->SendSystemInfo(event->caster->GetActor()->GetClientID(),effectText.GetData());
    }
}

/*-------------------------------------------------------------*/

psSpellGameEvent::psSpellGameEvent(psSpellManager *mgr,
                                   const psSpell * spell,
                                   Client *caster,
                                   gemObject *target,
                                   float min_range,
                                   float max_range,
                                   float powerLevel)
    : psGameEvent(0,spell->GetCastingDuration(),"psSpellGameEvent")
{
    spellmanager     = mgr;
    this->spell      = spell;
    this->caster     = caster;
    this->target     = target;
    valid            = true;
    this->min_range = min_range;
    this->max_range = max_range;
    this->powerLevel= powerLevel;

    target->RegisterCallback( this );
    caster->GetActor()->RegisterCallback( this );
    caster->GetCharacterData()->SetSpellCasting(this);
}

psSpellGameEvent::~psSpellGameEvent()
{
    if ( target )
        target->UnregisterCallback(this);
    if ( caster )
    {
        caster->GetCharacterData()->SetSpellCasting(NULL);
        caster->GetActor()->UnregisterCallback(this);    
    }
}

void psSpellGameEvent::DeleteObjectCallback(iDeleteNotificationObject * object)
{
    if ( target )
        target->UnregisterCallback(this);
    if ( caster )        
        caster->GetActor()->UnregisterCallback(this);

    Interrupt();   

    target = NULL;
    caster = NULL;    
}

void psSpellGameEvent::Interrupt()
{
    // Check if this event have been stoped before
    if (!IsValid()) return;
    
    psserver->SendSystemInfo(caster->GetClientNum(),"Your spell (%s) has been interrupted!",spell->GetName().GetData());

    caster->GetActor()->SetMode( PSCHARACTER_MODE_PEACE );
    caster->GetCharacterData()->SetSpellCasting(NULL);

    // Stop event from beeing executet when trigged.
    SetValid(false);
}

void psSpellGameEvent::Trigger()
{
    spellmanager->HandleSpellEvent(this);
}

/*-------------------------------------------------------------*/

psSpellEffectGameEvent::psSpellEffectGameEvent(psSpellManager *mgr,
                                   const psSpell *spell,
                                   Client  *caster, 
                                   gemObject *target, 
                                   csTicks progression_delay,
                                   float min_range,
                                   float max_range,
                                   bool saved,
                                   float powerLevel)
    : psGameEvent(0, progression_delay,"psSpellEffectGameEvent")
{
    spellmanager    = mgr;
    this->spell     = spell;
    this->caster    = caster;
    this->target    = target;
    this->min_range = min_range;
    this->max_range = max_range;
    this->saved     = saved;
    this->powerLevel= powerLevel;

    target->RegisterCallback( this );
    caster->GetActor()->RegisterCallback( this );
}


psSpellEffectGameEvent::~psSpellEffectGameEvent()
{
    if ( target )
        target->UnregisterCallback(this);
    if ( caster )
        caster->GetActor()->UnregisterCallback(this);    
}

void psSpellEffectGameEvent::DeleteObjectCallback(iDeleteNotificationObject * object)
{
    SetValid(false); // Prevent the Trigger from beeing called.

    if ( target )
        target->UnregisterCallback(this);
    if ( caster )        
        caster->GetActor()->UnregisterCallback(this);

    target = NULL;
    caster = NULL;    
}

void psSpellEffectGameEvent::Trigger()
{
    spell->PerformResult(caster->GetActor(), target, min_range, max_range, saved, powerLevel);
}
