/*
 * psclientchar.cpp
 *
 * Copyright (C) 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.
 *
 * The client side version of char manager that talks with the server side
 * version. Used for things like inventory query's and such.
 *
 * Client version of character manager that talks to server version about 
 * the details involved about a player. 
 */
#include <psconfig.h>

#include <iutil/databuff.h>
#include <iengine/mesh.h>
#include <iengine/movable.h>
#include <csutil/sysfunc.h>
#include <iutil/objreg.h>
#include <iutil/vfs.h>
#include <imesh/object.h>
#include <imesh/partsys.h>
#include <imesh/explode.h>
#include <imesh/spiral.h>
#include <imesh/fountain.h>
#include <imesh/emit.h>
#include <iutil/plugin.h>
#include <csutil/xmltiny.h>

#include "net/message.h"
#include "net/messages.h"
#include "net/msghandler.h"
#include "net/charmessages.h"
#include "engine/celbase.h"
#include "engine/netpersist.h"

// CLIENT INCLUDES
#include "globals.h"
#include "pscelclient.h"
#include "psclientchar.h"
#include "iclient/isoundmngr.h"

#include "paws/pawsmanager.h"
#include "paws/pawstextbox.h"
#include "paws/pawsobjectview.h"
#include "gui/inventorywindow.h"
#include "gui/pawssummary.h"
#include "gui/pawsspellcancelwindow.h"
#include "gui/pawsinfowindow.h"

#include "effects/pseffectmanager.h"
#include "util/psmeshutil.h"
#include "pscamera.h"

//------------------------------------------------------------------------------

void Trait::Load( iDocumentNode* node )
{ 
    csString genderStr;
    name                 = node->GetAttributeValue( "name" );
    int cstr_id_mesh     = node->GetAttributeValueAsInt( "mesh" );
    int cstr_id_texture  = node->GetAttributeValueAsInt( "tex" );
    int cstr_id_material = node->GetAttributeValueAsInt( "mat" );
    genderStr            = node->GetAttributeValue( "gender" );                
    location             = ConvertTraitLocationString(node->GetAttributeValue( "loc" ));
    csString shaderColours = node->GetAttributeValue("shader");    
    if ( shaderColours.Length() > 0 )
        sscanf( shaderColours.GetData(), "%f,%f,%f", &shader.x, &shader.y, &shader.z );
    else
        shader = csVector3(0,0,0);        
    
    texture = psengine->FindCommonString(cstr_id_texture);
    material = psengine->FindCommonString(cstr_id_material);
    mesh = psengine->FindCommonString(cstr_id_mesh);

    if (genderStr == "") 
        genderStr = "N";
    if (genderStr != "F" && genderStr != "M" && genderStr != "N")
    {
       Error2("%s isn't a legal gender. Use (F,M,N)",genderStr.GetData());
       genderStr = "N";
    }

    if (genderStr == "F")
    {
        gender = PSCHARACTER_GENDER_FEMALE;
    } 
    else if (genderStr == "M")
    {
        gender = PSCHARACTER_GENDER_MALE;
    } 
    else 
    {
        gender = PSCHARACTER_GENDER_NONE;
    }

    csRef<iDocumentAttribute> idNode = node->GetAttribute("id");
    if ( idNode.IsValid())
        uid = idNode->GetValueAsInt();
    else
        uid = 0;

    csRef<iDocumentAttribute> nextNode = node->GetAttribute("next");
    if ( nextNode.IsValid())
        next_trait_uid = nextNode->GetValueAsInt();
    else
        next_trait_uid = 0;

    csRef<iDocumentAttribute> raceNode = node->GetAttribute("race");
    if ( raceNode.IsValid())
        raceID = raceNode->GetValueAsInt();
    else
        raceID = 0;

    next_trait = NULL;
    prev_trait = NULL;
}

//------------------------------------------------------------------------------

PSTRAIT_LOCATION Trait::ConvertTraitLocationString(const char *locationstring)
{
                                                                                                                                                                                                            
    if (locationstring==NULL)
        return PSTRAIT_LOCATION_NONE;
                                                                                                                                                                                                            
    if (!strcasecmp(locationstring,"HAIR_STYLE"))
        return PSTRAIT_LOCATION_HAIR_STYLE;
                                                                                                                                                                                                            
    if (!strcasecmp(locationstring,"HAIR_COLOR"))
        return PSTRAIT_LOCATION_HAIR_COLOR;
                                                                                                                                                                                                            
    if (!strcasecmp(locationstring,"BEARD_STYLE"))
        return PSTRAIT_LOCATION_BEARD_STYLE;
                                                                                                                                                                                                            
    if (!strcasecmp(locationstring,"FACE"))
        return PSTRAIT_LOCATION_FACE;
                                                                                                                                                                                                            
    if (!strcasecmp(locationstring,"SKIN_TONE"))
        return PSTRAIT_LOCATION_SKIN_TONE;
                                                                                                                                                                                                            
    if (!strcasecmp(locationstring,"ITEM"))
        return PSTRAIT_LOCATION_ITEM;
                                                                                                                                                                                                            
    return PSTRAIT_LOCATION_NONE;
}
//------------------------------------------------------------------------------

psClientCharManager::psClientCharManager(iObjectRegistry *objectreg)
{    
    ready = true;

    objectReg = objectreg;
    vfs = CS_QUERY_REGISTRY(objectReg, iVFS);

    charCreation = 0;

    target = 0;
    targetEffect = 0;
   
}

psClientCharManager::~psClientCharManager()
{
    if (msghandler)
    {
        msghandler->Unsubscribe(this, MSGTYPE_CHARREJECT);
        msghandler->Unsubscribe(this, MSGTYPE_EQUIPMENT);
        msghandler->Unsubscribe(this, MSGTYPE_EFFECT);
        msghandler->Unsubscribe(this, MSGTYPE_EFFECT_STOP);                
        msghandler->Unsubscribe(this, MSGTYPE_PLAYSOUND); 
        msghandler->Unsubscribe(this, MSGTYPE_USERACTION);
        msghandler->Unsubscribe(this, MSGTYPE_GUITARGETUPDATE);
    }
    delete charCreation;
}

bool psClientCharManager::Initialize( MsgHandler* msgHandler, 
                                      psCelClient* GEMSupervisor)
{
    msghandler = msgHandler;
    cel = GEMSupervisor;

    if ( !msghandler->Subscribe(this, MSGTYPE_CHARREJECT) )
        return false;
    if ( !msghandler->Subscribe(this, MSGTYPE_EQUIPMENT) )
        return false;
    if ( !msghandler->Subscribe(this, MSGTYPE_EFFECT) )
        return false;
    if ( !msghandler->Subscribe(this, MSGTYPE_EFFECT_STOP) )
        return false;        
    if ( !msghandler->Subscribe(this, MSGTYPE_PLAYSOUND) )
        return false;    
    if (!msghandler->Subscribe(this, MSGTYPE_USERACTION) )
        return false;
    if (!msghandler->Subscribe(this, MSGTYPE_GUITARGETUPDATE) )
        return false;
    if (!msghandler->Subscribe(this, MSGTYPE_CHANGE_TRAIT) )
        return false;        
    charCreation = new psCreationManager( objectReg );
   
    
    return true;
}

void psClientCharManager::HandleMessage ( MsgEntry* me )
{
    switch ( me->GetType() )
    {
        case MSGTYPE_CHANGE_TRAIT:
        {
            ChangeTrait(me);
            break;
        }
        
        case MSGTYPE_USERACTION:
        {
            HandleAction( me );
            break;
        }
        case MSGTYPE_CHARREJECT:
        {            
            HandleRejectCharMessage( me );
            break;
        }

        case MSGTYPE_EQUIPMENT: 
        {
            HandleEquipment(me); 
            return;
        }

        case MSGTYPE_EFFECT:
        {
            HandleEffect(me);
            return;
        }
        
        case MSGTYPE_EFFECT_STOP:
        {
            HandleEffectStop(me);
            return;
        }
        
        case MSGTYPE_PLAYSOUND:
        {
            if(psengine->GetSoundStatus())
            {
                HandlePlaySound(me);
            }
            return;
        }
        
        case MSGTYPE_GUITARGETUPDATE:
        {
            HandleTargetUpdate(me);
            return;
        }

        default:
        {
            ready = true;
            return;
        }
    }
}

void psClientCharManager::ChangeTrait( MsgEntry* me )
{
    psTraitChangeMessage mesg(me);
    unsigned int objectID = mesg.target;

    GEMClientObject* object = (GEMClientObject *)psengine->GetCelClient()->FindObject(objectID);
    if (!object)
    {
        Error2("Got trait change for object %d, but couldn't find it!", objectID);
        return;
    }

    // Update main object
    psengine->BuildAppearance( object->pcmesh->GetMesh(), mesg.string );

    // Update any doll views registered for changes
    csArray<iPAWSSubscriber*> dolls = PawsManager::GetSingleton().ListSubscribers("sigActorUpdate");
    for (size_t i=0; i<dolls.Length(); i++)
    {
        if (dolls[i] == NULL) 
            continue;
            
        pawsObjectView* doll = dynamic_cast<pawsObjectView*>(dolls[i]);
        
        if (doll == NULL) 
            continue;

        if (doll->GetID() == objectID) // This is a doll of the updated object
        {
            iMeshWrapper* dollObject = doll->GetObject();
            if (dollObject == NULL)
            {
                Error2("Cannot update registered doll view with ID %d because it has no object", doll->GetID());
                continue;
            }

            psengine->BuildAppearance( dollObject, mesg.string );
        }
    }
}


void psClientCharManager::SetTarget(GEMClientObject * newTarget, char * action, bool notifyServer)
{
    if (target != newTarget)
    {
        target = newTarget;

        PawsManager::GetSingleton().Publish("sTargetName",target?target->GetName():"" );
        if (!target)
            PawsManager::GetSingleton().Publish("fVitalValue0:Target",0);
    }

    // delete the old target effect
    psengine->GetEffectManager()->DeleteEffect(targetEffect);
    targetEffect = 0;
        
    PS_ID mappedID = 0;

    // Action locations don't have effects
    if (target && target->GetObjectType() != GEM_ACTION_LOC)
    {
         // render the target effect
         iMeshWrapper * targetMesh = target->pcmesh->GetMesh();
         if (targetMesh)
             targetEffect = psengine->GetEffectManager()->RenderEffect("target", csVector3(0,0,0), targetMesh);
    
         // notify the server of selection
         mappedID = target->GetEntity()->GetID();
    }

    // if it's a message sent by server, there is no need to resend back the same information
    if (notifyServer) 
    {
        psUserActionMessage userAction(0, mappedID, action);
        userAction.SendMessage();
    }
}

void psClientCharManager::HandleAction( MsgEntry* me )
{
    psUserActionMessage action(me);
    GEMClientActor* actor = dynamic_cast<GEMClientActor*>(cel->FindObject(action.target));
    if (actor)
    {
        if ( !actor->SetAnimation(action.action) )
            Error2("Error playing animation %s!", action.action.GetData() );
    }
    else
        Error1("Received psUserActionMessage for invalid object.");
}

void psClientCharManager::HandlePlaySound( MsgEntry* me )
{
    csRef<iSoundManager> soundmanager = psengine->GetSoundManager();
    if (soundmanager)
    {
        soundmanager->StartActionsSound( me->GetStr() );
    }
}

void psClientCharManager::HandleTargetUpdate( MsgEntry* me )
{
    psGUITargetUpdateMessage targetMsg(me);
    GEMClientObject* object = cel->FindObject( targetMsg.targetID );
    if ( object )
        SetTarget( object, "select", false);
    else
        Error1("Received TagetUpdateMessage with invalid target.");
}


void psClientCharManager::Equip( iMeshWrapper* object,
                                 csString& slotname,
                                 csString& mesh,
                                 csString& part,
                                 csString& partMesh,
                                 csString& texture
                               )
{
    psMeshUtil * meshutil = psengine->GetMeshUtil();


    if ( mesh.Length() )
    {
        meshutil->Attach( object, slotname, mesh );
    }

    // This is a part mesh (ie Mesh) change so change the mesh for that part.
    if ( partMesh.Length() )
    {
        meshutil->ChangeMesh( object, part, partMesh );
        if ( texture.Length() )
        {
            meshutil->ChangeMaterial( object, 
                                      meshutil->ParseStrings(object,part,partMesh),
                                      texture, texture );
        }
    }
    else
    {
        if ( part.Length() )
        {
            meshutil->ChangeMaterial( object, part, texture, texture );
        }
    }
}

void psClientCharManager::Dequip( iMeshWrapper* object,
                                  csString& slotname,
                                  csString& mesh,
                                  csString& part,
                                  csString& partMesh,
                                  csString& mat,
                                  csString& txt
                                )
{
    psMeshUtil * meshutil = psengine->GetMeshUtil();

    if ( mesh.Length() )
    {
        meshutil->Detach( object, slotname );
    }

    // This is a part mesh (ie Mesh) set default mesh for that part.
    if ( partMesh.Length() )
    {
        meshutil->DefaultMesh( object, part );
    }

    if ( part.Length() )
    {
        if ( mat.Length() )
        {
            meshutil->ChangeMaterial( object, part, mat, txt);
        }
        else
        {
            meshutil->DefaultMaterial( object, part );
        }            
    }
}


void psClientCharManager::Equip( iMeshWrapper * object, psEquipmentMessage * equip, csString slotname, csPDelArray<Trait>& list)
{
    if ( equip->type == psEquipmentMessage::EQUIP )
    {
        Equip(object,slotname,equip->mesh,equip->part,equip->partMesh,equip->texture);
    }
    else
    {
        csString mat("");
        csString txt("");
        for (size_t z = 0; z < list.Length(); z++ )
        {
            if ( list[z]->mesh == equip->part )
            {
                mat = list[z]->material;
                txt = list[z]->texture;
            }                   
        }
        Dequip(object,slotname,equip->mesh,equip->part,equip->partMesh,mat,txt);       
    }
}


void psClientCharManager::HandleEquipment( MsgEntry* me )
{
    psEquipmentMessage equip( me );
    unsigned int playerID = equip.player;
    
    GEMClientActor* object = (GEMClientActor*)cel->FindObject( playerID );
    if (!object)
    {
        Error2("Got equipment for actor %d, but couldn't find it!", playerID);
        return;
    }

    
    const char* slotname = psengine->slotName.GetName(equip.slot);
    
    if ( equip.slot == PSCHARACTER_SLOT_HEAD )
    {
        psString result(equip.mesh);
        result.ReplaceAllSubString("$H",object->helmGroup);                                                    
        equip.mesh = result;
    }
        
    // Update the actor
    Equip(object->pcmesh->GetMesh(), &equip, slotname, object->traitList);

    // Update any doll views registered for changes
    csArray<iPAWSSubscriber*> dolls = PawsManager::GetSingleton().ListSubscribers("sigActorUpdate");
    for (size_t i=0; i<dolls.Length(); i++)
    {
        if (dolls[i] == NULL) 
            continue;
        pawsObjectView* doll = dynamic_cast<pawsObjectView*>(dolls[i]);
        
        if (doll == NULL) 
            continue;
        
        if (doll->GetID() == playerID) // This is a doll of the updated object
        {
            iMeshWrapper* dollObject = doll->GetObject();
            if (dollObject == NULL)
            {
                Error2("Cannot update registered doll view with ID %d because it has no object", doll->GetID());
                continue;
            }
            
            Equip(dollObject, &equip, slotname, object->traitList);
        }
    }
}

void psClientCharManager::HandleEffectStop(MsgEntry* me)
{
    psStopEffectMessage effect(me);
    
    if (psengine->GetEffectManager ())
    {
        // This code work as long as PutUnique is used to enter
        // new entries to the hash.
        unsigned int effectID = effectMapper.Get(effect.uid,0);
        psengine->GetEffectManager()->DeleteEffect(effectID);
        effectMapper.DeleteAll(effect.uid);
    }        
}

void psClientCharManager::HandleEffect( MsgEntry* me )
{
    psEffectMessage effect(me);

    // get the anchor
    GEMClientObject* gemAnchor = cel->FindObject(effect.anchorID);
    iMeshWrapper * anchor = 0;

    if (!gemAnchor && effect.anchorID != 0)
    {
        // The entity isn't here, probably because it's too far away, so don't render the effect.
        // Not sure if we should do anything here, but meh.
    }
    else
    {
        if (gemAnchor)
            anchor = gemAnchor->pcmesh->GetMesh();
     
        // get the target
        GEMClientObject* gemTarget = cel->FindObject(effect.targetID);
        iMeshWrapper *target = anchor;
        if (gemTarget)
            target = gemTarget->pcmesh->GetMesh();
    
        // render the actual effect
        if (psengine->GetEffectManager ())
        {  
            unsigned int effectID = 0;
            unsigned int uniqueIDOverride = effectMapper.Get(effect.uid, 0); // Will return 0 if no uid in store or uid is 0.
            csVector3 up(0,1,0);
            
            if (anchor)
            {
                effectID = psengine->GetEffectManager()->RenderEffect(effect.name, effect.offset, 
                                                                      anchor, target,up,uniqueIDOverride);
            }
            else
            {
                iSector * sector = psengine->GetPSCamera()->GetICamera()->GetSector(); // Sector should come in the message
                effectID = psengine->GetEffectManager()->RenderEffect(effect.name, sector, 
                                                                      effect.offset, target,
                                                                      up,uniqueIDOverride); 
            }
            
            if (effectID == 0)
            {
                Error2("Failed to render effect %s",effect.name.GetDataSafe());
            }

            if ( effect.uid != 0 )
            {            
                effectMapper.PutUnique( effect.uid, effectID );
            }                
        }            
    
        // if this is a spell effect, and it's anchored to the main actor then the you must be casting the spell
        // show the spell cancel window
        if (effect.anchorID == psengine->GetCelClient()->GetMainActor()->GetID() && effect.castDuration > 0)
        {
            pawsSpellCancelWindow* widget = (pawsSpellCancelWindow *)PawsManager::GetSingleton().FindWidget( "SpellCancelWindow" );
            if (widget)
            {
                widget->Start(effect.castDuration);
            }
        }
    }
}


void psClientCharManager::HandleRejectCharMessage( MsgEntry* me )
{
    psCharRejectedMessage reject( me );
    
    switch ( reject.errorType )
    {
        case psCharRejectedMessage::NON_LEGAL_NAME:
        case psCharRejectedMessage::NON_UNIQUE_NAME:        
        case psCharRejectedMessage::RESERVED_NAME: 
        case psCharRejectedMessage::INVALID_CREATION: 
        {           
            PawsManager::GetSingleton().CreateWarningBox( reject.errorMesg );
            PawsManager::GetSingleton().FindWidget("CharCreateMain")->Show();            
            PawsManager::GetSingleton().FindWidget("Summary")->Hide();
            
            break;
        }
    }

}

//------------------------------------------------------------------------------

psCreationManager::psCreationManager( iObjectRegistry* objReg )
{
    objectReg = objReg;
    LoadRaceInformation();     
    
    msgHandler = psengine->GetMsgHandler();   
    msgHandler->Subscribe(this, MSGTYPE_CHAR_CREATE_CP);                         
    msgHandler->Subscribe(this, MSGTYPE_CHAR_CREATE_PARENTS);
    msgHandler->Subscribe(this, MSGTYPE_CHAR_CREATE_CHILDHOOD);    
    msgHandler->Subscribe( this, MSGTYPE_CHAR_CREATE_UPLOAD );
    msgHandler->Subscribe( this, MSGTYPE_CHAR_CREATE_VERIFY );    
    msgHandler->Subscribe( this, MSGTYPE_CHAR_CREATE_LIFEEVENTS );
    msgHandler->Subscribe( this, MSGTYPE_CHAR_CREATE_TRAITS );
    
    hasParentData = false;
    hasChildhoodData = false;
    hasLifeEventData = false;
    hasTraitData = false;

    selectedRace = -1;
    selectedFace = -1;
    selectedHairStyle = -1;
    selectedBeardStyle = -1;
    selectedHairColour = -1;
    selectedSkinColour = -1;  
    selectedGender = PSCHARACTER_GENDER_MALE;
    fatherMod = 1;
    motherMod = 1;
    
    nameGenerator = new NameGenerationSystem();
    nameGenerator->LoadDatabase( objReg );
    
    ClearChoices();
}

psCreationManager::~psCreationManager()
{
    msgHandler->Unsubscribe( this, MSGTYPE_CHAR_CREATE_UPLOAD );
    msgHandler->Unsubscribe( this, MSGTYPE_CHAR_CREATE_LIFEEVENTS );
    msgHandler->Unsubscribe( this, MSGTYPE_CHAR_CREATE_VERIFY );
    delete nameGenerator;

}

int psCreationManager::GetRaceCP( int race )
{
    if ( raceDescriptions[race]->startingCP == -1 )
    {
        // This is a simple message so don't need a seperate class for it.
        // We can just create the message our selves.        
        MsgEntry *msg = new MsgEntry( 100 );
        msg->SetType(MSGTYPE_CHAR_CREATE_CP);   
        msg->Add( (int32_t) race );
        msg->ClipToCurrentSize();
        
        msgHandler->SendMessage(msg);
        return REQUESTING_CP;
    }
    else
    {
        return raceDescriptions[race]->startingCP;
    }
}

void psCreationManager::HandleMessage( MsgEntry* me )
{ 
    switch ( me->GetType() )
    {
        case MSGTYPE_CHAR_CREATE_CP:
        {  
            int race   = me->GetInt32();          
            int points = me->GetInt32();
            raceDescriptions[race]->startingCP = points;
            return;
        }
        case MSGTYPE_CHAR_CREATE_PARENTS:
        {
            HandleParentsData( me );
            hasParentData = true;
            break;
        }
        case MSGTYPE_CHAR_CREATE_CHILDHOOD:
        {
            HandleChildhoodData( me );
            hasChildhoodData = true;
            break;
        }   
        
        case MSGTYPE_CHAR_CREATE_LIFEEVENTS:
        {
            HandleLifeEventData( me );
            hasLifeEventData = true;
            break;
        }

        case MSGTYPE_CHAR_CREATE_TRAITS:
        {
            HandleTraitData( me );
            hasTraitData = true;
            break;
        }

        case MSGTYPE_CHAR_CREATE_VERIFY:
        {
            HandleVerify( me );
            break;
        }
        
        case MSGTYPE_CHAR_CREATE_UPLOAD:
        {
            pawsWidget* wdg = PawsManager::GetSingleton().FindWidget("Summary");
            if (wdg)
                wdg->Hide();

            psengine->StartLoad();
            break;
        }
     
    }    
}

void psCreationManager::HandleLifeEventData( MsgEntry* me )
{
    psLifeEventMsg incomming( me );

    lifeEventData.SetLength( incomming.choices.Length() );

    for ( size_t x = 0; x < incomming.choices.Length(); x++ )
    {
        lifeEventData[x].id = incomming.choices[x]->id;
        lifeEventData[x].name = incomming.choices[x]->name;
        lifeEventData[x].description = incomming.choices[x]->description;
        lifeEventData[x].common = incomming.choices[x]->common;
        lifeEventData[x].cpCost = incomming.choices[x]->cpCost;

        incomming.choices[x]->adds.TransferTo( lifeEventData[x].adds );
        incomming.choices[x]->removes.TransferTo( lifeEventData[x].removes );
    }
}

void psCreationManager::HandleTraitData( MsgEntry* me )
{
    psCharCreateTraitsMessage msg(me);
    iDocumentSystem* xml = psengine->GetXMLParser ();
    csRef<iDocument> doc = xml->CreateDocument();

    const char* error = doc->Parse(msg.GetString().GetData());
    if ( error )
    {
        Error2("Error in XML: %s", error );
        return;
    }

    csRef<iDocumentNode> root = doc->GetRoot();
    if(!root)
    {
        Error1("No XML root in Trait Data");
        return;
    }
    csRef<iDocumentNodeIterator> iter1 = root->GetNode("traits")->GetNodes("trait");
    
    // Build the traits list
    while ( iter1->HasNext() )
    {
        csRef<iDocumentNode> node = iter1->Next();
        
        Trait *t = new Trait;
        t->Load( node );

        traits.Push(t);
    } 

    TraitIterator iter2 = GetTraitIterator();
    while ( iter2.HasNext() )
    {
        Trait * t = iter2.Next();
        t->next_trait = GetTrait(t->next_trait_uid);
        if (t->next_trait != NULL)
        {
           t->next_trait->prev_trait = t;
        }
    }

    // Insert into the custom location structure all top trait
    TraitIterator iter3 = GetTraitIterator();
    while ( iter3.HasNext() )
    {
        Trait * t = iter3.Next();
        // Check for top trait
        if (t->prev_trait == NULL)
        {            
            RaceDefinition * race = GetRace(t->raceID);
            if (race != NULL)
            {
                if (t->gender == PSCHARACTER_GENDER_NONE)
                {
                    race->location[t->location][PSCHARACTER_GENDER_MALE].Push(t);
                    race->location[t->location][PSCHARACTER_GENDER_FEMALE].Push(t);
                }
                else
                {
                    race->location[t->location][t->gender].Push(t);
                }
            } 
            else
            {
                Error3("Failed to insert trait '%s' into location table for race %d.\n",t->name.GetData(),t->raceID);
            }
        }
    }
}

Trait* psCreationManager::GetTrait(unsigned int uid)
{
    TraitIterator tIter = GetTraitIterator();
    while ( tIter.HasNext() )
    {
        Trait * t = tIter.Next();
        if (t->uid == uid) 
            return t;
    }
    return NULL;
}

void psCreationManager::HandleVerify( MsgEntry* me )
{
    psCharVerificationMesg mesg(me);
    pawsSummaryWindow * window = (pawsSummaryWindow*)PawsManager::GetSingleton().FindWidget("Summary");
    if ( window )
        window->SetVerify( mesg.stats, mesg.skills );                    
}

void psCreationManager::HandleChildhoodData( MsgEntry* me )
{
    psCreationChoiceMsg incomming( me );
    // Simple create a copy of the data in the message. 
    incomming.choices.TransferTo( childhoodData );       
}


void psCreationManager::HandleParentsData( MsgEntry* me )
{
    psCreationChoiceMsg incomming( me );
    
    // Simple create a copy of the data in the message. 
    incomming.choices.TransferTo( parentData );       
}


void psCreationManager::LoadRaceInformation()
{
    iDocumentSystem* xml = psengine->GetXMLParser ();
    iVFS* vfs = psengine->GetVFS ();
    
    csRef<iDataBuffer> buff = vfs->ReadFile( "/this/data/races/descriptions.xml" );

    if ( !buff || !buff->GetSize() )
    {
        Error2( "Could not load XML: %s", "descriptions.xml" );
        return;        
    }
    
    csRef<iDocument> doc = xml->CreateDocument();
    const char* error = doc->Parse( buff );
    if ( error )
    {
        Error3( "Error parsing XML file %s: %s",  "descriptions.xml", error );
        return;
    }
    
    csRef<iDocumentNode> root = doc->GetRoot();
    if(!root)
    {
        Error1("No XML root in descriptions.xml");
        return;
    }
    csRef<iDocumentNode> topNode = root->GetNode("descriptions");
    if(!topNode)
    {
        Error1("No <description> tag in descriptions.xml");
        return;
    }
    csRef<iDocumentNodeIterator> iter = topNode->GetNodes();
    
    while ( iter->HasNext() )
    {
        csRef<iDocumentNode> node = iter->Next();
        
        csString raceName     = node->GetAttributeValue( "name" );
        csString description  = node->GetAttributeValue( "description" );        
        csString maleModel    = node->GetAttributeValue( "male" );
        csString femaleModel  = node->GetAttributeValue( "female" );
        bool mavailable       = node->GetAttributeValueAsBool("male_available");
        bool favailable       = node->GetAttributeValueAsBool("female_available");
        
        RaceDefinition* race = new RaceDefinition;
        race->name = raceName;
        race->description = description;
        race->startingCP = -1;  
        race->femaleModelName = femaleModel;
        race->maleModelName = maleModel;
        race->femaleAvailable = favailable;
        race->maleAvailable = mavailable;
        
        // defaults 
        race->FollowPos.Set(0,3,4);   
        race->LookatPos.Set(0,2,0);
        race->FirstPos.Set(0,1.5,-.25);
        
        csRef<iDocumentNode> fp = node->GetNode("FollowPos");
        if (fp)
            race->FollowPos.Set(fp->GetAttributeValueAsFloat("x"),fp->GetAttributeValueAsFloat("y"),fp->GetAttributeValueAsFloat("z"));


        fp = node->GetNode("LookatPos");
        if (fp)
            race->LookatPos.Set(fp->GetAttributeValueAsFloat("x"),fp->GetAttributeValueAsFloat("y"),fp->GetAttributeValueAsFloat("z"));


        fp = node->GetNode("FirstPos");
        if (fp)
            race->FirstPos.Set(fp->GetAttributeValueAsFloat("x"),fp->GetAttributeValueAsFloat("y"),fp->GetAttributeValueAsFloat("z"));


        // Load the default camera position in the custom carachter generator
        fp = node->GetNode("ViewerPos");
        if (fp)        
            race->ViewerPos.Set(fp->GetAttributeValueAsFloat("x"),fp->GetAttributeValueAsFloat("y"),fp->GetAttributeValueAsFloat("z"));

        for ( int z = 0; z < PSTRAIT_LOCATION_COUNT; z++ )
        {
            race->zoomLocations[z].Set( 0,0,0 );
        }                                  
        
        fp = node->GetNode("FACEPOS");
        
        if (fp)        
            race->zoomLocations[PSTRAIT_LOCATION_FACE].Set(fp->GetAttributeValueAsFloat("x"),fp->GetAttributeValueAsFloat("y"),fp->GetAttributeValueAsFloat("z"));


        raceDescriptions.Push( race );
    }

}

void psCreationManager::LoadPathInfo()
{
    iDocumentSystem* xml = psengine->GetXMLParser ();
    iVFS* vfs = psengine->GetVFS ();
    
    csRef<iDataBuffer> buff = vfs->ReadFile( "/this/data/races/quickpaths.xml" );

    if ( !buff || !buff->GetSize() )
    {
        Error2( "Could not load XML: %s", "quickpaths.xml" );
        return;        
    }
    
    csRef<iDocument> doc = xml->CreateDocument();
    const char* error = doc->Parse( buff );
    if ( error )
    {
        Error3( "Error parsing XML file %s: %s",  "quickpaths.xml", error );
        return;
    }
    
    csRef<iDocumentNode> root = doc->GetRoot();
    if(!root)
    {
        Error1("No XML root in quickpaths.xml");
        return;
    }
    csRef<iDocumentNode> topNode = root->GetNode("paths");
    if(!topNode)
    {
        Error1("No <paths> tag in quickpaths.xml");
        return;
    }
    csRef<iDocumentNodeIterator> iter = topNode->GetNodes();
    
    while ( iter->HasNext() )
    {
        csRef<iDocumentNode> node = iter->Next();
        
        PathDefinition* path = new PathDefinition();
        path->name = node->GetAttributeValue("name");
        if ( path->name == "" )
            path->name = "None";
        path->info = node->GetAttributeValue("info");
        if ( path->info == "" )
            path->info = "None";

        //Load in all of the stat bonuses
        csRef<iDocumentNodeIterator> iter = node->GetNodes("StatBonus");
        while (iter->HasNext())
        {
            csRef<iDocumentNode> tmp = iter->Next();

            PathDefinition::Bonus* bonus = new PathDefinition::Bonus();

            csString name  = tmp->GetAttributeValue("name");
            float value = tmp->GetAttributeValueAsFloat("value");
            bonus->name = name;
            bonus->value = value;
            path->statBonuses.Push(bonus);
        }

        //Load in all of the bonus skills 
        iter = node->GetNodes("SkillBonus");
        while (iter->HasNext())
        {
            csRef<iDocumentNode> tmp = iter->Next();

            PathDefinition::Bonus* bonus = new PathDefinition::Bonus();

            csString skillName  = tmp->GetAttributeValue("name");
            float rank = tmp->GetAttributeValueAsFloat("rank");
            bonus->name = skillName;
            bonus->value = rank;
            path->skillBonuses.Push(bonus);
        }
        pathDefinitions.Push(path);
    }
}

const char* psCreationManager::GetRaceDescription( int race )
{
    if ( (size_t)race < raceDescriptions.Length() )
        return raceDescriptions[race]->description;
    else
        return "None";
}

const char* psCreationManager::GetModelName( int race, int gender )
{    
    if ( (size_t)race < raceDescriptions.Length() && race >= 0 )
    {
        if ( gender == PSCHARACTER_GENDER_NONE ||
             gender == PSCHARACTER_GENDER_MALE )
        {
            return raceDescriptions[race]->maleModelName;
        }
        else
        {
            return raceDescriptions[race]->femaleModelName;
        }
    }
    
    return NULL;
}    
                        


void psCreationManager::GetParentData()
{
    if ( !hasParentData )
    {
        psCreationChoiceMsg outgoing(MSGTYPE_CHAR_CREATE_PARENTS);
        outgoing.SendMessage();        
    }        
}

void psCreationManager::GetChildhoodData()
{
    if ( !hasChildhoodData )
    {
        psCreationChoiceMsg outgoing(MSGTYPE_CHAR_CREATE_CHILDHOOD);
        outgoing.SendMessage();        
    } 
}

void psCreationManager::GetLifeEventData()
{
    if ( !hasLifeEventData )
    {
        psLifeEventMsg outgoing;
        outgoing.SendMessage();        
    }
}

void psCreationManager::GetTraitData()
{
    if ( !hasTraitData )
    {
        psCreationChoiceMsg outgoing(MSGTYPE_CHAR_CREATE_TRAITS);
        outgoing.SendMessage();
    }        
}


RaceDefinition* psCreationManager::GetRace( int race )
{
    return raceDescriptions[race];
}

const char* psCreationManager::GetDescription( int id )
{
    size_t x;
    for ( x = 0; x < parentData.Length(); x++ )
    {
        if (  parentData[x].id == id )
            return parentData[x].description;
    }
    
    for ( x = 0; x < childhoodData.Length(); x++ )
    {
        if (  childhoodData[x].id == id )
            return childhoodData[x].description;
    }
    
    
    return "None";
} 

CreationChoice* psCreationManager::GetChoice( int id )
{
    size_t x;
    for ( x = 0; x < parentData.Length(); x++ )
    {
        if (  parentData[x].id == id )
            return &parentData[x];
    }
    
    for ( x = 0; x < childhoodData.Length(); x++ )
    {
        if (  childhoodData[x].id == id )
            return &childhoodData[x];
    }
    
    
    return NULL;
} 

void psCreationManager::SetRace( int newRace )
{
    selectedRace = newRace;
}

void psCreationManager::UploadChar( bool verify )
{
    RaceDefinition* race = GetRace( selectedRace );
    if ( !race )
        return;
        
    csString firstname;
    csString lastname;

    // Separate names
    firstname = selectedName.Slice(0,selectedName.FindFirst(' '));
    if (selectedName.FindFirst(' ') != SIZET_NOT_FOUND)
        lastname = selectedName.Slice(selectedName.FindFirst(' ')+1,selectedName.Length());
    else
        lastname = "";

    // Make BIO
    pawsSummaryWindow* summary = (pawsSummaryWindow*)PawsManager::GetSingleton().FindWidget("Summary");
    pawsMultiLineTextBox* btext = (pawsMultiLineTextBox*) summary->FindWidget("text_birth");
    pawsMultiLineTextBox* ltext = (pawsMultiLineTextBox*) summary->FindWidget("text_life");

    csString bio;
    bio += btext->GetText();
    bio += ltext->GetText();

    psCharUploadMessage upload( verify, firstname.GetDataSafe(), lastname.GetDataSafe(),
                                selectedRace, selectedGender, choicesMade, motherMod, fatherMod,
                                lifeEventsMade, selectedFace, selectedHairStyle, selectedBeardStyle,
                                selectedHairColour, selectedSkinColour, bio.GetDataSafe(), path.GetDataSafe() );

    upload.SendMessage();                                                                            
}

void psCreationManager::SetName( const char* newName )
{ 
    selectedName.Replace( newName ); 
}

csString psCreationManager::GetName()
{ 
    return selectedName; 
}



LifeEventChoice* psCreationManager::FindLifeEvent( int idNumber )
{
    for ( size_t x = 0; x < lifeEventData.Length(); x++ )
    {
        if ( lifeEventData[x].id == idNumber )
            return &lifeEventData[x];
    }

    return NULL;
}


void psCreationManager::AddChoice( int choice, int modifier )
{   
    if(!GetChoice(choice))
    {
        Error2("Invalid creation choice: %i", choice);
        return;
    }
    choicesMade.Push( choice );    
    currentCP-=GetCost( choice )*modifier;
}


void psCreationManager::RemoveChoice( uint32_t choice, int modifier )
{
    for ( size_t x = 0; x < choicesMade.Length(); x++ )
    {
        if ( choicesMade[x] == choice )
        {
            currentCP += GetCost( choicesMade[x] )*modifier;
            choicesMade.DeleteIndex(x);
            
            return;
        }
    }
}

void psCreationManager::AddLifeEvent( int event )
{
    lifeEventsMade.Push( event );
    currentCP -= GetLifeCost( event );
}

void psCreationManager::RemoveLifeEvent( uint32_t event )
{
    for ( size_t x = 0; x < lifeEventsMade.Length(); x++ )
    {
        if ( lifeEventsMade[x] == event )
        {            
            currentCP += GetLifeCost( event );
            lifeEventsMade.DeleteIndex(x);
            return;
        }
    }
}

int psCreationManager::GetCost( int id )
{
    size_t x;
    for ( x = 0; x < parentData.Length(); x++ )
    {
        if (  parentData[x].id == id )
            return parentData[x].cpCost;
    }
    
    for ( x = 0; x < childhoodData.Length(); x++ )
    {
        if (  childhoodData[x].id == id )
            return childhoodData[x].cpCost;
    }    
    
    return -1;
}

int psCreationManager::GetLifeCost( int id )
{
    LifeEventChoice* life = FindLifeEvent( id );
    if ( life != NULL )
        return life->cpCost;    
    
    return 0;        
}

void psCreationManager::SetCustomization( int face, int hairStyle, int beardStyle, int hairColour, int skinColour )
{
    selectedFace = face;
    selectedHairStyle = hairStyle;
    selectedBeardStyle = beardStyle;
    selectedHairColour = hairColour;
    selectedSkinColour = skinColour;
}

RaceDefinition* psCreationManager::GetRace( const char* name )
{
    for ( size_t i = 0; i < raceDescriptions.Length(); i++ )
    {
        if ( raceDescriptions[i]->name == name )
            return raceDescriptions[i];
    }
    return NULL;
}

csPDelArray<PathDefinition>::Iterator psCreationManager::GetPathIterator()
{
    if (pathDefinitions.Length() == 0)
        LoadPathInfo();
    return pathDefinitions.GetIterator();
}

PathDefinition* psCreationManager::GetPath(int i)
{
    return pathDefinitions[i];
}

const char* psCreationManager::GetLifeEventDescription( int id )
{
     for ( size_t n = 0; n < lifeEventData.Length(); n++ )
     {
        if ( lifeEventData[n].id == id )
        {
            return lifeEventData[n].description;
        }
     }
     
     return "None";
}

bool psCreationManager::IsAvailable(int id, int gender)
{   
    RaceDefinition* race = GetRace(id);
    if (!race)
        return false;
    
    if (gender==PSCHARACTER_GENDER_NONE) 
        return race->maleAvailable; //If neutral gender, check if male gender is available
    else if (gender==PSCHARACTER_GENDER_FEMALE) 
        return race->femaleAvailable;
    else if (gender==PSCHARACTER_GENDER_MALE) 
        return race->maleAvailable;

    return false;
}

void psCreationManager::ClearChoices()
{
    if (selectedRace != -1)
    {
        currentCP = GetRaceCP( selectedRace );
        path = "None";
    }
    choicesMade.DeleteAll();
    fatherMod = 1;
    motherMod = 1;
    lifeEventsMade.DeleteAll();
}
