/*
 * CacheManager.cpp
 *
 * Copyright (C) 2001 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 "util/log.h"
#include "util/stringarray.h"
#include "util/serverconsole.h"

#include "cachemanager.h"
#include "commandmanager.h"
#include "util/eventmanager.h"
#include "util/psdatabase.h"
#include "globals.h"
#include "bulkobjects/psraceinfo.h"
#include "bulkobjects/psguildinfo.h"
#include "bulkobjects/pstrade.h"
#include "bulkobjects/psaccountinfo.h"
#include "client.h"
#include "bulkobjects/psquest.h"
#include "bulkobjects/psmerchantinfo.h"
#include "bulkobjects/psspell.h"
#include "bulkobjects/psglyph.h"



CacheManager::CacheManager()
{
    slotMap[PSCHARACTER_SLOT_RIGHTHAND]   = PSITEMSTATS_SLOT_RIGHTHAND;
    slotMap[PSCHARACTER_SLOT_LEFTHAND]    = PSITEMSTATS_SLOT_LEFTHAND;
    slotMap[PSCHARACTER_SLOT_BOTHHANDS]   = PSITEMSTATS_SLOT_BOTHHANDS;
    slotMap[PSCHARACTER_SLOT_HEAD]        = PSITEMSTATS_SLOT_HEAD;
    slotMap[PSCHARACTER_SLOT_RIGHTFINGER] = PSITEMSTATS_SLOT_RIGHTFINGER;
    slotMap[PSCHARACTER_SLOT_LEFTFINGER]  = PSITEMSTATS_SLOT_LEFTFINGER;
    slotMap[PSCHARACTER_SLOT_NECK]        = PSITEMSTATS_SLOT_NECK;
    slotMap[PSCHARACTER_SLOT_BOOTS]       = PSITEMSTATS_SLOT_BOOTS;    
    slotMap[PSCHARACTER_SLOT_BACK]        = PSITEMSTATS_SLOT_BACK;
    slotMap[PSCHARACTER_SLOT_ARMS]        = PSITEMSTATS_SLOT_ARMS;
    slotMap[PSCHARACTER_SLOT_GLOVES]      = PSITEMSTATS_SLOT_GLOVES;
    slotMap[PSCHARACTER_SLOT_LEGS]        = PSITEMSTATS_SLOT_LEGS;
    slotMap[PSCHARACTER_SLOT_BELT]        = PSITEMSTATS_SLOT_BELT;
    slotMap[PSCHARACTER_SLOT_BRACERS]     = PSITEMSTATS_SLOT_BRACERS;
    slotMap[PSCHARACTER_SLOT_TORSO]       = PSITEMSTATS_SLOT_TORSO;
    slotMap[PSCHARACTER_SLOT_MIND]        = PSITEMSTATS_SLOT_MIND;

    psItemStatFlags statflag("MELEEWEAPON",  PSITEMSTATS_FLAG_IS_A_MELEE_WEAPON);
    ItemStatFlagArray.Push(statflag);
    statflag.Set( "ARMOR",        PSITEMSTATS_FLAG_IS_ARMOR);
    ItemStatFlagArray.Push(statflag);
    statflag.Set( "RANGEWEAPON",  PSITEMSTATS_FLAG_IS_A_RANGED_WEAPON);
    ItemStatFlagArray.Push(statflag);
    statflag.Set( "SHIELD",       PSITEMSTATS_FLAG_IS_A_SHIELD );
    ItemStatFlagArray.Push(statflag);
    statflag.Set( "AMMO",         PSITEMSTATS_FLAG_IS_AMMO );
    ItemStatFlagArray.Push(statflag);
    statflag.Set( "CONTAINER",    PSITEMSTATS_FLAG_IS_A_CONTAINER );
    ItemStatFlagArray.Push(statflag);
    statflag.Set( "CANTRANSFORM", PSITEMSTATS_FLAG_CAN_TRANSFORM );
    ItemStatFlagArray.Push(statflag);
    statflag.Set( "USESAMMO",     PSITEMSTATS_FLAG_USES_AMMO );
    ItemStatFlagArray.Push(statflag);
    statflag.Set( "STACKABLE",    PSITEMSTATS_FLAG_IS_STACKABLE );
    ItemStatFlagArray.Push(statflag);
    statflag.Set( "EQUIP_STACKABLE",    PSITEMSTATS_FLAG_IS_EQUIP_STACKABLE );
    ItemStatFlagArray.Push(statflag);    
    statflag.Set( "GLYPH",        PSITEMSTATS_FLAG_IS_GLYPH );
    ItemStatFlagArray.Push(statflag);
    statflag.Set( "TRIA",         PSITEMSTATS_FLAG_TRIA );
    ItemStatFlagArray.Push(statflag);
    statflag.Set( "HEXA",         PSITEMSTATS_FLAG_HEXA );
    ItemStatFlagArray.Push(statflag);
    statflag.Set( "OCTA",         PSITEMSTATS_FLAG_OCTA );
    ItemStatFlagArray.Push(statflag);
    statflag.Set( "CIRCLE",       PSITEMSTATS_FLAG_CIRCLE );                                        
    ItemStatFlagArray.Push(statflag);
    statflag.Set( "CONSUMABLE",   PSITEMSTATS_FLAG_CONSUMABLE );
    ItemStatFlagArray.Push(statflag);
    statflag.Set( "READABLE",     PSITEMSTATS_FLAG_IS_READABLE );
    ItemStatFlagArray.Push(statflag);
    statflag.Set( "END",0 );
    ItemStatFlagArray.Push(statflag);

    effectID = 0;
}

CacheManager::~CacheManager()
{
    UnloadAll();
}

bool CacheManager::PreloadAll()
{
    maxCommonStrID = db->SelectSingleNumber("SELECT MAX(id) FROM common_strings");
    
    if (!PreloadCommonStrings())
      return false;
    if (!PreloadSectors())
      return false;
    if (!PreloadSkills())
      return false;
    if (!PreloadRaceInfo())
      return false;
    if (!PreloadTraits())
      return false; // Need RaceInfo
    if (!PreloadItemCategories())
      return false;
    if (!PreloadItemAnimList())
      return false;
    if (!PreloadItemStatsDatabase())
      return false;
    if (!PreloadWays())
      return false;
    if (!PreloadSpells())
      return false;
    if (!PreloadQuests())
      return false;
    if (!PreloadTradeCombinations())
      return false;
    if (!PreloadTradeTransformations())
      return false;
    if (!PreloadTradePatterns())
      return false;
    if (!PreloadTips())
      return false;
    if (!PreloadBadNames())
      return false;
    if (!PreloadArmorVsWeapon())
      return false;
    if (!PreloadMovement())
      return false;    
    
//    PreCreateCraftMessages();
    PreloadCommandGroups();
    PreloadUpdateInfo();

    return true;
}

void CacheManager::PreloadCommandGroups()
{
    commandManager = new psCommandManager;
    commandManager->LoadFromDatabase();
}
void CacheManager::PreCreateCraftMessages()
{
    /// Get a list of all the trade patterns in the database. 
    Result allPatterns(db->Select("SELECT * from trade_patterns") );
    
    int currentPattern = 0;
    psMsgCraftingInfo * tradePattern;
    
    for (currentPattern=0; currentPattern < (int)allPatterns.Count(); currentPattern++)
    {                  
                                          
        int patternID = allPatterns[currentPattern].GetInt("id");
        int groupID = allPatterns[currentPattern].GetInt("group_id");
        csString patternName = allPatterns[currentPattern]["pattern_name"];
        /// Create the new pattern message for it.
        tradePattern = new psMsgCraftingInfo( patternID, patternName );
    
        CacheCraftCombos( tradePattern, patternID, groupID );
        CacheCraftTransforms( tradePattern, patternID, groupID );
                                
        tradePattern->Construct();        
        craftingMessages.Push( tradePattern );
    }            
}    

psMsgCraftingInfo* CacheManager::GetCraftInfo( int patternID )
{
    for ( size_t z = 0; z < craftingMessages.GetSize(); z++ )
    {
        if ( craftingMessages[z]->tradePatternID == patternID )
            return craftingMessages[z];            
    }
    
    return NULL;
}


void CacheManager::UnloadAll()
{
    delete commandManager;

    {
        csHash<psQuest *>::GlobalIterator it (quests_by_id.GetIterator ());
        while (it.HasNext ())
        {
            psQuest* quest = it.Next ();
            delete quest;
        }
    }

    {
        csHash<psGuildAlliance *>::GlobalIterator it (alliance_by_id.GetIterator ());
        while (it.HasNext ())
        {
            psGuildAlliance* alliance = it.Next ();
            delete alliance;
        }
    }

    {
        csHash<psGuildInfo *>::GlobalIterator it (guildinfo_by_id.GetIterator ());
        while (it.HasNext ())
        {
            psGuildInfo* guildinfo = it.Next ();
            delete guildinfo;
        }
    }

    {
        csHash<psItemStats *, csString>::GlobalIterator it (itemStats_NameHash.GetIterator ());
        while (it.HasNext ())
        {
            psItemStats* itemstats = it.Next ();
            delete itemstats;
        }
    }

    {
        csHash<psSectorInfo *>::GlobalIterator it (sectorinfo_by_id.GetIterator ());
        while (it.HasNext ())
        {
            psSectorInfo* sector = it.Next ();
            delete sector;
        }
    }

    // ToDo: unload everything else
}

void CacheManager::RemoveInstance( psItem * & item )
{
    Notify2(LOG_CACHE, "Removing Instance of item: %u", item->GetUID());
    if (item->GetUID() != 0)
        db->Command("DELETE from item_instances where id='%u'", item->GetUID());
    delete item;
    item = NULL;
}

bool CacheManager::PreloadSkills()
{
    unsigned int currentrow;
    psSkillInfo *newskill;
    Result result(db->Select("SELECT * from skills") );

    if (!result.IsValid())
    return false;

    for (currentrow=0; currentrow<result.Count(); currentrow++)
    {
        if (result[currentrow]["skill_id"]!=NULL)
        {
            newskill=new psSkillInfo;

            newskill->id = (PSSKILL) result[currentrow].GetInt("skill_id");
            newskill->name = result[currentrow]["name"];
            newskill->description = result[currentrow]["description"];
            newskill->practice_factor = result[currentrow].GetInt("practice_factor");
            newskill->mental_factor = result[currentrow].GetInt("mental_factor");
            newskill->price = psMoney(result[currentrow].GetInt("price"));
            newskill->baseCost = result[currentrow].GetInt("base_rank_cost");

            csString type( result[currentrow]["category"] );
            if ( type == "STATS" )
                newskill->category = PSSKILLS_CATEGORY_STATS;
            else if ( type == "COMBAT" )
                newskill->category = PSSKILLS_CATEGORY_COMBAT;
            else if ( type == "MAGIC" )
                newskill->category = PSSKILLS_CATEGORY_MAGIC;
            else if ( type == "JOBS" )
                newskill->category = PSSKILLS_CATEGORY_JOBS;
            else if ( type == "VARIOUS" )
                newskill->category = PSSKILLS_CATEGORY_VARIOUS;

            skillinfolist.Push(newskill);
            maxCommonStrID++;
            msg_strings.Register(newskill->name,(csStringID)maxCommonStrID);
        }
    }

    return true;
}

bool CacheManager::PreloadSectors()
{
    unsigned int currentrow;
    psSectorInfo *newsector;
    Result result(db->Select("SELECT * from sectors") );

    if (!result.IsValid())
        return false;

    for (currentrow=0; currentrow< result.Count(); currentrow++)
    {
        newsector=new psSectorInfo;
        CS_ASSERT(newsector!=NULL);

        newsector->uid  = result[currentrow].GetInt("id");
        newsector->name = result[currentrow]["name"];
        newsector->rain_enabled = strcmp(result[currentrow]["rain_enabled"],"Y")==0;

        newsector->rain_min_gap = result[currentrow].GetInt("rain_min_gap");
        newsector->rain_max_gap = result[currentrow].GetInt("rain_max_gap");
        CS_ASSERT(newsector->rain_min_gap <= newsector->rain_max_gap);
        
        newsector->rain_min_duration = result[currentrow].GetInt("rain_min_duration");
        newsector->rain_max_duration = result[currentrow].GetInt("rain_max_duration");
        CS_ASSERT(newsector->rain_min_duration <= newsector->rain_max_duration);

        newsector->rain_min_drops = result[currentrow].GetInt("rain_min_drops");
        newsector->rain_max_drops = result[currentrow].GetInt("rain_max_drops");
        CS_ASSERT(newsector->rain_min_drops <= newsector->rain_max_drops);

        newsector->lightning_min_gap = result[currentrow].GetInt("lightning_min_gap");
        newsector->lightning_max_gap = result[currentrow].GetInt("lightning_max_gap");
        CS_ASSERT(newsector->lightning_min_gap <= newsector->lightning_max_gap);

        newsector->rain_min_fade_in = result[currentrow].GetInt("rain_min_fade_in");
        newsector->rain_max_fade_in = result[currentrow].GetInt("rain_max_fade_in");
        CS_ASSERT(newsector->rain_min_fade_in <= newsector->rain_max_fade_in);

        newsector->rain_min_fade_out = result[currentrow].GetInt("rain_min_fade_out");
        newsector->rain_max_fade_out = result[currentrow].GetInt("rain_max_fade_out");
        CS_ASSERT(newsector->rain_min_fade_out <= newsector->rain_max_fade_out);

        sectorinfo_by_id.Put(newsector->uid,newsector);
        sectorinfo_by_name.Put(csHashCompute(newsector->name),newsector);
        
        maxCommonStrID++;
        msg_strings.Register(newsector->name,(csStringID)maxCommonStrID);
    }
    return true;
}

bool CacheManager::PreloadMovement()
{
    Result modes(db->Select("SELECT * FROM movement_modes"));
    if ( !modes.IsValid() || !modes.Count() )
        return false;

    for (unsigned int i=0; i<modes.Count(); i++)
    {
        psCharMode* newmode = new psCharMode;

        newmode->id = modes[i].GetUInt32("id");
        newmode->name = modes[i]["name"];
        newmode->move_mod.x = modes[i].GetFloat("move_mod_x");
        newmode->move_mod.y = modes[i].GetFloat("move_mod_y");
        newmode->move_mod.z = modes[i].GetFloat("move_mod_z");
        newmode->rotate_mod.x = modes[i].GetFloat("rotate_mod_x");
        newmode->rotate_mod.y = modes[i].GetFloat("rotate_mod_y");
        newmode->rotate_mod.z = modes[i].GetFloat("rotate_mod_z");
        newmode->idle_animation = modes[i]["idle_animation"];

        if (newmode->id > 127)  // Based on DR message variable size
        {
            Error3("ID %u for movement '%s' is to large.\n"
                   "Clients only support up to 128 different movement types, with IDs from 0-127",
                   newmode->id, newmode->name.GetData() );
            return false;
        }

        char_modes.Put(newmode->id,newmode);
    }

    Result types(db->Select("SELECT * FROM movement_types"));
    if ( !types.IsValid() || !types.Count() )
        return false;
        
    for (unsigned int i=0; i<types.Count(); i++)
    {
        psMovement* newmove = new psMovement;

        newmove->id = types[i].GetUInt32("id");
        newmove->name = types[i]["name"];
        newmove->base_move.x = types[i].GetFloat("base_move_x");
        newmove->base_move.y = types[i].GetFloat("base_move_y");
        newmove->base_move.z = types[i].GetFloat("base_move_z");
        newmove->base_rotate.x = types[i].GetFloat("base_rotate_x");
        newmove->base_rotate.y = types[i].GetFloat("base_rotate_y");
        newmove->base_rotate.z = types[i].GetFloat("base_rotate_z");

        if (newmove->id > 31)  // Based on active move bits in client movement manager
        {
            Error3("ID %u for movement '%s' is to large.\n"
                   "Clients only support up to 32 different movement types, with IDs from 0-31",
                   newmove->id, newmove->name.GetData() );
            return false;
        }

        movements.Put(newmove->id,newmove);
    }

    return true;
}

uint8_t CacheManager::GetCharModeID(const char* name)
{
    for (size_t i=0; i<char_modes.Length(); i++)
        if (char_modes[i]->name == name)
            return (uint8_t)i;
    return (uint8_t)-1;
}

uint8_t CacheManager::GetMovementID(const char* name)
{
    for (size_t i=0; i<movements.Length(); i++)
        if (movements[i]->name == name)
            return (uint8_t)i;
    return (uint8_t)-1;
}

bool CacheManager::PreloadArmorVsWeapon()
{
    unsigned int currentrow;
    Result result(db->Select("select * from armor_vs_weapon"));

    if (!result.IsValid())
        return false;

    for (currentrow=0; currentrow<result.Count(); currentrow++)
    {
        ArmorVsWeapon* newvs=new ArmorVsWeapon;

        newvs->id          = result[currentrow].GetUInt32("id");
        newvs->c[0][0]     = result[currentrow].GetFloat("1a");
        newvs->c[0][1]     = result[currentrow].GetFloat("1b");
        newvs->c[0][2]     = result[currentrow].GetFloat("1c");
        newvs->c[0][3]     = result[currentrow].GetFloat("1d");

        newvs->c[1][0]     = result[currentrow].GetFloat("2a");
        newvs->c[1][1]     = result[currentrow].GetFloat("2b");
        newvs->c[1][2]     = result[currentrow].GetFloat("2c");

        newvs->c[2][0]     = result[currentrow].GetFloat("3a");
        newvs->c[2][1]     = result[currentrow].GetFloat("3b");
        newvs->c[2][2]     = result[currentrow].GetFloat("3c");
        newvs->weapontype  = result[currentrow]["weapon_type"];

        armor_vs_weapon.Push(newvs);
    }

    
    Notify2(LOG_COMBAT,"Testing Armor VS Weapon table ('3c','Dagger'): %f\n",GetArmorVSWeaponResistance("3c","Dagger"));
    Notify2(LOG_COMBAT,"Testing Armor VS Weapon table ('1d','Claymore'): %f\n",GetArmorVSWeaponResistance("1d","Claymore"));
    Notify2(LOG_COMBAT,"Testing Armor VS Weapon table ('2b','Sabre'): %f\n",GetArmorVSWeaponResistance("2b","Sabre"));
    
    return true;
}

bool CacheManager::PreloadCommonStrings()
{
    unsigned int currentrow;
    Result result(db->Select("select id,string from common_strings"));

    if (!result.IsValid())
        return false;

    for (currentrow=0; currentrow<result.Count(); currentrow++)
        msg_strings.Register(result[currentrow]["string"],(csStringID)result[currentrow].GetUInt32("id"));

    Notify2( LOG_STARTUP, "%d Common Strings Loaded", msg_strings.GetSize() );
    return true;
}

bool CacheManager::PreloadQuests()
{
    unsigned int currentrow;
    psQuest *quest;

    Result result(db->Select("select * from quests order by id"));

    if (!result.IsValid())
        return false;

    for (currentrow=0; currentrow<result.Count(); currentrow++)
    {
        quest = new psQuest;

        if (quest->Load(result[currentrow]))
            quests_by_id.Put(quest->GetID(),quest);
        else
            delete quest;
    }

    // Process loaded quests
    csHash<psQuest *>::GlobalIterator it (quests_by_id.GetIterator ());
    while (it.HasNext ())
    {
    psQuest* quest = it.Next ();
    quest->PostLoad();
    }

    return true;
}

csHash<psQuest *>::GlobalIterator CacheManager::GetQuestIterator()
{
    return quests_by_id.GetIterator();
}

////////////////////////////////////////////////////////////////////
// Trade Combinations
bool CacheManager::PreloadTradeCombinations()
{
    unsigned int currentrow;
    uint32 lastarrayid = 0;
    uint32 lastitemid = 0;
    //csPDelArray<psTradeCombinations> *newarray;

    
    Result result(db->Select(
     "select * from trade_combinations order by pattern_id, result_id, item_id"));
    if (!result.IsValid())
        return false;

        
    CombinationConstruction *ctr = NULL;
    CombinationSet *set = NULL;
    
    for (currentrow=0; currentrow<result.Count(); currentrow++)
    {   
        // If it is a new pattern start a new combination set for them
        uint32 id = result[currentrow].GetUInt32("pattern_id");

        // pattern 0 is not valid
        if (id==0) {
            uint32 rowid = result[currentrow].GetUInt32("id");
            Error2("Error in loading trade_combinations.id=%i , it has a pattern_id=0",rowid);
            return false;
        }
        if ( id != lastarrayid )
        {
            set = new CombinationSet;
            set->patternID = id;
            combinations.Push(set);
            lastarrayid = id;
        }

        // If it is a new item start a new construction set for it.
        uint32 resultItem = result[currentrow].GetUInt32("result_id"); 
        if ( resultItem != lastitemid )
        {
            ctr = new CombinationConstruction;
            ctr->resultItem = resultItem;
            ctr->resultQuantity = result[currentrow].GetInt("result_qty");
            set->set.Push(ctr);
            lastitemid = resultItem;
        }

        // Load the combination and push it into it's construction set.
        psTradeCombinations* comb = new psTradeCombinations;
        if (comb->Load(result[currentrow]))
            ctr->combinations.Push(comb);
        else
            delete comb;        
    }

    return true;
}

// TODO: Make this faster with hash tables
CombinationSet * CacheManager::FindCombinationsList(uint32 patternid)
{
    for (size_t x=0; x < combinations.Length(); x++)
    {
        if (combinations[x]->patternID == patternid)
            return combinations[x];
    }
    return NULL;
}

// Trade Transformations
bool CacheManager::PreloadTradeTransformations()
{
    unsigned int currentrow;
    uint32 lastpid = (size_t)-1;
    uint32 lastiid = (size_t)-1;
    csPDelArray<psTradeTransformations> *newarray;

    Result result(db->Select("select * from trade_transformations order by pattern_id, item_id"));
    if (!result.IsValid())
        return false;

    for (currentrow=0; currentrow<result.Count(); currentrow++)
    {
        uint32 pid = result[currentrow].GetUInt32("pattern_id");
        uint32 iid = result[currentrow].GetUInt32("item_id");
        if ((pid != lastpid) || (iid != lastiid))  // new set
        {
            newarray = new csPDelArray<psTradeTransformations>;
            transformations_list.Push(newarray);
            lastpid = pid;
            lastiid = iid;
        }
        psTradeTransformations* tran = new psTradeTransformations;
//        tran->SetTransformationCacheFlag(true);
        if (tran->Load(result[currentrow]))
            newarray->Push(tran);
        else
            delete tran;
    }

    return true;
}

// TODO: Make this faster with hash tables
csPDelArray<psTradeTransformations> *CacheManager::FindTransformationsList(uint32 patternid, uint32 targetid)
{
    for (size_t x=0; x<transformations_list.Length(); x++)
    {
        if ((transformations_list[x]->Get(0)->GetPatternId() == patternid) &&
            (transformations_list[x]->Get(0)->GetItemId() == targetid) )
            return transformations_list[x];
    }
    return NULL;
}

// Trade Patterns
bool CacheManager::PreloadTradePatterns()
{
    unsigned int currentrow;
    psTradePatterns* newPattern;

    // Get a list of the trade patterns ignoring the group and dummy ones
    Result result(db->Select("select * from trade_patterns where designitem_id != 0 order by designitem_id"));
    if (!result.IsValid())
        return false;

    for (currentrow=0; currentrow<result.Count(); currentrow++)
    {
        newPattern = new psTradePatterns;
        if (!newPattern->Load(result[currentrow]))
        {
            delete newPattern;
            return false;
        }
        tradePatterns_IDHash.Put(newPattern->GetDesignItemId(),newPattern);
    }
    return true;
}

psTradePatterns *CacheManager::GetTradePatternByItemID(uint32 id)
{
    return tradePatterns_IDHash.Get(id,NULL);
}

/*
psTradePatterns* CacheManager::InstantiateTradePatternByID(uint32 patternid)
{
    Result result(db->Select("SELECT * from trade_patterns where id=%u",patternid));
    if (!result.IsValid() || result.Count()<1)
    {
        Warning3(LOG_CONNECTIONS,"Could not find trade pattern for id %u.  Error: %s",
         patternid,db->GetLastError());
        return NULL;
    }
    psTradePatterns *patterninfo = new psTradePatterns;
    if (patterninfo->Load(result[0]))
        return patterninfo;
    else
    {
        delete patterninfo;
        return NULL;
    }
}

psTradePatterns* CacheManager::InstantiateTradePatternByDesignID(uint32 designid)
{
    Result result(db->Select("SELECT * from trade_patterns where designitem_Id=%u",designid));
    if (!result.IsValid() || result.Count()<1)
    {
        Warning3(LOG_CONNECTIONS,"Could not find trade pattern for design id %u.  Error: %s",
         designid,db->GetLastError());
        return NULL;
    }
    psTradePatterns *patterninfo = new psTradePatterns;
    if (patterninfo->Load(result[0]))
        return patterninfo;
    else
    {
        delete patterninfo;
        return NULL;
    }
}
*/
/*
psTradeAutoContainers* CacheManager::InstantiateTradeAutoContainerByID(uint32 container_ID)
{
    Result result(db->Select("SELECT * from trade_autocontainers where item_instance_id=%d", container_ID ) );
    
    if (!result.IsValid() || result.Count()<1)
    {
        Warning4(LOG_CONNECTIONS,"Could not find trade auto container for item ID %d.  Error: %s Query: %s",
         container_ID, db->GetLastError(), db->GetLastQuery());
        return 0;
    }
    psTradeAutoContainers *autocontainerinfo = new psTradeAutoContainers;
    if (autocontainerinfo->Load(result[0]))
    {
        return autocontainerinfo;
    }
    else
    {
        delete autocontainerinfo;
        return NULL;
    }
}
*/

////////////////////////////////////////////////////////////////////
bool CacheManager::PreloadTips()
{
    unsigned int currentrow;

    // Id<1000 means we are excluding Tutorial tips
    Result result(db->Select("select tip from tips where id<1000"));
    if (!result.IsValid())
        return false;

    for (currentrow=0; currentrow<result.Count(); currentrow++)
    {
        csString dummy(result[currentrow][0]);
        tips_list.Push(dummy);
    }

    return true;
}

/////////////////////////////////////////////////////////////////////
const char *CacheManager::FindCommonString(unsigned int id)
{
    if (id==0)
      return "";
    return msg_strings.Request(id);
}
unsigned int CacheManager::FindCommonStringID(const char *name)
{
    if ( name == NULL )
    return 0;

    csStringID id = (csStringID) msg_strings.Request(name);
    return (id == csInvalidStringID) ? 0 : id;
}

psQuest *CacheManager::GetQuestByID(unsigned int id)
{
    psQuest *quest = quests_by_id.Get(id, NULL);
    return quest;
}

psQuest *CacheManager::GetQuestByName(const char *name)
{
    if (name==NULL)
        return NULL;

    csHash<psQuest *>::GlobalIterator it (quests_by_id.GetIterator ());
    while (it.HasNext ())
    {
    psQuest* quest = it.Next ();
    if (!strcmp(quest->GetName(),name))
            return quest;
    }
    return NULL;
}

psQuest *CacheManager::AddDynamicQuest(const char *name, psQuest *parentQuest, int step)
{
    if (!name)
        return NULL;

    psQuest *ptr=(psQuest*)1;
    //  subquests need a fixed id to be loaded at next restart
    // quest_id*10000+sub_id
    int id = 10000+(parentQuest->GetID()*100)+step;
    ptr = new psQuest;
    ptr->Init(id,name);
    ptr->SetTask("NULL");
    ptr->SetParentQuest(parentQuest);
    quests_by_id.Put(id, ptr);

    return ptr;
}




// TODO: This should be done faster, probably not with an array
psSkillInfo *CacheManager::GetSkillByID(unsigned int id)
{
    size_t i;
    psSkillInfo *currentskill;

    for (i=0;i<skillinfolist.Length();i++)
    {
        currentskill=skillinfolist.Get(i);
    if (currentskill && (unsigned int)currentskill->id==id)
            return currentskill;
    }
    return NULL;

}

// TODO: This should be done faster, probably not with an array
psSkillInfo *CacheManager::GetSkillByName(const char *name)
{
    size_t i;
    psSkillInfo *currentskill;

    for (i=0;i<skillinfolist.Length();i++)
    {
        currentskill=skillinfolist.Get(i);
        if (currentskill && currentskill->name.CompareNoCase(name) )
            return currentskill;
    }
    return NULL;
}

void CacheManager::GetSkillsListbyCategory(csArray <psSkillInfo>& listskill,int category )
{
    psSkillInfo *currentskill;
    for(size_t i=0;i<skillinfolist.Length();i++)
    {
        currentskill=skillinfolist.Get(i);
        if(currentskill && currentskill->category == category)
            listskill.Push(*currentskill);
    }
}


csHash<psSectorInfo *>::GlobalIterator CacheManager::GetSectorIterator()
{
    return sectorinfo_by_name.GetIterator();
}

psSectorInfo *CacheManager::GetSectorInfoByID(unsigned int id)
{
    return sectorinfo_by_id.Get(id, NULL);
}

psSectorInfo *CacheManager::GetSectorInfoByName(const char *name)
{
    if (name == NULL || strlen(name) == 0)
        return NULL;

    return sectorinfo_by_name.Get(csHashCompute(name), NULL);
}


PSTRAIT_LOCATION CacheManager::ConvertTraitLocationString(const char *locationstring)
{

    if (locationstring==NULL)
        return PSTRAIT_LOCATION_NONE;

    for (int i = 0; i < PSTRAIT_LOCATION_COUNT; i++)
    {
        if (!strcasecmp(locationstring,psTrait::locationString[i]))
            return (PSTRAIT_LOCATION)i;
    }
                
    return PSTRAIT_LOCATION_NONE;
}

bool CacheManager::PreloadTraits()
{
    unsigned int currentrow;
    psTrait *newtrait;
    Result result(db->Select("SELECT * from traits order by id"));

    if (!result.IsValid())
        return false;

    for (currentrow=0; currentrow<result.Count(); currentrow++)
    {
        newtrait=new psTrait;
        
        PSTRAIT_LOCATION loc;

        newtrait->uid               = result[currentrow].GetUInt32("id");
        newtrait->next_trait_uid    = result[currentrow].GetUInt32("next_trait");
        newtrait->raceID            = result[currentrow].GetUInt32("race_id");
        newtrait->name              = result[currentrow]["name"];        
        newtrait->cstr_id_mesh      = result[currentrow].GetUInt32("cstr_id_mesh");        
        newtrait->cstr_id_material  = result[currentrow].GetUInt32("cstr_id_material");
        newtrait->cstr_id_texture   = result[currentrow].GetUInt32("cstr_id_texture");
        newtrait->onlyNPC           = result[currentrow].GetInt("only_npc") != 0;
        newtrait->shaderVar         = result[currentrow]["shader"];

        psRaceInfo * raceInfo = GetRaceInfoByID(newtrait->raceID);
        if (raceInfo == NULL)
        {
            Error3("Trait (%u) references unresolvable race  %s.",
                newtrait->uid,result[currentrow]["race_id"]);
            delete newtrait;
            continue;
        }

        newtrait->race = raceInfo->race;
        newtrait->gender = raceInfo->gender;
        
        loc=ConvertTraitLocationString(result[currentrow]["location"]);
        if (loc==PSTRAIT_LOCATION_NONE)
        {
            Error3("Trait (%u) references unresolvable location  %s.",
                newtrait->uid,result[currentrow]["location"]);
            delete newtrait;
            continue;
        }
        newtrait->location = loc;

        traitlist.Push(newtrait);
    }
    // Update cross ref to next_trait
    for (size_t i = 0; i < traitlist.Length(); i++)
    {
         unsigned int next_uid = traitlist[i]->next_trait_uid;
         traitlist[i]->next_trait = GetTraitByID(next_uid);
    }



    Notify2( LOG_STARTUP, "%d Traits Loaded", traitlist.Length() );
    return true;
}

// TODO: This should be done faster, probably not with an array
psTrait *CacheManager::GetTraitByID(unsigned int id)
{
    size_t i;
    psTrait *currenttrait;

    for (i=0;i<traitlist.Length();i++)
    {
        currenttrait=traitlist.Get(i);
        if (currenttrait!=NULL && currenttrait->uid==id)
            return currenttrait;
    }
    return NULL;
}

// TODO: This should be done faster, probably not with an array
psTrait *CacheManager::GetTraitByName(const char *name)
{
/*
    int i;
    psTrait *currenttrait;

    for (i=0;i<traitlist.Length();i++)
    {
        currenttrait=traitlist.Get(i);
        if (currenttrait!=NULL && currenttrait->name == name)
            return currenttrait;
    }
    */
    return NULL;
}

CacheManager::TraitIterator CacheManager::GetTraitIterator()
{
    return traitlist.GetIterator();
}


PSCHARACTER_GENDER CacheManager::ConvertGenderString(const char *genderstring)
{
    if (genderstring==NULL)
        return PSCHARACTER_GENDER_NONE;

    switch (genderstring[0])
    {
    case 'M':
    case 'm':
        return PSCHARACTER_GENDER_MALE;
    case 'F':
    case 'f':
        return PSCHARACTER_GENDER_FEMALE;
    case 'N':
    case 'n':
        return PSCHARACTER_GENDER_NONE;
    };

    return PSCHARACTER_GENDER_NONE;
}


bool CacheManager::PreloadRaceInfo()
{
    unsigned int currentrow;
    psRaceInfo *newraceinfo;
    Result result(db->Select("SELECT * from race_info"));

    if (!result.IsValid())
        return false;


    for (currentrow=0; currentrow<result.Count(); currentrow++)
    {
        newraceinfo=new psRaceInfo;

    if (newraceinfo->Load(result[currentrow]))
        raceinfolist.Push(newraceinfo);
    else
    {
        delete newraceinfo;
    }
    }
    return true;
}


psRaceInfo *CacheManager::GetRaceInfoByIndex(int idx)
{
    if (idx<0 || (size_t)idx>=raceinfolist.Length())
        return NULL;

    return raceinfolist.Get(idx);
}


// TODO: This should be done faster, probably not with an array
psRaceInfo *CacheManager::GetRaceInfoByID(unsigned int id)
{
    size_t i;
    psRaceInfo *currentri;

    for (i=0;i<raceinfolist.Length();i++)
    {
        currentri=raceinfolist.Get(i);
        if (currentri && currentri->uid==id)
            return currentri;
    }
    return NULL;
}

// TODO: This should be done faster, probably not with an array
psRaceInfo *CacheManager::GetRaceInfoByNameGender(const char *name,PSCHARACTER_GENDER gender)
{
    size_t i;
    psRaceInfo *currentri;

    for (i=0;i<raceinfolist.Length();i++)
    {
        currentri=raceinfolist.Get(i);
        if (currentri!=NULL && currentri->gender==gender && currentri->name ==name)
            return currentri;
    }
    return NULL;
}

// TODO: This should be done faster, probably not with an array
psRaceInfo *CacheManager::GetRaceInfoByNameGender( PSCHARACTER_RACE id, PSCHARACTER_GENDER gender)
{
    size_t i;
    psRaceInfo *currentri;

    for (i=0;i<raceinfolist.Length();i++)
    {
        currentri=raceinfolist.Get(i);
        if (currentri!=NULL && 
            (PSCHARACTER_RACE)currentri->race==id && 
            currentri->gender==gender)
            return currentri;
    }
    return NULL;
}

psItemCategory *CacheManager::GetItemCategoryByID(unsigned int id)
{
    size_t i;
    for (i=0;i<itemCategoryList.Length();i++)
    {
        psItemCategory *currentCategory;
        currentCategory=itemCategoryList.Get(i);
        if (currentCategory && id==currentCategory->id)
            return currentCategory;
    }
    return NULL;

}

psItemCategory *CacheManager::GetItemCategoryByName(const csString & name)
{
    size_t i;
    for (i=0;i<itemCategoryList.Length();i++)
    {
        psItemCategory *currentCategory;
        currentCategory=itemCategoryList.Get(i);
        if (currentCategory && name==currentCategory->name)
            return currentCategory;
    }
    return NULL;

}

// TODO:  This function needs to be implemented in a fast fashion
psWay *CacheManager::GetWayByID(unsigned int id)
{
    size_t i;
    for (i=0;i<wayList.Length();i++)
    {
        psWay *currentWay;
        currentWay=wayList.Get(i);
        if (currentWay && id==currentWay->id)
            return currentWay;
    }
    return NULL;
}

// TODO:  This function needs to be implemented in a fast fashion
psWay *CacheManager::GetWayByName(const csString & name)
{
    size_t i;
    for (i=0;i<wayList.Length();i++)
    {
        psWay *currentWay;
        currentWay=wayList.Get(i);
        if (currentWay && name==currentWay->name)
            return currentWay;
    }
    return NULL;
}


// TODO:  This function needs to be implemented in a fast fashion
psSpell *CacheManager::GetSpellByID(unsigned int id)
{
    size_t i;
    for (i=0;i<spellList.Length();i++)
    {
        psSpell *currentSpell;
        currentSpell=spellList.Get(i);
        if (currentSpell && id==(unsigned int)currentSpell->GetID())
            return currentSpell;
    }
    return NULL;
}

// TODO:  This function needs to be implemented in a fast fashion
psSpell *CacheManager::GetSpellByName(const csString & name)
{    
    size_t i;
    for (i=0;i<spellList.Length();i++)
    {
        psSpell *currentSpell;
        currentSpell=spellList.Get(i);
        if (currentSpell && name.CompareNoCase(currentSpell->GetName()))
            return currentSpell;
    }
    return NULL;
}

CacheManager::SpellIterator CacheManager::GetSpellIterator()
{
    return spellList.GetIterator();
}


// Get item basic stats by hashed table
psItemStats *CacheManager::GetBasicItemStatsByName(csString name)
{
    return itemStats_NameHash.Get(name,NULL);
/*
    size_t i;
    for (i=0;i<basicitemstatslist.Length();i++)
    {
        psItemStats *currentitem;
        currentitem=basicitemstatslist.Get(i);
        if (currentitem && currentitem->GetName()!=NULL && !strcasecmp(name,currentitem->GetName()))
            return currentitem;
    }
    return NULL;
*/
}

// Get item basic stats by hashed table
psItemStats *CacheManager::GetBasicItemStatsByID(uint32 id)
{
    return itemStats_IDHash.Get(id,NULL);
/*
    size_t i;
    for (i=0;i<basicitemstatslist.Length();i++)
    {
        psItemStats *currentitem;
        currentitem=basicitemstatslist.Get(i);
        if (currentitem && id==currentitem->GetUID())
            return currentitem;
    }
    return NULL;
*/
}

void CacheManager::GetTipByID(int id, csString& tip)
{
    if ((size_t)id>=tips_list.Length())
    {
        tip = "";
        return;
    }        

    tip = tips_list.Get(id);
}

unsigned int CacheManager::GetTipLength()
{
    return (unsigned int)tips_list.Length();
}

/*
psItemStats *CacheManager::GetBasicItemStatsByIndex(unsigned int idx)
{
    if (idx>(unsigned int)basicitemstatslist.Length())
        return NULL;
    return basicitemstatslist.Get(idx);
}
*/

const char* CacheManager::Attribute2String( PSITEMSTATS_STAT s )
{
    switch ( s )
    {
        case PSITEMSTATS_STAT_STRENGTH:
            return "STRENGTH";            
        case PSITEMSTATS_STAT_AGILITY:
            return "AGILITY";
        case PSITEMSTATS_STAT_ENDURANCE:
            return "ENDURANCE";
        case PSITEMSTATS_STAT_INTELLIGENCE:
            return "INTELLIGENCE";
        case PSITEMSTATS_STAT_WILL:
            return "WILL";
        case PSITEMSTATS_STAT_CHARISMA:
            return "CHARISMA";
        default:
            return "None";
    }
}

PSITEMSTATS_STAT CacheManager::ConvertAttributeString(const char *attributestring)
{
    if (attributestring==NULL)
        return PSITEMSTATS_STAT_NONE;

    if (!strcasecmp(attributestring,"STRENGTH") || !strcasecmp(attributestring,"STR"))
        return PSITEMSTATS_STAT_STRENGTH;
    if (!strcasecmp(attributestring,"AGILITY") || !strcasecmp(attributestring,"AGI"))
        return PSITEMSTATS_STAT_AGILITY;
    if (!strcasecmp(attributestring,"ENDURANCE") || !strcasecmp(attributestring,"END") )
        return PSITEMSTATS_STAT_ENDURANCE;
    if (!strcasecmp(attributestring,"INTELLIGENCE") || !strcasecmp(attributestring,"INT"))
        return PSITEMSTATS_STAT_INTELLIGENCE;
    if (!strcasecmp(attributestring,"WILL") || !strcasecmp(attributestring,"WIL"))
        return PSITEMSTATS_STAT_WILL;
    if (!strcasecmp(attributestring,"CHARISMA") || !strcasecmp(attributestring,"CHA"))
        return PSITEMSTATS_STAT_CHARISMA;
    return PSITEMSTATS_STAT_NONE;
}


PSSKILL CacheManager::ConvertSkillString(const char *skillstring)
{
    if (skillstring==NULL)
        return PSSKILL_NONE;

    psSkillInfo *skillinfo=GetSkillByName(skillstring);

    if (skillinfo==NULL)
        return PSSKILL_NONE;
    return skillinfo->id;
}

PSSKILL CacheManager::ConvertSkill(int skill_id)
{
    if (skill_id >= (int)PSSKILL_NONE && skill_id < (int)PSSKILL_COUNT)
    {
        return (PSSKILL)skill_id;
    }

    return PSSKILL_NONE;
}

bool CacheManager::PreloadItemCategories()
{
    Result categories(db->Select("SELECT * from item_categories"));
    
    if (categories.IsValid())
    {
        int i,count=categories.Count();

        for (i=0;i<count;i++)
        {
            psItemCategory *category = new psItemCategory;

            category->id                   = categories[i].GetInt("category_id");
            category->name                 = categories[i]["name"];
            category->repair_tool_stat_id  = categories[i].GetInt("item_stat_id_repair_tool");
            const char *flag = categories[i]["is_repair_tool_consumed"];
            category->repair_tool_consumed = (flag && flag[0]=='Y');
            category->repair_skill_id      = categories[i].GetInt("skill_id_repair");

            itemCategoryList.Push(category);
         }
    }
    return true;
}

bool CacheManager::PreloadWays()
{
    Result ways(db->Select("SELECT * from ways"));
    if (ways.IsValid())
    {
        int i,count=ways.Count();

        for (i=0;i<count;i++)
        {
            psWay *way = new psWay;
            way->id =   atoi(ways[i]["id"]);
            way->name = ways[i]["name"];
            if (way->name == "Crystal")
            {
                way->skill = PSSKILL_CRYSTALWAY;
                way->related_stat = PSSKILL_CHA;
            }
            else
            if (way->name == "Azure")
            {
                way->skill = PSSKILL_AZUREWAY;
                way->related_stat = PSSKILL_WILL;
            }
            else
            if (way->name == "Red")
            {
                way->skill = PSSKILL_REDWAY;
                way->related_stat = PSSKILL_WILL;
            }
            else
            if (way->name == "Dark")
            {
                way->skill = PSSKILL_DARKWAY;
                way->related_stat = PSSKILL_CHA;
            }
            else
            if (way->name == "Brown")
            {
                way->skill = PSSKILL_BROWNWAY;
                way->related_stat = PSSKILL_INT;
            }
            else
            if (way->name == "Blue")
            {
                way->skill = PSSKILL_BLUEWAY;
                way->related_stat = PSSKILL_INT;
            }
            else
            {
                Error2("Unknown WAY: %s",way->name.GetData());
            }

            wayList.Push(way);
        }
    }
    return true;
}

bool CacheManager::PreloadSpells()
{
    Result spells(db->Select("SELECT * from spells"));
    if (spells.IsValid())
    {
        int i,count=spells.Count();

        for (i=0;i<count;i++)
        {
            psSpell *spell = new psSpell;
            if (spell->Load(spells[i]))
                spellList.Push(spell);
            else
                delete spell;
        }
    }
    return true;
}

csPDelArray<psItemAnimation> *CacheManager::FindAnimationList(int id)
{
    for (size_t x=0; x<item_anim_list.Length(); x++)
    {
        if (item_anim_list[x]->Get(0)->id == id)
            return item_anim_list[x];
    }
    return NULL;
}

bool CacheManager::PreloadItemAnimList()
{
    unsigned int currentrow,lastarrayid=0;
    psItemAnimation *newitem;
    csPDelArray<psItemAnimation> *newarray;

    Result result(db->Select("SELECT * from item_animations order by id, min_use_level"));

    if (!result.IsValid())
        return false;

    for (currentrow=0; currentrow<result.Count(); currentrow++)
    {
        unsigned int id = result[currentrow].GetInt("id");
        if (id != lastarrayid) // new set
        {
            newarray = new csPDelArray<psItemAnimation>;
            item_anim_list.Push(newarray);
            lastarrayid = id;
        }
        newitem            = new psItemAnimation;
        newitem->id        = id;
        newitem->anim_name = FindCommonString( result[currentrow].GetInt("cstr_id_animation") );
        newitem->anim_id   = csInvalidStringID;
        newitem->flags     = result[currentrow].GetInt("type_flags");
        newitem->min_level_required = result[currentrow].GetInt("min_use_level");

        newarray->Push(newitem);
    }

    return true;
}

psItemSet *CacheManager::LoadWorldItems(psSectorInfo *sector,int &loadeditems)
{
    uint32 parentid;
    loadeditems=0;

    //Result result(sector ?
    //    db->Select("SELECT * from item_instances where loc_sector_id='%u' and char_id_owner=0 or char_id_owner is NULL or parent_item_id=!0",sector->uid) :        
    //    db->Select("SELECT * from item_instances where char_id_owner=0 or char_id_owner is NULL or parent_item_id=!0"));
    
    Result result(sector ?
        db->Select("SELECT * from item_instances where loc_sector_id='%u'",sector->uid) :        
        db->Select("SELECT * from item_instances where loc_sector_id is NOT NULL"));

        
    
    /*  Our approach here is to allocate item instance data for every item instance returned.
    *  On our first pass we do not resolve any parent item references.  Instead we store the parent id
    *  in an array.  
    *  On the second pass we resolve all parent references.
    *    A tree would be faster, but since this is only done on world start
    *   it's not too much of a big deal.
    */

    csArray<uint32> containers;
    psItemSet *itemset = new psItemSet;

    // Load items
    if ( result.IsValid() )
    {
        int i, count=result.Count();
      
        for (i=0;i<count;i++)
        {
            psItem *item;
            unsigned int stats_id=result[i].GetUInt32("item_stats_id_standard");
            psItemStats *stats= GetBasicItemStatsByID(stats_id);

            if (!stats)
            {
                Error3("Error in LoadWorldItems! Item instance %u could not load base stats %u. Check the item_stats_id_standard value for this item. Skipping.\n",
                    result[i].GetUInt32("id"), result[i].GetUInt32("item_stats_id_standard"));
                continue;
            }

            if (stats->GetIsGlyph())
            {
                item = new psGlyph();
            }
            else
            {
                item = new psItem();
            }

            if (!item->Load(result[i],parentid))
            {
                Error2("Error in LoadWorldItems! Item instance %u could not be loaded. Skipping.\n",result[i].GetUInt32("id"));
                delete item;
                continue;
            }
            item->SetCreator(ITEM_CACHE);
            
            // This item is in another item.  Update the parent item id list and then move on.
            if ( item->GetParentID() && !item->GetLocInParent() )
            {  
                // Field missing.  Needed for items in other items.
                Error2("Item with id %s has parent item without location in parent.\n",result[i]["id"]);
                delete item;
                continue;
            }            

            if ( item->GetIsContainer() )
            {
                containers.Push(item->GetUID());    
            }
            
            loadeditems++;
            itemset->Add(item,parentid);
        }
                
                
        for ( size_t z = 0; z < containers.Length(); z++ )
        {
            Result result( db->Select("SELECT * from item_instances where parent_container_id=%u",containers[z]));        
                
            if ( result.IsValid() )
            {
                int i, count=result.Count();
                                
                for (i=0;i<count;i++)
                {
                    psItem *item;
                    unsigned int stats_id=result[i].GetUInt32("item_stats_id_standard");
                    psItemStats *stats= GetBasicItemStatsByID(stats_id);

                    if (!stats)
                    {
                        Error3("Error in LoadWorldItems! Item instance %u could not load base stats %u. Check the item_stats_id_standard value for this item. Skipping.\n",
                            result[i].GetUInt32("id"), result[i].GetUInt32("item_stats_id_standard"));
                        continue;
                    }

                    if (stats->GetIsGlyph())
                    {
                        item = new psGlyph();
                    }
                    else
                    {
                        item = new psItem();
                    }

                    if (!item->Load(result[i],parentid))
                    {
                        Error2("Error in LoadWorldItems! Item instance %u could not be loaded. Skipping.\n",result[i].GetUInt32("id"));
                        delete item;
                        continue;
                    }
                    item->SetCreator(ITEM_CACHE);
            
                    // This item is in another item.  Update the parent item id list and then move on.
                    if ( item->GetParentID() && !item->GetLocInParent() )
                    {  
                        // Field missing.  Needed for items in other items.
                        Error2("Item with id %s has parent item without location in parent.\n",result[i]["id"]);
                        delete item;
                        continue;
                    }                                
                
            
                    loadeditems++;
                    itemset->Add(item,parentid);
                }                    
            }                
        }
        
        
        
        if (itemset->ResolveAllParents())
        {
            for (size_t i=0; i < itemset->GetSize(); i++)
                if (itemset->Get(i))
                    itemset->Get(i)->SetLoaded();

            return itemset;
        }
        else
            delete itemset;
    }
    return NULL;
}

bool CacheManager::PreloadItemStatsDatabase()
{
    uint32 currentrow;
    psItemStats *newitem;
    Result result(db->Select("SELECT * from item_stats where stat_type='B'"));

    if (!result.IsValid())
        return false;

    for (currentrow=0; currentrow<result.Count(); currentrow++)
    {
        newitem = new psItemStats;
        if (!newitem->ReadItemStats(result[currentrow])) {
            delete newitem;
            return false;
        } else
        {
            CS_ASSERT( newitem->GetUID() != 0 );
            
//            basicitemstatslist.Push(newitem);
            itemStats_IDHash.Put(newitem->GetUID(),newitem);
            itemStats_NameHash.Put(newitem->GetName(),newitem);
        }
    }
    return true;
}

psItemStats* CacheManager::CopyItemStats( uint32 id )
{
    csString sql;

    sql.Append("INSERT INTO `item_stats` ( stat_type, name, weight, visible_distance, size, container_max_size, valid_slots, flags, decay_rate, \
                item_skill_id_1, item_skill_id_2, item_skill_id_3, item_bonus_1_attr, item_bonus_2_attr, item_bonus_3_attr, item_bonus_1_max, \
                item_bonus_2_max, item_bonus_3_max, weapon_speed, weapon_dmg_slash, weapon_dmg_blunt, weapon_dmg_pierce, weapon_dmg_force, \
                weapon_dmg_fire, weapon_dmg_ice, weapon_dmg_air, weapon_dmg_poison, weapon_dmg_disease, weapon_dmg_holy, weapon_dmg_unholy, \
                weapon_dmg_pct_slash, weapon_dmg_pct_blunt, weapon_dmg_pct_pierce, weapon_dmg_pct_force, weapon_dmg_pct_fire, weapon_dmg_pct_ice, \
                weapon_dmg_pct_air, weapon_dmg_pct_poison, weapon_dmg_pct_disease, weapon_dmg_pct_holy, weapon_dmg_pct_unholy, weapon_penetration, \
                weapon_block_targeted, weapon_block_untargeted, weapon_counterblock, armor_hardness, armor_prot_slash, \
                armor_prot_blunt, armor_prot_pierce, armor_prot_force, armor_prot_fire, armor_prot_ice, armor_prot_air, \
                armor_prot_poison, armor_prot_disease, armor_prot_holy, armor_prot_unholy, cstr_id_gfx_mesh, cstr_id_gfx_icon, \
                cstr_id_gfx_texture, cstr_id_part, armorvsweapon_type, category_id, base_sale_price, item_type, \
                requirement_1_name, requirement_1_value, requirement_2_name, requirement_2_value, requirement_3_name, \
                requirement_3_value, item_type_id_ammo, spell_id_on_hit, spell_on_hit_prob, spell_id_feature, spell_feature_charges, \
                spell_feature_timing, item_anim_id, description, sound, item_max_quality, prg_evt_equip, prg_evt_unequip,illum_definition) ");
    sql.Append("SELECT stat_type, name, weight, visible_distance, size, container_max_size, valid_slots, flags, decay_rate, item_skill_id_1, \
               item_skill_id_2, item_skill_id_3, item_bonus_1_attr, item_bonus_2_attr, item_bonus_3_attr, item_bonus_1_max, item_bonus_2_max, \
               item_bonus_3_max, weapon_speed, weapon_dmg_slash, weapon_dmg_blunt, weapon_dmg_pierce, weapon_dmg_force, weapon_dmg_fire, weapon_dmg_ice, \
               weapon_dmg_air, weapon_dmg_poison, weapon_dmg_disease, weapon_dmg_holy, weapon_dmg_unholy, weapon_dmg_pct_slash, weapon_dmg_pct_blunt, \
               weapon_dmg_pct_pierce, weapon_dmg_pct_force, weapon_dmg_pct_fire, weapon_dmg_pct_ice, weapon_dmg_pct_air, weapon_dmg_pct_poison, \
               weapon_dmg_pct_disease, weapon_dmg_pct_holy, weapon_dmg_pct_unholy, weapon_penetration, weapon_block_targeted, weapon_block_untargeted, \
               weapon_counterblock, armor_hardness, armor_prot_slash, armor_prot_blunt, armor_prot_pierce, armor_prot_force, armor_prot_fire, \
               armor_prot_ice, armor_prot_air, armor_prot_poison, armor_prot_disease, armor_prot_holy, armor_prot_unholy, cstr_id_gfx_mesh, \
               cstr_id_gfx_icon, cstr_id_gfx_texture, cstr_id_part, armorvsweapon_type, category_id, base_sale_price, item_type, requirement_1_name, \
               requirement_1_value, requirement_2_name, requirement_2_value, requirement_3_name, requirement_3_value, item_type_id_ammo, spell_id_on_hit, \
               spell_on_hit_prob, spell_id_feature, spell_feature_charges, spell_feature_timing, item_anim_id, description, sound, item_max_quality, \
               prg_evt_equip, prg_evt_unequip,illum_definition FROM item_stats WHERE id = '%u'" );

    psItemStats *newItem;

    if ( db->Command( sql.GetData(), id ) == QUERY_FAILED) {
        Error2("Error while copying item stats for id %d.\n",id);
        return NULL;
    }

    id = db->GetLastInsertID();
    CS_ASSERT(id != 0);

    Result result( db->Select( "SELECT * from item_stats where id='%u'", id ) );

    if ( !result.IsValid() )
        return NULL;

    if ( result.Count() != 1 )
        return NULL;

    newItem = new psItemStats;
    if ( !newItem->ReadItemStats( result[0] ) )
    {
        delete newItem;
        return NULL;
    }
//    basicitemstatslist.Push( newItem );
    itemStats_IDHash.Put(newItem->GetUID(),newItem);
    itemStats_NameHash.Put(newItem->GetName(),newItem);
    return newItem;
}

bool CacheManager::PreloadBadNames()
{
    unsigned int currentRow;
    csString* name = NULL;

    Result result(db->Select("SELECT * from bad_names"));        

    if (!result.IsValid())
    {
        Error1("Couldn't load bad names!");
        return false;
    }

    for (currentRow=0; currentRow<result.Count(); currentRow++)
    {
        name = new csString;        
        *name = NormalizeCharacterName(result[currentRow]["name"]);
        
        bad_names.Push(name);
    }

    return true;
}

void CacheManager::AddBadName(const char* name)
{
    if(!name)
        return;
    csString* newname = new csString();
    *newname = NormalizeCharacterName(name);

    db->Command("INSERT INTO bad_names ( `name` ) VALUES ('%s')",newname->GetDataSafe());

    bad_names.Push(newname);
}

void CacheManager::DelBadName(const char* name)
{
    csString cname = NormalizeCharacterName(name);

    for(size_t i = 0; i < bad_names.Length();i++)
    {
        if(cname.CompareNoCase(*bad_names[i]))
        {
            bad_names.DeleteIndex(i);
            db->Command("DELETE FROM bad_names WHERE name='%s'",cname.GetData());
            return;
        }
    }
}

size_t CacheManager::GetBadNamesCount()
{
    return bad_names.Length();
}

const char* CacheManager::GetBadName(int pos)
{
     csString* string = bad_names.Get(pos);

     return string->GetData();
}

psAccountInfo *CacheManager::GetAccountInfoByID(unsigned int accountid)
{
    Result result(db->Select("SELECT * from accounts where id=%u",accountid));
    if (!result.IsValid() || result.Count()<1)
    {
        Warning3(LOG_CONNECTIONS,"Could not find account for id %u.  Error: %s",accountid,db->GetLastError());
        return NULL;
    }
    psAccountInfo *accountinfo=new psAccountInfo;
    if (accountinfo->Load(result[0]))
    return accountinfo;
    else
    {
    delete accountinfo;
    return NULL;
    }
}


psAccountInfo *CacheManager::GetAccountInfoByCharID(unsigned int charid)
{
    unsigned int accountid = db->SelectSingleNumber("SELECT account_id from characters where id=%u",charid);
    return GetAccountInfoByID( accountid );
}

psAccountInfo *CacheManager::GetAccountInfoByUsername(const char *username)
{
    iCachedObject *obj = RemoveFromCache(username);
    if (obj)
    {
        Notify2(LOG_CACHE, "Found account for %s in cache!", username);
        return (psAccountInfo *)obj->RecoverObject();
    }

    csString escape;
    db->Escape( escape, username );
    Result result(db->Select("SELECT * from accounts where username='%s'",escape.GetData()));
    if (!result.IsValid() || result.Count()<1)
    {
        Warning3(LOG_CONNECTIONS,"Could not find account for login %s.  Error: %s",username,db->GetLastError());
        return NULL;
    }
    psAccountInfo *accountinfo=new psAccountInfo;
    if (accountinfo->Load(result[0]))
        return accountinfo;
    else
    {
        delete accountinfo;
        return NULL;
    }
}


bool CacheManager::UpdateAccountInfo(psAccountInfo *ainfo)
{
    const char *fieldnames[]= {
        "username",
        "password",
        "last_login_ip",
        "security_level",
        "last_login"
    };
    char accountidstring[11];
    psStringArray fields;

    fields.Push(ainfo->username);
    fields.Push(ainfo->password);
    fields.Push(ainfo->lastloginip);
    fields.FormatPush("%d",ainfo->securitylevel);
    fields.Push(ainfo->lastlogintime);
    snprintf(accountidstring,11,"%u",ainfo->accountid);
    accountidstring[10]=0x00;

    if (!db->GenericUpdateWithID("accounts","id",accountidstring,fieldnames,fields))
    {
        Error3("Failed to update account %u. Error %s",ainfo->accountid,db->GetLastError());
        return false;
    }
    return true;
}

unsigned int CacheManager::NewAccountInfo(psAccountInfo *ainfo)
{
    const char *fieldnames[]= {
        "username",
        "password",
        "last_login_ip",
        "security_level"
    };
    psStringArray fields;

    fields.Push(ainfo->username);
    fields.Push(ainfo->password);
    fields.Push(ainfo->lastloginip);
    fields.FormatPush("%d",ainfo->securitylevel);

    unsigned int id=db->GenericInsertWithID("accounts",fieldnames,fields);

    if (id == 0)
    {
        Error3("Failed to create new account for user %s. Error %s",ainfo->username.GetData(),db->GetLastError());
        return false;
    }

    ainfo->accountid = id;
    return id;
}

psGuildInfo *CacheManager::FindGuild(unsigned int id)
{
    psGuildInfo *g = guildinfo_by_id.Get(id, 0);
    if (g)
        return g;

    // Load on demand if not found
    g = new psGuildInfo;
    if (g->Load(id))
    {
        guildinfo_by_id.Put(g->id,g);
        return g;
    }
    delete g;
    return NULL;
}

psGuildInfo * CacheManager::FindGuild(const csString & name)
{
    unsigned int id;
    csString escape;
    db->Escape( escape, name );
    Result result(db->Select("select id from guilds where name=\"%s\"", escape.GetData()) );
    if (!result.IsValid())
        return NULL;
    
    if (result.Count() == 0)
        return NULL;
    
    id = result[0].GetInt("id");
    
    return FindGuild(id);
}

bool CacheManager::CreateGuild(const char *guildname, Client *client)
{
    int leaderID = client->GetPlayerID();

    psGuildInfo *gi = new psGuildInfo(guildname,leaderID);

    if (!gi->InsertNew(leaderID))
    {
        delete gi;
        return false;
    }

    gi->AddNewMember(client->GetCharacterData(), MAX_GUILD_LEVEL);

    guildinfo_by_id.Put(gi->id,gi);
    return true;
}

void CacheManager::RemoveGuild(psGuildInfo *which)
{
    guildinfo_by_id.Delete(which->id,which);
    delete which;
}




psGuildAlliance * CacheManager::FindAlliance(unsigned int id)
{
    psGuildAlliance *a = alliance_by_id.Get(id, 0);
    if (a)
        return a;

    a = new psGuildAlliance();
    if (a->Load(id))
    {
        alliance_by_id.Put(id,a);
        return a;
    }
    delete a;
    return NULL;
}

bool CacheManager::CreateAlliance(const csString & name, psGuildInfo * founder, Client *client)
{
    psGuildAlliance *a = new psGuildAlliance(name);

    if (!a->InsertNew())
    {
        delete a;
        return false;
    }

    a->AddNewMember(founder);
    a->SetLeader(founder);
    
    alliance_by_id.Put(a->GetID(),a);
    return true;
}

bool CacheManager::RemoveAlliance(psGuildAlliance *which)
{
    alliance_by_id.Delete(which->GetID(),which);
    which->RemoveAlliance();
    delete which;
    return true;
}


float CacheManager::GetArmorVSWeaponResistance(const char* armor_type, const char* weapon_type)
{
    if(!weapon_type || !armor_type || strlen(armor_type) < 2)
        return 1.0f;

    // Loopide loop
    for(size_t i = 0;i < armor_vs_weapon.Length();i++)
    {
        ArmorVsWeapon* atbl = armor_vs_weapon.Get(i);
        if(!atbl)
            continue;

        if(atbl->weapontype == weapon_type)
        {
            // Get the float in our 2d array
            csString armor = armor_type;
            char lvl = armor.GetAt(1);
            int arrayLvl = 0;

            int cat = atoi(csString(armor.GetAt(0)));
            if(lvl == 'a')
                arrayLvl = 0;
            else if(lvl =='b')
                arrayLvl = 1;
            else if(lvl == 'c')
                arrayLvl = 2;
            else
                arrayLvl = 3;

            return atbl->c[cat-1][arrayLvl];
        }
    }
    
    // Not found
    Debug2(LOG_COMBAT,0,"Didn't find weapon %s in the armor vs weapon table, assuming value is 1.0\n",weapon_type);
    return 1.0f;
}

float CacheManager::GetArmorVSWeaponResistance(psItemStats* armor, psItemStats* weapon)
{
    if(!armor || !weapon)
        return 1.0f;

    // Get the values to use
    csString armorstr,weaponstr;
    armor->GetArmorVsWeaponType(armorstr);
    weapon->GetArmorVsWeaponType(weaponstr);

    return GetArmorVSWeaponResistance(armorstr,weaponstr);
}

void CacheManager::CacheCraftTransforms( psMsgCraftingInfo* tradePattern, int patternID, int groupID )
{
    /// Select all the items that are used in the transformations.        
    Result transItems(db->Select("SELECT * FROM trade_transformations where pattern_id=%d OR pattern_id=%d", patternID, groupID));
    
    for ( int currentTrans = 0; currentTrans < (int)transItems.Count(); currentTrans++ )
    {            
        // Don't load the garbage trasfromations for the message
        int reqItem = transItems[currentTrans].GetInt("item_id");
        if( reqItem != 0)
        {
            psMsgCraftingInfo::CraftingItem* trans = new psMsgCraftingInfo::CraftingItem;
            int resultItem = transItems[currentTrans].GetInt("result_id");
            
            Result equipName(db->Select("SELECT name from item_stats where id=%d", transItems[currentTrans].GetInt("equipment_id")));
            if ( equipName.IsValid() && equipName.Count() > 0 )
            {
                trans->requiredEquipment = equipName[0]["name"];                        
            }
            
            Result workItemName(db->Select("SELECT name from item_stats where id=%d", transItems[currentTrans].GetInt("workitem_id")));
            if ( workItemName.IsValid() && workItemName.Count() > 0 )
            {
                trans->requiredWorkItem = workItemName[0]["name"];                        
            }
            
            
            Result itemName(db->Select("SELECT name from item_stats where id=%d", resultItem) );
            if ( itemName.IsValid() && itemName.Count() > 0 )
            {
                csString name = itemName[0]["name"];
                trans->name = name;
                trans->count = 0;       
            
                psMsgCraftingInfo::CraftingItem* subtrans = new psMsgCraftingInfo::CraftingItem;            
                Result reqItemName(db->Select("SELECT name from item_stats where id=%d", reqItem) );
                if ( reqItemName.IsValid() && reqItemName.Count()>0 )
                {
                    name = reqItemName[0]["name"];
                    subtrans->name = name;                     
                    subtrans->count = transItems[currentTrans].GetInt("item_qty");
                    trans->subItems.Push( subtrans );                    
                    tradePattern->items.Push( trans );
                } else {
                    Error2("CacheManager::CacheCraftTransforms - Failed to load item_stats for item %d.",reqItem);
                    delete trans;
                }
            } else {
                delete trans;
            }
        }
    }                
}


void CacheManager::CacheCraftCombos( psMsgCraftingInfo* tradePattern, int patternID, int groupID )
{                        
    /// Select all the items that are used in the combination of this item.        ToDo:  ordering the results by item_id will speedup/simplify match
    Result combItems(db->Select("SELECT distinct result_id FROM trade_combinations where pattern_id=%d OR pattern_id=%d", patternID, groupID));
    
    if (combItems.IsValid())
    {    
        
        for ( int currentCombo = 0; currentCombo < (int)combItems.Count(); currentCombo++ )
        {
            uint32 resultItem = combItems[currentCombo].GetUInt32("result_id");
            Result itemName(db->Select("SELECT name from item_stats where id=%d", resultItem) );
            if ( itemName.IsValid())
            {
                if (itemName.Count()==0)
                {
                  Error2("CacheManager::CacheCraftCombos - Failed to load item_stats for item %d.",resultItem);
                  continue;
                }
                
                csString name = itemName[0]["name"];

                psMsgCraftingInfo::CraftingItem* comb = new psMsgCraftingInfo::CraftingItem;
                comb->name = name;
                comb->count = 0;                    
            
                /// Get all the items involved in creating this item.             
                Result requiredItems(db->Select("SELECT * FROM `trade_combinations` where result_id=%d", resultItem));
                if ( !requiredItems.IsValid() )
                {
                    Error2("CacheManager::CacheCraftCombinations - Failed to load trade_combinations for item %d.",resultItem);
                    delete comb;
                    return;
                }
            
                for ( int currentReqItem = 0; currentReqItem < (int)requiredItems.Count(); currentReqItem++ )    
                {
                    psMsgCraftingInfo::CraftingItem* reqItem = new psMsgCraftingInfo::CraftingItem;
                    uint32 reqID = requiredItems[currentReqItem].GetUInt32("item_id");
                    Result itemName(db->Select("SELECT name from item_stats where id=%d", reqID) );
                    if ( !itemName.IsValid() || itemName.Count() == 0)
                    {
                        Error2("CacheManager::CacheCraftCombinations - Failed to load item_stats for item %d.",reqID);
                        delete comb;
                        delete reqItem;
                        return;
                    }
                
                    csString name = itemName[0]["name"];
                    reqItem->name = name;
                    reqItem->count = requiredItems[currentReqItem].GetInt("min_qty");
                    comb->subItems.Push( reqItem );                            
                }                                
                tradePattern->items.Push( comb );
            }       
        }                
    }            
}

void CacheManager::PreloadUpdateInfo()
{
    csRef<iVFS> vfs = CS_QUERY_REGISTRY (psserver->GetObjectReg(), iVFS);
    CS_ASSERT(vfs != NULL);

    csRef<iDataBuffer> data = vfs->ReadFile("/this/version.dat");
    if (!data)
    {
        Warning1(LOG_ANY,"Missing /this/version.dat!  Cannot send update notifications to clients.");
        updateTimeStamp = 0;
        return;
    }

    // The first 32 characters are the version stamp.
    // We want the time stamp after it.
    csString tmp(data->GetData());
    tmp.DeleteAt(0,33);
    updateTimeStamp = atoi( tmp.GetDataSafe() );
}

const char *CacheManager::MakeCacheName(const char *prefix, uint32 id)
{
    sprintf(CacheNameBuffer,"%.5s%u",prefix,id);
    return CacheNameBuffer;
}

void CacheManager::AddToCache(iCachedObject *obj, const char *name, int max_cache_time_seconds)
{
    Debug4(LOG_CACHE,0,"Now adding object <%s:%p> to cache for %d seconds.",name,obj,max_cache_time_seconds);

    // Set up the object which the event trigger will use to recover pointers or cancel itself.
    CachedObject *newRecord = new CachedObject;

    newRecord->name = name;
    newRecord->object = obj;

    // Now create the timed event which will make the cache clean it up later
    psCacheExpireEvent *evt = new psCacheExpireEvent(max_cache_time_seconds*1000, newRecord);

    // Setting this enables removal from cache to clear this event
    newRecord->event = evt;

    // Now queue it
    psserver->GetEventManager()->Push(evt);

    // Ensure no duplicate keys in generic cache, and clean up after self if found
    iCachedObject *oldObject = RemoveFromCache(newRecord->name);

    if (oldObject)  
    {
        oldObject->ProcessCacheTimeout(); // Make sure object knows it is gone
        oldObject->DeleteSelf();
    }

    // Now add the new one back in
    generic_object_cache.Put(newRecord->name, newRecord);
}

iCachedObject *CacheManager::RemoveFromCache(const char *name)
{
    CachedObject *oldRecord = generic_object_cache.Get(name, NULL);
    if (oldRecord)
    {
        oldRecord->event->CancelEvent();
        generic_object_cache.DeleteAll(oldRecord->name);
        iCachedObject *save = oldRecord->object;
        delete oldRecord;
        Notify2(LOG_CACHE,"Found object in cache and returning ptr %p.",save);
        return save;
    }
    Debug2(LOG_CACHE,0,"Object <%s> not found in cache.",name);
    return NULL;
}

CacheManager::psCacheExpireEvent::psCacheExpireEvent(int delayticks,CachedObject *object)
: psGameEvent(0,delayticks,"psCacheExpireEvent")
{
    valid = true;
    myObject = object;
}

void CacheManager::psCacheExpireEvent::Trigger()
{
    if (valid)
    {
        Debug2(LOG_CACHE,0,"Deleting object <%s> from cache due to timeout.",myObject->name.GetDataSafe());
        // Notify object it is going away
        myObject->object->ProcessCacheTimeout();
        // Delete the underlying object
        myObject->object->DeleteSelf();
        // Now remove the record from the cache last
        CacheManager::GetSingleton().RemoveFromCache(myObject->name);
    }
}
