/*
 * psitem.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 <csutil/stringarray.h>
#include <physicallayer/entity.h>
#include <propclass/mesh.h>

#include "util/log.h"
#include "util/psstring.h"
#include "util/serverconsole.h"

#include "psitem.h"
#include "pscharacter.h"
#include "pssectorinfo.h"
#include "../playergroup.h"
#include "../globals.h"
#include "../psserver.h"
#include "../entitymanager.h"
#include "util/eventmanager.h"
#include "util/psdatabase.h"
#include "../cachemanager.h"
#include "../gem.h"
#include "../events.h"
#include "../spawnmanager.h"
#include "pstrade.h"
#include "psmerchantinfo.h"

#if SAVE_DEBUG
#include <typeinfo>
#endif

#if SAVE_TRACER
#include <csutil/callstack.h>
#endif


/**
 * This class handles auto-removal of transient objects
 * (objects placed in the world by players, basically).
 */
class psItemRemovalEvent : public psGameEvent
{
protected:
    PS_ID item_to_remove;

public:
    psItemRemovalEvent(int delayticks,PS_ID gemID)    
        : psGameEvent(0,delayticks*1000,"psItemRemovalEvent")
    {
        item_to_remove = gemID;
    }


    virtual void Trigger()
    {
        printf("Removing object %u now.\n",item_to_remove);
        // cannot store pointer because object may have already been removed and ptr not valid
        gemObject *obj = psserver->GetEntityManager()->GetGEM()->FindObject(item_to_remove);
        if (obj)
        {
            psItem *item = obj->GetItem();
            if (item)
            {
                psserver->GetEntityManager()->RemoveActor(obj);
                item->Destroy();  // obj is deleted in RemoveActor
            }
        }
    }
};

/*  psContainerIterator
 *
 *
*/

psContainerIterator::psContainerIterator(psItem *container)
{
    UseContainerItem(container);
}


psContainerIterator::~psContainerIterator()
{

}

bool psContainerIterator::HasNext()
{
    if (current_container && next_position!=PSITEM_MAX_CONTAINER_SLOTS)
        return true;
    return false;
}

psItem *psContainerIterator::Next()
{
    psItem *nextitem;
    if (!current_container || next_position==PSITEM_MAX_CONTAINER_SLOTS)
        return NULL;

    nextitem=current_container->container_data.contained_item_ptr[next_position];

    // Set the next_position marker to the next valid contained item, or PSITEM_MAX_CONTAINER_SLOTS if no items
    for (next_position++;next_position<PSITEM_MAX_CONTAINER_SLOTS;next_position++)
    {
        if (current_container->container_data.contained_item_ptr[next_position]!=NULL)
            break;
    }

    return nextitem;
}

void psContainerIterator::UseContainerItem(psItem *container)
{
    current_container=container;
    // Set the next_position marker to the next valid contained item, or PSITEM_MAX_CONTAINER_SLOTS if no items
    for (next_position=0;next_position<PSITEM_MAX_CONTAINER_SLOTS;next_position++)
    {
        if (current_container->container_data.contained_item_ptr[next_position]!=NULL)
            break;
    }
}


// Definition of the itempool for psItems
PoolAllocator<psItem> psItem::itempool;

void *psItem::operator new(size_t allocSize)
{
    CS_ASSERT(allocSize<=sizeof(psItem));
    return (void *)itempool.CallFromNew();
}

void psItem::operator delete(void *releasePtr)
{
    itempool.CallFromDelete((psItem *)releasePtr);
}


// These are declared static in the class to ensure that there is only 1 instance in the entire system
csRef<csMutex> psItem::global_itemid_mutex=NULL;
uint32 psItem::global_itemid=0;
csRef<csMutex> psItem::global_uniqueitemid_mutex=NULL;
uint32 psItem::global_uniqueitemid=0;


psItem::psItem() : gItem(NULL), transformationEvent(NULL), loaded(false), pendingsave(false)
{
    int i;

    uid=0;
    creator=0;
//    parentid=0;
    loc_in_parent=0;
    sum_weight=0.0f;
    item_in_use=false;

    for (i=0;i<PSITEM_MAX_CONTAINER_SLOTS;i++)
        container_data.contained_item_ptr[i]=NULL;
        
    decay_resistance = 0;
    item_quality = 0;

    stack_count=1; // There will allways be one of a item.
                   // The GetIsStackable is to be used to check if this item
                   // is stackable.
    flags      = 0;
    crafter_id = 0;
    guild_id   = 0;
    container_parent=NULL;
//    container_in_location=(unsigned int)-1;
    base_stats=NULL;
    current_stats=NULL;
    owning_character=NULL;
    owningCharacterID = 0;

    location.loc_sectorinfo=NULL;
    location.loc_x=0.0f;
    location.loc_y=0.0f;
    location.loc_z=0.0f;
    location.loc_yrot=0.0f;

    for (i=0;i<PSITEM_MAX_MODIFIERS;i++)
        modifiers[i]=NULL;

    lockStrength = 0;
    lockpickSkill = PSSKILL_NONE;    
    schedule = NULL;
    patternID = -1;
    
    self_reference.AttachNew(new psItemSafe(this));
}

psItem::~psItem()
{
    //printf("In item %s:%u dtor...\n", GetName(), uid);

    if (!current_stats)
        return;

    if (item_in_use)
    {
        Error2("Item %s is being deleted while in use!\n", GetName() );
    }

    if(schedule)
    {
        delete schedule; // Finally delete the pattern used for spawning this item
        schedule = NULL;
    }

    // Destroy contained items first
    if (current_stats != NULL)  // if our item wasn't properly initialised and this attribute wasn't set, don't continue so we won't crash
    {
        if (GetIsContainer())
        {
            unsigned int slot;
            for (slot=0;slot<PSITEM_MAX_CONTAINER_SLOTS;slot++)
            {
                if (container_data.contained_item_ptr[slot]!=NULL)
                {
                    delete container_data.contained_item_ptr[slot];
                    container_data.contained_item_ptr[slot]=NULL;
                }
            }
        }
    }

    // If this is a uniq item delete the item state
    if (base_stats!=NULL && flags & PSITEM_FLAG_UNIQUE_ITEM )
    {
        delete base_stats;
        base_stats = NULL;
        flags |= PSITEM_FLAG_USES_BASIC_ITEM;
        flags &= ~PSITEM_FLAG_UNIQUE_ITEM;
    }
    
	
    if (item_quality != item_quality_original )
    {

        UpdateItemQuality(uid, item_quality);
    }

    self_reference->item = NULL;  // Not valid to use this object anymore

    if (gItem)
        gItem->UnregisterCallback(this);
    gItem = NULL;
}

char * psItem::GetCreatorName()
{
    switch(creator)
    {
        case ITEM_DB_LOAD : return "created by Load()";
        case ITEM_CLONE : return "created by Copy()";
		case ITEM_CACHE : return "created by cacheManager with Load()";  //created by CacheManager
        case ITEM_MONEY : return "created by psserverchar::MakeMoneyItem";   //created by psserverchar::MakeMoneyItem
        case ITEM_BASIC : return "created by psItemStats::InstantiateBasicItem";  //created by psitemstats::instantiateBasicItem
        default : return "some unknown code";
    }
}
void psItem::UpdateItemQuality(uint32 id, float qual)
{
    if (!id || id==ID_DONT_SAVE_ITEM)
        return;

    Debug3(LOG_USER,id,"UpdateItemQuality(%u,%1.2f)\n",id, qual);
    int ret = db->Command("update item_instances set item_quality=%1.2f where id=%u",qual, id);
    if (ret == 0)
    {
        Error3("Could not update item quality.  SQL was <%s> and error was <%s>",db->GetLastQuery(),db->GetLastError());
    }
}


// Functions that manipulate psItem Data

bool psItem::Load(iResultRow& row,uint32 &parentid)
{
    // Begin filling in the item properties
    // Item Unique ID #
    SetUID(row.GetUInt32("id"));
	CS_ASSERT(uid != 0);
    parentid = row.GetUInt32("parent_item_id");
    loc_in_parent = row.GetInt("location_in_parent");

    // Stack count (will be 0 if NULL - either means this is a non stackable item)
    stack_count=(unsigned short)row.GetInt("stack_count");

    if (row.GetInt("creator_mark_id"))
        SetCrafterID(row.GetInt("creator_mark_id"),false);
    if (row.GetInt("guild_mark_id"))
        SetGuildID(row.GetInt("guild_mark_id"),false);
    if (row.GetFloat("decay_resistance"))
        SetDecayResistance(row.GetFloat("decay_resistance"),false);
    else
        SetDecayResistance(0,false);

    // Flags
    psString flagstr(row["flags"]);
    if (flagstr.FindSubString("LOCKED",0,true)!=-1)
    {
        flags |= PSITEM_FLAG_LOCKED;
    }
    if (flagstr.FindSubString("LOCKABLE",0,true)!=-1)
    {
        flags |= PSITEM_FLAG_LOCKABLE;
    }
    if (flagstr.FindSubString("KEY",0,true)!=-1)
    {
        flags |= PSITEM_FLAG_KEY;
    }
    if (flagstr.FindSubString("PURIFIED",0,true)!=-1)
    {
       flags |= PSITEM_FLAG_PURIFIED;
    }
    if (flagstr.FindSubString("PURIFYING",0,true)!=-1)
    {
       flags |= PSITEM_FLAG_PURIFIED;
    }
    if (flagstr.FindSubString("NOPICKUP",0,true)!=-1)
    {
       flags |= PSITEM_FLAG_NOPICKUP;
    }
    if (flagstr.FindSubString("TRANSIENT",0,true)!=-1)
    {
       flags |= PSITEM_FLAG_TRANSIENT;
    }


    // Lockpick stuff
    SetLockStrength(row.GetInt("lock_str"));
    SetLockpickSkill((PSSKILL)row.GetInt("lock_skill"));

    // load openableLocks
    psString olstr(row["openable_locks"]);
    psString w;
    olstr.GetWordNumber(1, w);
    for (int n = 2; w.Length(); olstr.GetWordNumber(n++, w))
    {
        if (w == "SKEL")
            openableLocks.Push(KEY_SKELETON);
        else 
        {
            unsigned int u;
            sscanf(w.GetData(), "%u", &u);
            openableLocks.Push(u);
        }
    }

    unsigned int stats_id=row.GetUInt32("item_stats_id_standard");
    psItemStats *stats=CacheManager::GetSingleton().GetBasicItemStatsByID(stats_id);
    if (!stats)
    {
        Error3("Item with id %s has unresolvable basic item stats id %u",row["id"],stats_id);
        return false;
    }
    SetBaseStats(stats);

    if (row.GetFloat("item_quality"))
    {
        SetItemQuality(row.GetFloat("item_quality"),false);
    }
    else
    {
        SetItemQuality(GetMaxItemQuality(),false);
    }
        
    if ( row.GetUInt32("char_id_owner") == 0 )
    {
        psSectorInfo *itemsector=CacheManager::GetSingleton().GetSectorInfoByID(row.GetInt("loc_sector_id"));
        if (!itemsector && row.GetUInt32("parent_item_id") == 0 )
        {
            csString error;
            error.Format("Item %s(%s) Could not be loaded\nIt is in sector id %s which does not resolve\n",
               GetName(), row["id"], row["loc_sector_id"] );
            Error1( error );   
            return false;
        }

        float x,y,z,yrot;
        x = row.GetFloat("loc_x");
        y = row.GetFloat("loc_y");
        z = row.GetFloat("loc_z");
        yrot = row.GetFloat("loc_yrot");

        SetLocationInWorld(itemsector,x,y,z,yrot);
    }
    else
    {
        owningCharacterID = row.GetUInt32("char_id_owner");
    }        

    // TODO:Modifiers loaded and resolved later

    // Unique item handling, stats specified are deltas to standard stats
    stats_id = row.GetUInt32("item_stats_id_unique");

    if (stats_id)
    {
        Result result(db->Select("SELECT * from item_stats where id=%u",stats_id));
        if (!result.IsValid())
        {
            Error3("Item with id %s has unresolvable unique item stats id %u",row["id"],stats_id);
            return false;
        }

        psItemStats *stats=new psItemStats;
        if (!stats->ReadItemStats(result[0]))
        {
            Error3("Item with id %s has unique item stats that cannot be parsed (unique item stats id %u)",row["id"],stats_id);
            delete stats;
            return false;
        }
        SetUniqueStats(stats);
    }

    psTradePatterns* pattern = CacheManager::GetSingleton().GetTradePatternByItemID( GetCurrentStats()->GetUID() );
	
    if(pattern)
        patternID = pattern->GetId();
	SetCreator(ITEM_DB_LOAD);
	//Loaded(); // we really should be loaded after Load(), eh?
    return true;
}

void psItem::Save()
{
    if (loaded && !pendingsave)
    {
        #if SAVE_TRACER
            csCallStack* stack = csCallStackHelper::CreateCallStack(0,true);
            if (stack)
            {
                /// Store the function that queued this save (check this with a debugger if Commit() fails)
                last_save_queued_from = stack->GetEntryAll(1,true);
                
                #if SAVE_DEBUG
                    printf("\n%s::Save() for '%s', queued from stack:\n", typeid(*(GetSafeReference()->item)).name(), GetName() );
                    stack->Print();
                    printf("\n");
                #endif

                stack->Free();
            }
            else
            {
                Bug2("Could not get call stack for %p!",this);
                last_save_queued_from = "ERROR:  csCallStackHelper::CreateCallStack(0,true) returned NULL!";
            }
        #elif SAVE_DEBUG
            printf("%s::Save() for '%s' queued\n", typeid(*(GetSafeReference()->item)).name(), GetName() );
        #endif

        // Queue this item to save in 500ms
        psserver->GetEventManager()->Push(new psDelaySave(500,GetSafeReference()));
	    pendingsave = true;
    }

    #if SAVE_DEBUG
        else if (loaded) printf("%s::Save() for '%s' skipped\n", typeid(*(GetSafeReference()->item)).name(), GetName() );
    #endif
}

void psItem::Commit()
{
    if (!pendingsave || uid == ID_DONT_SAVE_ITEM)
        return;

    pendingsave = false;

    if (!loaded)
        return;

    psStringArray fields;

    if (GetUID()==0)
    {
        // New Item, need a UID
        const char *fieldnames[]= {
            "id",
            "char_id_owner",
             "stack_count",
             "item_quality",
             "decay_resistance",
             "creator_mark_id",
             "guild_mark_id",
             "flags",
             "item_stats_id_standard",
             "item_stats_id_unique",
             "location",
             "parent_item_id",
             "location_in_parent",
             "equipped_slot",
             "loc_x",
             "loc_y",
             "loc_z",
             "loc_yrot",
             "loc_sector_id",
             "lock_str",
             "lock_skill",
             "openable_locks"
        };

        // Get a unique ID for this item.
        SetUID(GetNextItemUID());

        // Error getting a unique ID, this should never happen
        CS_ASSERT_MSG("GetNextItemUID() returned 0!",GetUID()!=0);
        if (GetUID()==0)
            return;

        // We have to add the id to the fields before we add the rest of the field values
        fields.FormatPush("%u",GetUID());

        GetFieldArray(fields);

        // TODO: FIXME: BUG: We should check the return below, but since we can only return a 32 bit value on an insert
        //  we will get every possible value back at different times for success.  We need a GenericInsert (without ID) 
        //  that returns a bool.
        // This can only return a 32 bit value      
        db->GenericInsertWithID("item_instances",fieldnames,fields);

        item_quality_original = item_quality;
    }
    else
    {
        // Existing Item, update
        const char *fieldnames[]= {
             "char_id_owner",
             "stack_count",
             "item_quality",
             "decay_resistance",
             "creator_mark_id",
             "guild_mark_id",
             "flags",
             "item_stats_id_standard",
             "item_stats_id_unique",
             "location",
             "parent_item_id",
             "location_in_parent",
             "equipped_slot",
             "loc_x",
             "loc_y",
             "loc_z",
             "loc_yrot",
             "loc_sector_id",
             "lock_str",
             "lock_skill",
             "openable_locks"
        };

        GetFieldArray(fields);

        // Save this entry
        csString uid;
        uid.Format("%u",GetUID());
        if ( !db->GenericUpdateWithID("item_instances","id",uid,fieldnames,fields) )
        {
            Error2("Failed to save item instance %u!", GetUID() );
        }
        else
            item_quality_original = item_quality;
    }
}

void psItem::GetFieldArray(psStringArray& fields)
{
    // Owning character ID
    fields.FormatPush("%u",owning_character?owning_character->GetCharacterID():0);

    // Stack count
    fields.FormatPush("%u",GetStackCount());

    // Item quality
    fields.FormatPush("%1.2f",GetItemQuality());

    fields.FormatPush("%1.2f",GetDecayResistance());

    // Crafter ID
    if (GetIsCrafterIDValid())
        fields.FormatPush("%u",GetCrafterID());
    else
        fields.Push(NULL);

    // Guild ID
    if (GetIsGuildIDValid())
        fields.FormatPush("%u",GetGuildID());
    else
        fields.Push(NULL);

    // Flags
    csString flagString = "";

    // Thise two are actualy glyhs things and should be moved to psGlyph
    // if a generic way of updating flags are implemented.
    if (flags & PSITEM_FLAG_PURIFIED)
    {
        if (flagString != "") flagString.Append(",");
        flagString.Append("PURIFIED");
    }
    if (flags & PSITEM_FLAG_PURIFYING)
    {
        if (flagString != "") flagString.Append(",");
        flagString.Append("PURIFYING");
    }
    if (flags & PSITEM_FLAG_LOCKED)
    {
        if (flagString != "") flagString.Append(",");
        flagString.Append("LOCKED");
    }
    if (flags & PSITEM_FLAG_LOCKABLE)
    {
        if (flagString != "") flagString.Append(",");
        flagString.Append("LOCKABLE");
    }
    if (flags & PSITEM_FLAG_KEY)
    {
        if (flagString != "") flagString.Append(",");
        flagString.Append("KEY");
    }
    if (flags & PSITEM_FLAG_NOPICKUP)
    {
        if (flagString != "") flagString.Append(",");
        flagString.Append("NOPICKUP");
    }
    if (flags & PSITEM_FLAG_TRANSIENT)
    {
        if (flagString != "") flagString.Append(",");
        flagString.Append("TRANSIENT");
    }

    fields.Push(flagString);

    // item_stats_id_standard - base stats if non unique, unique stats if unique
    fields.FormatPush("%u",GetBaseStats()->GetUID());

    // item_stats_id_unique
    fields.FormatPush("%u",0);

    // Owner stuff
    psCharacter *owner=GetOwningCharacter();
    bool equipped = false;
    int slotlocation = 0;

    // location
    if (!owner)
    {
        if ( GetParentItem() )
        {
            fields.Push("C");
        }
        else
        {
            fields.Push(NULL);
        }
    }
    else
    {
        // Find the root item, this makes location searches faster
        psItem *rootitem=this;
        while (rootitem->GetParentItem()!=NULL)
            rootitem=rootitem->GetParentItem();

        // Find the general area
        if ( GetParentItem() )
        {
            fields.Push("C");
        }
        else if ((slotlocation=(int)owner->Inventory().FindItemInTopLevelEquipment(rootitem))!=-1)
        {
            equipped=true;
            fields.Push("E");
        }
        else if ((slotlocation=owner->Inventory().FindItemInTopLevelBulk(rootitem))!=-1)
        {
            fields.Push("I");
        }
        else
            CS_ASSERT(!"Attempted to save item in NULL location.");
    }

    // Container stuff
    if (!GetParentItem())
    {
        fields.Push(NULL);
        
        if ( owner )
        {
            if (!equipped)
            {
                fields.FormatPush("%d",slotlocation);
                fields.Push(NULL);
            }
            else
            {
                fields.Push(NULL);
                fields.FormatPush("%s",CacheManager::GetSingleton().slotName.GetName(slotlocation));
            }
        }
        else
        {
            fields.Push(NULL);
            fields.Push(NULL);
        }
    }
    else
    {
        fields.FormatPush("%u",GetParentItem()->GetUID());
        fields.FormatPush("%u",GetLocInParent());
        fields.Push("0");
    }

    if (owner || GetParentItem())
    {
        fields.Push(NULL);
        fields.Push(NULL);
        fields.Push(NULL);
        fields.Push(NULL);
        fields.Push(NULL);
    }
    else  // Item is not held or in something; must be in the world
    {
        float locx,locy,locz,locyrot;
        psSectorInfo *sectorinfo;

        GetLocationInWorld(&sectorinfo,locx,locy,locz,locyrot);
        if ( sectorinfo )
        {
            fields.FormatPush("%1.2f",locx);
            fields.FormatPush("%1.2f",locy);
            fields.FormatPush("%1.2f",locz);
            fields.FormatPush("%1.2f",locyrot);
            fields.FormatPush("%u",sectorinfo->uid);
        }
        else  //  Item is nowhere; cannot be saved
        {
            Error3("Item %s(%u) could not be saved because it is in a null location and has no parent or character owner", GetName(), GetUID() );
            CS_ASSERT(!"Attempt to save item without a parent in a NULL position");
        }
    }

    fields.FormatPush("%d",GetLockStrength());
    fields.FormatPush("%d",(int)GetLockpickSkill());

    // push openableLocks
    csString openableLocksString = "";
    csArray<unsigned int>::Iterator iter = openableLocks.GetIterator();
    while(iter.HasNext())
    {
        csString tmp;
        unsigned int n = iter.Next();
        if (openableLocksString != "") openableLocksString.Append(",");
        if (n == KEY_SKELETON)
            openableLocksString.Append("SKEL");
        else 
        {
            tmp.Format( "%u", n);
            openableLocksString.Append(tmp);
        }
    }
    fields.Push(openableLocksString);
}

void psItem::ForceSaveIfNew()
{
    if (GetUID()==0)
    {
        // No UID. Force a save now to generate one.
        pendingsave = true;
        Commit();
    }
    else
    {
        // Already saved. Queue normal save.
        Save();
    }
}

bool psItem::GetIsCrafterIDValid()
{
    return (flags & PSITEM_FLAG_CRAFTER_ID_IS_VALID);
}

void psItem::SetIsCrafterIDValid(bool v)
{
    if (v)
        flags=flags | PSITEM_FLAG_CRAFTER_ID_IS_VALID;
    else
        flags=flags & ~PSITEM_FLAG_CRAFTER_ID_IS_VALID;
}

bool psItem::GetIsGuildIDValid()
{
    return (flags & PSITEM_FLAG_GUILD_ID_IS_VALID) ? true : false;
}

void psItem::SetIsGuildIDValid(bool v)
{
    if (v)
        flags=flags | PSITEM_FLAG_GUILD_ID_IS_VALID;
    else
        flags=flags & ~PSITEM_FLAG_GUILD_ID_IS_VALID;
}

bool psItem::GetIsUnique()
{
    return (flags & PSITEM_FLAG_UNIQUE_ITEM) ? true : false;
}

void psItem::SetUID(uint32 v)
{
    uid=v;
}

void psItem::SetStackCount(unsigned short v, bool save)
{
    // This adjusts the weight of parents as well
    AdjustSumWeight(current_stats->GetWeight() * (v-stack_count));
    stack_count=v;
    
    if (save)
        Save();
}

void psItem::SetCrafterID(unsigned int v,bool save)
{
    SetIsCrafterIDValid(true);
    crafter_id=v;
    if (save)
        Save();
}

void psItem::SetGuildID(unsigned int v,bool save)
{
    SetIsGuildIDValid(true);
    guild_id=v;
    if (save)
        Save();
}

float psItem::GetMaxItemQuality()
{
    return current_stats->GetQuality();
}

void psItem::SetMaxItemQuality(float v)
{
    base_stats->SetQuality(v);
}

float psItem::GetItemQuality()
{
    return item_quality;
}

void psItem::SetItemQuality(float v,bool save)
{
    item_quality = v;
    item_quality_original = v;

    if (save)
        Save();
}

void psItem::SetDecayResistance(float v,bool save)
{
    decay_resistance=v;
    if (save)
        Save();
}

float psItem::AddDecay(float severityFactor)
{
    if (!this) return 0;

    item_quality -= base_stats->GetDecayRate() * severityFactor * (1.0F-decay_resistance);
    if (item_quality < 0)
        item_quality = 0;

    // printf("Item %s quality decayed by %1.2f to %1.2f.\n", GetName(), base_stats->GetDecayRate()*severityFactor*(1.0F-decay_resistance), item_quality);
    return item_quality;
}

int psItem::GetRequiredRepairTool()
{
    if (GetCategory())
        return GetCategory()->repair_tool_stat_id;
    else
        return 0;
}

bool psItem::GetRequiredRepairToolConsumed()
{
    if (GetCategory())
        return GetCategory()->repair_tool_consumed;
    else
        return false;
}


void psItem::GetLocationInWorld(psSectorInfo **sectorinfo,float &loc_x,float &loc_y,float &loc_z,float &loc_yrot)
{
    *sectorinfo=location.loc_sectorinfo;
    loc_x=location.loc_x;
    loc_y=location.loc_y;
    loc_z=location.loc_z;
    loc_yrot=location.loc_yrot;
}

void psItem::SetLocationInWorld(psSectorInfo *sectorinfo,float loc_x,float loc_y,float loc_z,float loc_yrot)
{
    location.loc_sectorinfo=sectorinfo;
    location.loc_x=loc_x;
    location.loc_y=loc_y;
    location.loc_z=loc_z;
    location.loc_yrot=loc_yrot;
    Save();
}

csString psItem::GetQuantityName()
{
    psString name = GetName();
    return GetQuantityName(name,stack_count);
}

csString psItem::GetQuantityName(psString name, int stack_count)
{
    if (name.IsEmpty())
        return "???";

    csString list;
    if (stack_count == 1)
    {
        list.Format("%s %s", (name.IsVowel(0))?"an":"a", name.GetData() );
    }
    else
    {
        name.Plural(); // Get plural form of the name
        list.Format("%d %s", stack_count, name.GetData() );
    }

    return list;
}


void psItem::SetOwningCharacter(psCharacter *owner, bool save)
{
    int i;

    // No need to go through resetting all the owner pointers if they are already set
    if (owning_character==owner)
        return;

    owning_character=owner;
    if ( owner )
        owningCharacterID = owner->characterid;
    else
        owningCharacterID = 0;

    if (current_stats->GetIsContainer())
    {
        for (i=0;i<PSITEM_MAX_CONTAINER_SLOTS;i++)
        {
            if (container_data.contained_item_ptr[i]!=NULL)
                container_data.contained_item_ptr[i]->SetOwningCharacter(owner,true);
        }
    }
    
    // If this is NULL, we have to set a location to be valid; don't save here
    if (owner && save)
        Save();
}


bool psItem::GetIsContainerEmpty()
{
    if (!GetIsContainer())
        return true;

    for (int i=0;i<PSITEM_MAX_CONTAINER_SLOTS;i++)
    {
        if (container_data.contained_item_ptr[i]!=NULL)
            return false;
    }
    return true;
}

unsigned short psItem::GetContainedSize()
{
    if (!GetIsContainer())
        return 0;

    unsigned int size = 0;
    for (int i=0; i<PSITEM_MAX_CONTAINER_SLOTS; i++)
        if (container_data.contained_item_ptr[i]!=NULL)
            size += (unsigned int)container_data.contained_item_ptr[i]->GetSumSize();

    return size;
}


psItem *psItem::GetItemInSlot(unsigned int slot)
{
    if (!(current_stats->GetIsContainer()) || slot>=PSITEM_MAX_CONTAINER_SLOTS)
        return NULL;
    return container_data.contained_item_ptr[slot];
}



bool psItem::TakeOutOfContainer()
{
    unsigned int parent_idx;
    if (!container_parent)
        return false; // This object is not contained in any other object

    for (parent_idx=0;parent_idx<PSITEM_MAX_CONTAINER_SLOTS;parent_idx++)
    {
        if (container_parent->container_data.contained_item_ptr[parent_idx]==this)
        {
            // TODO: Adjust sum properties if need be - weight, etc
            container_parent->container_data.contained_item_ptr[parent_idx]=NULL;
            container_parent->AdjustSumWeight(-sum_weight);
            
            container_parent=NULL;
            owning_character=NULL;
            loc_in_parent=PSITEM_MAX_CONTAINER_SLOTS;

            // Cannot save this item because it is in a limbo state. The caller
            // must call save when the item is in a good state.
            //Save();
            // Save the parent in it's new state.
            return true;
        }
    }


    Error3("Item ID %u has parent with ID %u, but parent does not contain child.",GetUID(),container_parent->GetUID());

    SetOwningCharacter(NULL,true);

    return false; // The parent claims it does not contain this object?

}


bool psItem::AddItemToContainer(psItem *addme, psCharacter* useOwner)
{
    unsigned int location_idx;
    // Fail if this is not a container
    if (!(GetIsContainer()))
    {
        Error3( "%s(%u) is not a container", GetName(), GetUID() );
        return false;
    }        

    //we can't put item into itself
    if (addme==this  ||  IsIndirectParent(addme))
    {
        Error3( "Cannot add %s(%u) in itself", GetName(), GetUID() );     
        return false;
    }        

    // Check size
    if (GetContainerMaxSize() < addme->GetSize() || addme->GetSize()==65535)
    {
        csString error;
        error.Format( "Item %s(%u) will not fit inside container %s(%u)\nMax Size = %d Item Size = %d\n",
        addme->GetName(), addme->GetUID(), GetName(), GetUID(), GetContainerMaxSize(), addme->GetSize() );
        Error1( error );
        return false;
    }            
   
             

    // Remove item if it's in something else
    if (addme->container_parent)
    {
        if (!addme->TakeOutOfContainer())
        {
            Error3("Item %s(%u) could not be removed for it's other parent", GetName(), GetUID());
            return false; // Failed to remove from parent container
        }            
    }

    for (location_idx=0;location_idx<PSITEM_MAX_CONTAINER_SLOTS;location_idx++)
    {
        if (container_data.contained_item_ptr[location_idx]==NULL)
        {
            addme->container_parent=this;
            addme->loc_in_parent=location_idx;
            container_data.contained_item_ptr[location_idx]=addme;
            if ( useOwner )
                addme->SetOwningCharacter(useOwner,false);
            else
                addme->SetOwningCharacter(owning_character,false);
                            
            AdjustSumWeight(addme->GetSumWeight());

            addme->Save();
            return true;
        }
    }

    // No free space - try sub containers
    for (location_idx=0;location_idx<PSITEM_MAX_CONTAINER_SLOTS;location_idx++)
    {
        if (container_data.contained_item_ptr[location_idx]!=NULL && container_data.contained_item_ptr[location_idx]->GetIsContainer())
        {
            if (container_data.contained_item_ptr[location_idx]->AddItemToContainer(addme))
                return true;
        }
    }

    Error3("Item %s(%u) could not be added at all", addme->GetName(), addme->GetUID() );
    // Doesn't fit there either
    return false;
}

bool psItem::AddItemToContainer(psItem *addme,unsigned int slot)
{
    // Fail if this is not a container or slot is out of range
    if (!(GetIsContainer()) || slot>=PSITEM_MAX_CONTAINER_SLOTS)
    {
        Error3( "%s(%u) is not a container", GetName(), GetUID() );    
        return false;
    }

    //we can't put item into itself
    if (addme==this  ||  IsIndirectParent(addme))
    {
        Error3( "%s(%u) is not a container", GetName(), GetUID() );        
        return false;
    }

    // Check size
    if (GetContainerMaxSize() < addme->GetSize() || addme->GetSize()==65535)
    {
        csString error;
        error.Format( "Item %s(%u) will not fit inside container %s(%u)\nMax Size = %d Item Size = %d\n",
        addme->GetName(), addme->GetUID(), GetName(), GetUID(), GetContainerMaxSize(), addme->GetSize() );
        Error1( error );
     
        return false;
    }

    // Fail if the slot is not free and not stackable
    if (container_data.contained_item_ptr[slot]!=NULL)
    {
        csString error;
        error.Format("%s(%u) already has an item %s(%u) in slot %d.", GetName(), GetUID(),
        container_data.contained_item_ptr[slot]->GetName(), container_data.contained_item_ptr[slot]->GetUID(), slot ); 
        return false;
    }        

    // Remove item if it's in something else
    if (addme->container_parent)
    {
        if (!addme->TakeOutOfContainer())
        {
            Error3( "Item %s(%u) could not be removed for it's other parent", GetName(), GetUID());
            return false; // Failed to remove from parent container
        }            
    }

    addme->container_parent=this;
    addme->loc_in_parent=slot;
    container_data.contained_item_ptr[slot]=addme;

    addme->SetOwningCharacter(owning_character,false);
    AdjustSumWeight(addme->GetSumWeight());

    addme->Save();

    return true;
}

bool psItem::SwapItemsInContainer(unsigned int fromSlot, unsigned int toSlot)
{
    // Fail if this is not a container
    if (!GetIsContainer())
        return false;

    // Fail if fromSlot is out of range
    if (fromSlot>=PSITEM_MAX_CONTAINER_SLOTS)
        return false;

    // Fail if toSlot is out of range
    if (toSlot>=PSITEM_MAX_CONTAINER_SLOTS)
        return false;

    // swap 'em
    psItem* toItem = container_data.contained_item_ptr[toSlot];
    psItem* fromItem = container_data.contained_item_ptr[fromSlot];
    container_data.contained_item_ptr[toSlot] = fromItem;
    container_data.contained_item_ptr[fromSlot] = toItem;

    // only set items that exist
    if (toItem)
        toItem->SetLocInParent(fromSlot, true);
    if (fromItem)
        fromItem->SetLocInParent(toSlot, true);

    // commit it
    Save();
    return true;
}

bool psItem::MoveItemsInContainer(unsigned int fromSlot, unsigned int toSlot, int count)
{
    psItem * item;
    item = RemoveItemsInContainer(fromSlot, count);
    if (item == NULL)
        return false;

    // If the put fails put the item back
    if ( PutItemsInContainer(toSlot, item, owning_character) != PS_CONT_OK )
    {
        if ( PutItemsInContainer(fromSlot, item, owning_character) != PS_CONT_OK)
            Error1("psItem::MoveItemsInContainer - reinsertion failed");
    }

    return true;
}

psItem *psItem::RemoveItemsInContainer(unsigned int fromSlot, int count)
{
    // Fail if fromSlot is out of range
    if (fromSlot >= PSITEM_MAX_CONTAINER_SLOTS)
        return NULL;

    psItem* fromItem = container_data.contained_item_ptr[fromSlot];
    if (fromItem==NULL)
        return NULL;

    // Fail if move was more then current count
    if (count < 0 || count > fromItem->GetStackCount() )
        return NULL;

    // check if the item is pickable
    if (fromItem->flags & PSITEM_FLAG_NOPICKUP)
        return NULL;

    // Do full move
    if ( count == fromItem->GetStackCount() )
    {
        container_data.contained_item_ptr[fromSlot]=NULL;
        fromItem->container_parent = NULL;
        return fromItem;
    }
    // Do partial move
    else
    {
        psItem* newItem =  fromItem->SplitStack((unsigned short)count);
        newItem->container_parent = NULL;
        if (!newItem)
            return NULL;

        fromItem->Save();
        return newItem;
    }
    return NULL;
}

PS_CONTAINER_ERROR psItem::PutItemsInContainer(unsigned int toSlot,psItem *item, psCharacter* owner)
{
    // Fail if toSlot is out of range or item NULL
    if (toSlot>=PSITEM_MAX_CONTAINER_SLOTS)
        return PS_CONT_ERR_OUT_RANGE;
    if (item==NULL)
        return PS_CONT_ERR_NO_ITEM;

    //we can't put item into itself
    if (item==this  ||  IsIndirectParent(item))
        return PS_CONT_ERR_SELF;
                                                 
   if ( GetContainerMaxSize() < item->GetCurrentStats()->GetSize() )
    return PS_CONT_ERR_TO_BIG;
                             
        
    psItem* toItem = container_data.contained_item_ptr[toSlot];
   
    // If the slot is already occupied, we will try to stack items
    if (toItem!=NULL)
    {
        // Fail if toItem has other real owner
        if ((toItem->GetOwningCharacter() != owner) &&
            (toItem->GetOwningCharacter() != 0))
            return PS_CONT_ERR_NOT_OWNER;

        // Check if can't stack
        if (!item->CheckStackableWith(toItem))
            return PS_CONT_ERR_NO_STACK;

        // Combine the stacks.
        psItem *remainder_item = toItem->CombineStack(item);

        // Handle a remainder during the stacking attempt
        if (remainder_item) 
        {
            // Check if conbine stack returns passed pointer.
            CS_ASSERT(remainder_item==item);
            return PS_CONT_REMAINDER;
        }
    }

    // otherwise put the item
    else
    {
        container_data.contained_item_ptr[toSlot] = item;
        item->SetLocInParent( toSlot , false);
        //item->SetOwningCharacter( GetOwningCharacter() );
        if (owner) 
            item->SetOwningCharacter( owner ,false ); 
        item->SetParent( this );

        item->Save();
    }

    return PS_CONT_OK;
}

psItem * psItem::FindStackableAndStack(psItem *stackme)
{
    unsigned int location_idx;

    // If this isn't a container, call CombineStack which attempts to stack with just this item
    if (!GetIsContainer())
        return CombineStack(stackme);

    // Search through sub items
    for (location_idx=0;location_idx<PSITEM_MAX_CONTAINER_SLOTS;location_idx++)
    {
        if (container_data.contained_item_ptr[location_idx]!=NULL)
        {
            stackme = container_data.contained_item_ptr[location_idx]->FindStackableAndStack(stackme);

            if (!stackme)
                return NULL; // Fully stacked with a sub item
        }
    }
        
    // Finished without fully stacking
    return stackme;
}


float psItem::AdjustSumWeight(float delta)
{
    sum_weight += delta;

    if (container_parent != NULL)
        container_parent->AdjustSumWeight(delta);
    else if (owning_character != NULL && GetIsContainer())  // Top level container in character's inventory
        owning_character->Inventory().ReassessInventoryDimensions();  // Update inventory

    return sum_weight;
}

float psItem::RecalculateFullWeight()
{
    sum_weight=GetWeight();

    if (current_stats->GetIsContainer())
    {
        for (int i=0;i<PSITEM_MAX_CONTAINER_SLOTS;i++)
        {
            if (container_data.contained_item_ptr[i]!=NULL)
                sum_weight+=container_data.contained_item_ptr[i]->RecalculateFullWeight();
        }
    }
    return sum_weight;
}

void psItem::SetUniqueStats(psItemStats *statptr)
{
    // Consider weight changes
    float weight_delta=0.0f;

    // If base_stats is NULL then this is a new item. No chickens here, just us egg.
    if (base_stats!=NULL)
        weight_delta-=GetWeight();

    if (current_stats==base_stats)
        current_stats=statptr;

    base_stats=statptr;

    // Clear the "uses a stock basic item" flag and set the "uses a unique item stats entry" flag
    flags &= ~PSITEM_FLAG_USES_BASIC_ITEM;
    flags |= PSITEM_FLAG_UNIQUE_ITEM;


    if (current_stats!=base_stats)
        RecalcCurrentStats();

    weight_delta+=GetWeight();

    if (weight_delta!=0.0f)
        AdjustSumWeight(weight_delta);

    Save();
}



void psItem::SetBaseStats(psItemStats *statptr)
{
    // Consider weight changes
    float weight_delta=0.0f;

    // If base_stats is NULL then this is a new item. No chickens here, just us egg.
    if (base_stats!=NULL)
    {
        weight_delta-=GetWeight();
        // Consider the possibility of this previously being a unique item
        if (flags & PSITEM_FLAG_UNIQUE_ITEM)
        {
            // Note we delete here and then quickly reassign base_stats below after a pointer comparison
            delete base_stats;
            flags |= PSITEM_FLAG_USES_BASIC_ITEM;
            flags &= ~PSITEM_FLAG_UNIQUE_ITEM;
        }
    }

    // Consider quality
    SetItemQuality(statptr->GetQuality(),false);

    if (current_stats==base_stats)
        current_stats=statptr;

    base_stats=statptr;

    if (current_stats!=base_stats)
        RecalcCurrentStats();

    weight_delta+=GetWeight();

    if (weight_delta!=0.0f)
        AdjustSumWeight(weight_delta);

    Save();
}

void psItem::SetCurrentStats(psItemStats *statptr)
{
    current_stats=statptr;
    Save();
}

void psItem::RecalcCurrentStats()
{
    int i;
    bool has_modifiers=false;
    /* If there are no modifiers and no effects, then current_stats should equal base_stats.
     *  If the current stats pointer is not the same as the base stats or any of the modifiers
     *  then it may need adjustment.
     *
     */
    if (current_stats==base_stats)
        return;
    for (i=0;i<PSITEM_MAX_MODIFIERS;i++)
    {
        if (current_stats==modifiers[i])
            return;
        if (modifiers[i]!=NULL)
            has_modifiers=true;
    }



    // TODO:  When effects are added, take them into consideration here

    // TODO:  When modifiers are defined and the operations of how they apply to base stats is defined, implement that logic here.

    // For now, if there are no modifiers we presume that current_stats should = base_stats and that current_stats can be freed.
    if (!has_modifiers && current_stats!=NULL)
        delete current_stats;
    current_stats=base_stats;
}


bool psItem::HasModifier(psItemStats *modifier)
{
    int i;
    for (i=0;i<PSITEM_MAX_MODIFIERS;i++)
    {
        if (modifier==modifiers[i])
            return true;
    }
    return false;
}


bool psItem::AddModifier(psItemStats *modifier)
{
    int mod_number;

    for (mod_number=0;mod_number<PSITEM_MAX_MODIFIERS;mod_number++)
    {
        if (modifiers[mod_number]==NULL)
            break;
    }
    // No free modifier slots available
    if (mod_number==PSITEM_MAX_MODIFIERS)
        return false;

    // TODO:  We need to apply the modifier to the current stats of the item as well
    modifiers[mod_number]=modifier;

    /*  Modifiers can be applied to unique items but the effects of the modifier do not get rolled in.
     *  Instead, this allows modifiers to be used as a "flag" in some cases.
     *
     *
     */
    if (GetIsUnique())
        return true;

    /* if (current_stats==base_stats)
     * {
     *    // Create a new temporary stat entry that's a copy of the base stats
     *    current_stats=base_stats->CloneTemporary();
     * }
     * // Add this modifier's stats to the current_stats
     */

    return true;
}

psItemStats *psItem::GetModifier(int index)
{
    if (index<0 || index>=PSITEM_MAX_MODIFIERS)
        return NULL;
    return modifiers[index];
}


bool psItem::CheckStackableWith(psItem *otheritem)
{
    CS_ASSERT(otheritem != NULL);

    int i;
    // TODO:  Should unique items ever be stackable?
    if (GetIsUnique())
        return false;

    int purifyStatus = GetPurifyStatus();
    if (purifyStatus == 1)                      // purifying glyphs cannot be stacked
        return false;
    int otherPurifyStatus = otheritem->GetPurifyStatus();
    if (purifyStatus != otherPurifyStatus)      // glyphs with different purification status cannot be stacked
        return false;

    if (!GetIsStackable() || !otheritem->GetIsStackable())
        return false;

    /* Conditions that must be met for stacking:
     * 1) Base item stats point to the same entry
     * 2) If there are any modifiers they must point to the same entry in the same order
     * 3) No effects can be applied.
     * 4) Crafting attributes match
     */

    if (GetBaseStats()!=otheritem->GetBaseStats())
        // If these have different base stats it doesn't matter about modifiers - they cant be combined
        return false;

    // TODO: Instead of checking each modifier, we can compare the resulting stats and see if they are equivalent
    // This requires implementing a comparison function in psItemStats
    for (i=0;i<PSITEM_MAX_MODIFIERS;i++)
    {
        if (modifiers[i]!=otheritem->modifiers[i])
            return false;
    }

	//  Check some crafting stuff
	if (GetCrafterID() != otheritem->GetCrafterID())
		return false;
	if (GetGuildID() != otheritem->GetGuildID())
		return false;

    // TODO: Check effects

    if (GetCurrentStats()==otheritem->GetCurrentStats())
    {
        return true;
    }

    return false;
}

psItem *psItem::Copy(unsigned short newstackcount)
{
    psItem *newitem;

    // Cannot split into a stack of 0 or a stack of the same amount or more than there already are
    if (newstackcount < 1)
        return NULL;

    // Cannot copy unique items
    if (GetIsUnique())
        return NULL;
    
    // Allocate a new item
    newitem = CreateNew();
    Copy(newitem);

    newitem->SetStackCount(newstackcount);

    newitem->SetLoaded();  // Item is fully created

    return newitem;
}

void psItem::Copy(psItem * target)
{
    printf("Copying item '%s' clone it.  Owner is %s.\n", GetName(),
           owning_character ? owning_character->GetCharName() : "None" );

    // The location in world is the same
    target->SetLocationInWorld(location.loc_sectorinfo,location.loc_x,location.loc_y,location.loc_z,location.loc_yrot);
    // Base stats are the same
    target->SetBaseStats(GetBaseStats());
    
    // The decay is the same.
    target->SetDecayResistance(decay_resistance,false);

    // The quality is the same.
    target->SetItemQuality(item_quality,false);

    // The flags are the same
    target->flags = flags;

    // The crafter is the same.
    target->crafter_id = crafter_id;

    // The guild_id is the same.
    target->guild_id = guild_id;

    // Current stats are rebuilt;
    int i;
    for (i=0;i<PSITEM_MAX_MODIFIERS;i++)
    {
        if (modifiers[i]!=NULL)
            target->AddModifier(modifiers[i]);
    }
    target->SetOwningCharacter( owning_character ,true);
    SetCreator(ITEM_CLONE);
}

psItem *psItem::SplitStack(unsigned short newstackcount, bool save)
{
    // Cannot split into a stack of 0 or a stack of the same amount or more than there already are
    if (newstackcount<1 || newstackcount>=GetStackCount())
        return NULL;

    psItem *newitem = Copy(newstackcount);
    if (newitem == NULL)
        return NULL;

    // Adjust stack count of source
    SetStackCount(stack_count-newstackcount, save);

    printf("Split Stack to make a new stack of %d items.\n", newstackcount);

    return newitem;
}

psItem *psItem::CombineStack(psItem *& stackme, bool save)
{
    // Ensure stacking compatability.
    if (!CheckStackableWith(stackme))
        return stackme;

    // TODO: Implement maximum stack count here.
    // This just avoids an overflow at 65535 count.
    // We're working with an unsigned short so this is a bit funky. 
    // We can't just add and check <65536 because it could roll over during the add
    unsigned short maxmove = 65535 - stack_count;
    if (maxmove < stackme->GetStackCount())
    {
        // Move a partial stack
        stackme->SetStackCount(stackme->GetStackCount()-maxmove, save);
        SetStackCount(stack_count+maxmove, save);

        // Didn't completely move the old stack
        return stackme;
    }
    else
    {
        // If stacked item is from a spawn, we want to keep its spawning rules
        if (stackme->schedule)
        {
            if (!schedule)  // Absorb schedule
                SetScheduledItem( stackme->schedule );
            
            stackme->schedule = NULL; // Prevent deleting of shedule later in delete in RemoveInstance
        }
        
        // Average the qualities and set stack count
        int newStackCount = stack_count+stackme->GetStackCount();
        float newQuality = ((GetItemQuality()*GetStackCount())+(stackme->GetItemQuality()*stackme->GetStackCount()))/newStackCount;
        SetItemQuality(newQuality,save);
        SetStackCount(newStackCount, save);
        
        if (save)
        {
            CacheManager::GetSingleton().RemoveInstance(stackme);
        }
        else
        {
            delete stackme;  // Don't remove from DB if not saving
            stackme = NULL;
        }

        // Stacks completely merged
        return NULL;
    }
}



// Functions that call into the appropriate psItemStats object

int psItem::GetAttackAnimID(psCharacter *pschar,iCelEntity *entity)
{
    PSSKILL skill = current_stats->Weapon().Skill(PSITEMSTATS_WEAPONSKILL_INDEX_0);
    unsigned int curr_level = pschar->GetSkills()->GetSkillRank(skill);

    return current_stats->GetAttackAnimID(curr_level,entity);
}


bool psItem::GetIsMeleeWeapon()
{
    return current_stats->GetIsMeleeWeapon();
}

bool psItem::GetIsRangeWeapon()
{
    return current_stats->GetIsRangeWeapon();
}

bool psItem::GetIsAmmo()
{
    return current_stats->GetIsAmmo();
}

bool psItem::GetIsShield()
{
    return current_stats->GetIsShield();
}

bool psItem::GetIsContainer()
{
    return current_stats->GetIsContainer();
}

bool psItem::GetCanTransform()
{
    return current_stats->GetCanTransform();
}

bool psItem::GetUsesAmmo()
{
    return current_stats->GetUsesAmmo();
}

bool psItem::GetIsStackable()
{
    return current_stats->GetIsStackable();
}

const char *psItem::GetName()
{
    return current_stats->GetName();
}

const char *psItem::GetDescription()
{
    return current_stats->GetDescription();
}

PSITEMSTATS_WEAPONTYPE psItem::GetWeaponType()
{
    return current_stats->Weapon().Type();
}

PSSKILL psItem::GetWeaponSkill(PSITEMSTATS_WEAPONSKILL_INDEX index)
{
    return current_stats->Weapon().Skill(index);
}

float psItem::GetLatency()
{
    return current_stats->Weapon().Latency();
}


float psItem::GetDamage(PSITEMSTATS_DAMAGETYPE dmgtype)
{
    return current_stats->Weapon().Damage(dmgtype);
}

float psItem::GetExtraDamagePercent(PSITEMSTATS_DAMAGETYPE dmgtype)
{
    return current_stats->Weapon().ExtraDamagePercent(dmgtype);
}

PSITEMSTATS_AMMOTYPE psItem::GetAmmoType()
{
   return current_stats->Ammunition().AmmoType();
}

float psItem::GetPenetration()
{
    return current_stats->Weapon().Penetration();
}

float psItem::GetUntargetedBlockValue()
{
    return current_stats->Weapon().UntargetedBlockValue();
}

float psItem::GetTargetedBlockValue()
{
    return current_stats->Weapon().TargetedBlockValue();
}

float psItem::GetCounterBlockValue()
{
    return current_stats->Weapon().CounterBlockValue();
}

PSITEMSTATS_ARMORTYPE psItem::GetArmorType()
{
    return current_stats->Armor().Type();
}

float psItem::GetDamageProtection(PSITEMSTATS_DAMAGETYPE dmgtype)
{
    return current_stats->Armor().Protection(dmgtype);
}

float psItem::GetHardness()
{
    return current_stats->Armor().Hardness();
}

PSITEMSTATS_STAT psItem::GetWeaponAttributeBonusType(PSITEMSTATS_STAT_BONUS_INDEX index)
{
    return current_stats->Weapon().AttributeBonusType(index);
}

float psItem::GetWeaponAttributeBonus(PSITEMSTATS_STAT stat)
{
    if (GetWeaponAttributeBonusType(PSITEMSTATS_STAT_BONUS_INDEX_0)== stat)
        return GetWeaponAttributeBonusMax(PSITEMSTATS_STAT_BONUS_INDEX_0);

    else if (GetWeaponAttributeBonusType(PSITEMSTATS_STAT_BONUS_INDEX_1)== stat)
        return GetWeaponAttributeBonusMax(PSITEMSTATS_STAT_BONUS_INDEX_1);

    else if (GetWeaponAttributeBonusType(PSITEMSTATS_STAT_BONUS_INDEX_2)== stat)
        return GetWeaponAttributeBonusMax(PSITEMSTATS_STAT_BONUS_INDEX_2);
    else
        return 0.0F;
}

float psItem::GetWeaponAttributeBonusMax(PSITEMSTATS_STAT_BONUS_INDEX index)
{
    return current_stats->Weapon().AttributeBonusMax(index);
}

float psItem::GetWeight()
{
    if (GetIsStackable())
        return (current_stats->GetWeight() * GetStackCount());
    else
        return current_stats->GetWeight();
}

unsigned short psItem::GetSize()
{
    return current_stats->GetSize();
}

unsigned short psItem::GetContainerMaxSize()
{
    return current_stats->GetContainerMaxSize();
}

PSITEMSTATS_SLOTLIST psItem::GetValidSlots()
{
    return current_stats->GetValidSlots();
}

bool psItem::FitsInSlots(PSITEMSTATS_SLOTLIST slotmask)
{
    return current_stats->FitsInSlots(slotmask);
}

float psItem::GetDecayResistance()
{
    return decay_resistance;
}

psMoney& psItem::GetPrice()
{
    return current_stats->GetPrice();
}

psMoney psItem::GetSellPrice()
{
    // Merchants like 20% profit. So we take the total price,
    // multiply with 0.8 and make a new money with that amount
    // as trias.
    return psMoney (int (current_stats->GetPrice().GetTotal () * 0.8));
}

psItemCategory * psItem::GetCategory()
{
    return current_stats->GetCategory();
}

float psItem::GetVisibleDistance()
{
    return current_stats->GetVisibleDistance();
}



// Removed until future implementation to avoid confusion
/*
unsigned int psItem::GetMeshIndex()
{
    return current_stats->GetMeshIndex();
}

unsigned int psItem::GetTextureIndex()
{
    return current_stats->GetTextureIndex();
}

unsigned int psItem::GetTexturePartIndex()
{
    return current_stats->GetTexturePartIndex();
}

*/

const char *psItem::GetMeshName()
{
    return current_stats->GetMeshName();
}

const char *psItem::GetTextureName()
{
    return current_stats->GetTextureName();
}

const char *psItem::GetPartName()
{
    return current_stats->GetPartName();
}

const char *psItem::GetPartMeshName()
{
    return current_stats->GetPartMeshName();
}

const char *psItem::GetImageName()
{
    return current_stats->GetImageName();
}

const char *psItem::GetSound()
{
    return current_stats->GetSound();
}

uint32 psItem::GetNextItemUID()
{
    uint32 newid;

    if (global_itemid_mutex==NULL)
    {
        // This is the first call to initialize.  Setup the mutex and the global_itemid count
        global_itemid_mutex=csMutex::Create(false);
        CS_ASSERT(global_itemid_mutex!=NULL);
        if (global_itemid_mutex==NULL)
            return 0;
        if (global_itemid_mutex->LockWait())
        {
            Result result(db->Select("SELECT MAX(id) maxid from item_instances"));

            global_itemid=result[0].GetUInt32("maxid")+1;
            global_itemid_mutex->Release();
        }
    }

    if (global_itemid_mutex->LockWait())
    {
        newid=global_itemid++;
        global_itemid_mutex->Release();
        return newid;
    }
    CS_ASSERT(false); // Could not obtain mutex lock
    return 0;
}

uint32 psItem::GetNextUniqueItemStatsUID()
{
    uint32 newid;

    if (global_uniqueitemid_mutex==NULL)
    {
        // This is the first call to initialize.  Setup the mutex and the global_itemid count
        global_uniqueitemid_mutex=csMutex::Create(false);
        CS_ASSERT(global_uniqueitemid_mutex!=NULL);
        if (global_uniqueitemid_mutex==NULL)
            return 0;
        if (global_uniqueitemid_mutex->LockWait())
        {
            Result result(db->Select("SELECT MAX(id) maxid from item_stats"));

            global_uniqueitemid=result[0].GetUInt32("maxid")+1;
            global_uniqueitemid_mutex->Release();
        }
    }

    if (global_uniqueitemid_mutex->LockWait())
    {
        newid=global_uniqueitemid++;
        global_uniqueitemid_mutex->Release();
        return newid;
    }
    CS_ASSERT(false); // Could not obtain mutex lock
    return 0;
}

float psItem::GetArmorVSWeaponResistance(psItemStats* armor)
{
    return CacheManager::GetSingleton().GetArmorVSWeaponResistance(armor, current_stats);
}


double psItem::GetProperty(const char *ptr)
{
    if (!strcasecmp(ptr,"Skill1"))
    {
        return GetWeaponSkill((PSITEMSTATS_WEAPONSKILL_INDEX)0);
    }
    else if (!strcasecmp(ptr,"Skill2"))
    {
        return GetWeaponSkill((PSITEMSTATS_WEAPONSKILL_INDEX)1);
    }
    else if (!strcasecmp(ptr,"Skill3"))
    {
        return GetWeaponSkill((PSITEMSTATS_WEAPONSKILL_INDEX)2);
    }
    else if (!strcasecmp(ptr,"Quality"))
    {
        return GetItemQuality();
    }
    else if (!strcasecmp(ptr,"MaxQuality"))
    {
        return GetMaxItemQuality();
    }
    else if (!strcasecmp(ptr,"WeaponCBV"))
    {
        return GetCounterBlockValue();
    }
    else if (!strcasecmp(ptr,"Hardness"))
    {
        return GetHardness();
    }
    else if (!strcasecmp(ptr,"Penetration"))
    {
        return GetPenetration();
    }
    else if (!strcasecmp(ptr,"DamageSlash"))
    {
        return GetDamage(PSITEMSTATS_DAMAGETYPE_SLASH);
    }
    else if (!strcasecmp(ptr,"ProtectSlash"))
    {
        return GetDamageProtection(PSITEMSTATS_DAMAGETYPE_SLASH);
    }
    else if (!strcasecmp(ptr,"ExtraDamagePctSlash"))
    {
        return GetExtraDamagePercent(PSITEMSTATS_DAMAGETYPE_SLASH);
    }
    else if (!strcasecmp(ptr,"DamageBlunt"))
    {
        return GetDamage(PSITEMSTATS_DAMAGETYPE_BLUNT);
    }
    else if (!strcasecmp(ptr,"ProtectBlunt"))
    {
        return GetDamageProtection(PSITEMSTATS_DAMAGETYPE_BLUNT);
    }
    else if (!strcasecmp(ptr,"ExtraDamagePctBlunt"))
    {
        return GetExtraDamagePercent(PSITEMSTATS_DAMAGETYPE_BLUNT);
    }
    else if (!strcasecmp(ptr,"DamagePierce"))
    {
        return GetDamage(PSITEMSTATS_DAMAGETYPE_PIERCE);
    }
    else if (!strcasecmp(ptr,"ProtectPierce"))
    {
        return GetDamageProtection(PSITEMSTATS_DAMAGETYPE_PIERCE);
    }
    else if (!strcasecmp(ptr,"ExtraDamagePctPierce"))
    {
        return GetExtraDamagePercent(PSITEMSTATS_DAMAGETYPE_PIERCE);
    }
    else if (!strcasecmp(ptr,"StrMalus"))
    {
        return GetWeaponAttributeBonus(PSITEMSTATS_STAT_STRENGTH);
    }
    else if (!strcasecmp(ptr,"AgiMalus"))
    {
        return GetWeaponAttributeBonus(PSITEMSTATS_STAT_AGILITY);
    }
    else if (!strcasecmp(ptr,"Weight"))
    {
        return GetWeight();
    }
    else if (!strcasecmp(ptr,"MentalFactor"))
    {
        int temp = GetWeaponSkill((PSITEMSTATS_WEAPONSKILL_INDEX)0);
        return ( (double)CacheManager::GetSingleton().GetSkillByID((temp<0)?0:temp)->mental_factor / 100.0 );
    }
    else if (!strcasecmp(ptr,"RequiredRepairSkill"))
    {
        printf("%s=%d\n",ptr,base_stats->GetCategory()->repair_skill_id);
        return base_stats->GetCategory()->repair_skill_id;
    }
    else if (!strcasecmp(ptr,"SalePrice"))
    {
        printf("%s=%d\n",ptr,base_stats->GetPrice().GetTotal());
        return base_stats->GetPrice().GetTotal();
    }
    else
    {
        CPrintf(CON_ERROR, "psItem::GetProperty(%s) failed\n",ptr);
        return 0;
    }
}

void psItem::SetIsLocked(bool v)
{
    if (v)
        flags=flags | PSITEM_FLAG_LOCKED;
    else
        flags=flags & ~PSITEM_FLAG_LOCKED;

    Save();
}

void psItem::SetIsLockable(bool v)
{
    if (v)
        flags=flags | PSITEM_FLAG_LOCKABLE;
    else
        flags=flags & ~PSITEM_FLAG_LOCKABLE;

    Save();
}

void psItem::SetIsKey(bool v)
{
    if (v)
        flags = flags | PSITEM_FLAG_KEY;
    else
        flags = flags & ~PSITEM_FLAG_KEY;

    Save();
}

void psItem::SetLockpickSkill(PSSKILL v)
{
    lockpickSkill = v;
    Save();
}

void psItem::SetLockStrength(unsigned int v)
{
    lockStrength = v;
    Save();
}

bool psItem::CanOpenLock(uint32 id)
{
    return openableLocks.Find(id) != csArrayItemNotFound || openableLocks.Find(KEY_SKELETON) != csArrayItemNotFound;
}

void psItem::AddOpenableLock(uint32 v)
{
    if (openableLocks.Find(v) == csArrayItemNotFound)
        openableLocks.Push(v);

    Save();
}

void psItem::MakeSkeleton(bool b)
{
    if (b)
        AddOpenableLock(KEY_SKELETON);
    else
        RemoveOpenableLock(KEY_SKELETON);
}

bool psItem::GetIsSkeleton()
{
    return openableLocks.Find(KEY_SKELETON) != csArrayItemNotFound;
}

void psItem::RemoveOpenableLock(uint32 v)
{
    size_t n = openableLocks.Find(v);
    if (n != csArrayItemNotFound)
        openableLocks.DeleteIndexFast(n);

    Save();
}

void psItem::ClearOpenableLocks()
{
    openableLocks.DeleteAll();
    Save();
}

void  psItem::SetLocInParent(unsigned int location, bool save)
{
    loc_in_parent = location;
    if (save)
      Save();
}

void psItem::SetParent( psItem* item )
{
    container_parent = item;
    Save();
}

bool psItem::IsIndirectParent(psItem * item)
{
    psItem * parent = container_parent;

    while (parent != NULL   &&   parent != item)
        parent = parent->container_parent;

    return parent == item;
}

/********************************************************/
psItemSet::~psItemSet()
{
  while (set.Length())
    delete set.Pop();
}

void psItemSet::Add(psItem *item,uint32 parentid)
{
    set.Push(item);
    parents.Push(parentid);
}

bool psItemSet::ResolveAllParents()
{
    // Resolve parent-child relationships
    for (size_t i=0; i<set.Length(); i++)
    {
        if (set[i]!=NULL && parents[i]!=0)
        {
            bool success = false;
            bool found = false;
            for (size_t pidx=0; pidx<set.Length(); pidx++)
            {
                if (set[pidx]!=NULL && set[pidx]->GetUID()==parents[i])
                {
                    found = true;
                    success = set[pidx]->AddItemToContainer(set[i],set[i]->GetLocInParent() );

                    if (!success)
                    {
                        Error6("Item %s(%u) could not be added inside it's parent %s(%u) in location %d\n",
                            set[i]->GetName(), set[i]->GetUID(), set[pidx]->GetName(), set[pidx]->GetUID(), set[i]->GetLocInParent() );
                    }

                    // On to the next unresolved item
                    break;
                }
            }
            // Delete item because it could not be resolved
            if(!success)
            {
                if(!found)
                    Error3("Could not find parent for item %s(%u)\n", set[i]->GetName(), set[i]->GetUID());
                delete set[i];
                set[i] = NULL;
            }
        }
    }
    return true;
}

void psItemSet::Release()
{
    set.DeleteAll();    // Empty the set. This will not delete the items themselves.
    parents.DeleteAll();
}

psItem *psItemSet::Get(size_t n)
{
    if (n < set.Length())
        return set.Get(n);
    else
        return NULL;
}


void psItem::SetIsPickupable(bool v)
{
    if (!v)
        flags=flags | PSITEM_FLAG_NOPICKUP;
    else
        flags=flags & ~PSITEM_FLAG_NOPICKUP;

    Save();
}

void psItem::SetIsTransient(bool v)
{
    if (!v)
        flags=flags | PSITEM_FLAG_TRANSIENT;
    else
        flags=flags & ~PSITEM_FLAG_TRANSIENT;

    Save();
}

bool psItem::CheckRequirements( psCharacter* charData, csString& resp )
{
    return base_stats->CheckRequirements( charData, resp );
}

void psItem::ItemAboutToMove()
{
    ScheduleRespawn();
}

void psItem::ScheduleRespawn()
{
    if(!schedule)
        return;

    // Transfer the spawn rules to the new item
    if(!schedule->WantToDie()) // removed?
    {
        psItemSpawnEvent* event = new psItemSpawnEvent(schedule);
        psserver->GetEventManager()->Push(event);
    }
    else
    {
        // Want to die, delete it
        delete schedule;
    }

    // Remove this shedule for this item, since we don't want an item in for example
    // the inventory to call respawn when it's dropped and picked up again
    schedule = NULL;
}

psScheduledItem::psScheduledItem(int id,uint32 itemID,csVector3& position, psSectorInfo* sector, int interval,int maxrnd)
{
    spawnID = id;
    this->itemID = itemID;
    this->pos = position;
    this->sector = sector;
    this->interval = interval;
    this->maxrnd = maxrnd;
    wantToDie= false;
    printf("Item spawn (%u) created\n",itemID);
}

psItem* psScheduledItem::CreateItem() // Spawns the item
{
    if(wantToDie)
        return NULL;

    Notify2(LOG_SPAWN,"Spawning item (%u)\n",itemID);

    psItemStats *stats = CacheManager::GetSingleton().GetBasicItemStatsByID(itemID);
    if (stats==NULL)
    {
        Error2("Could not find basic stats with ID %u for item spawn.\n",itemID);
    }
    else
    {
        psItem *item = stats->InstantiateBasicItem();
        if (item)
        {
            // Create the item
            item->SetLocationInWorld(GetSector(),GetPosition().x, GetPosition().y, GetPosition().z, 0);
            if ( !psserver->GetEntityManager()->CreateItem(item,false) )
            {
                delete item;
                return NULL;
            }

            // Transfer the spawning rules for it to pass it forward
            psScheduledItem* newsch = new psScheduledItem(*this);
            item->SetScheduledItem(newsch);

            lastSpawn = csGetTicks();

            item->SetLoaded();  // Item is fully created
            item->Save();    // First save
            return item;
        }
    }
    return NULL;
}

void psScheduledItem::UpdatePosition(csVector3& position, const char *sector)
{
    if(wantToDie)
        return;

    pos = position;
    db->Command("UPDATE hunt_locations SET x='%f', y='%f', z='%f', sector='%s' WHERE id='%d'",
                pos.x,pos.y,pos.z,sector,spawnID);
}

void psScheduledItem::ChangeIntervals(int newint, int newrand)
{
    if(wantToDie)
        return;

    interval = newint;
    maxrnd = newrand;
    db->Command("UPDATE hunt_locations SET interval='%d', max_random='%d' WHERE id='%d'",
                interval,maxrnd,spawnID);
}

void psScheduledItem::Remove()
{
    db->Command("DELETE FROM hunt_locations WHERE id='%d'",spawnID);
    wantToDie = true;
}

int psScheduledItem::MakeInterval()
{
    int rnd = (int)psserver->GetRandom(maxrnd);
    return interval + rnd;
}

bool psItem::Destroy()
{
//    csString itemid;
    if (this->GetIsContainer())
    {
        for (int i=0;i<PSITEM_MAX_CONTAINER_SLOTS;i++)
        {
            psItem *child=this->GetItemInSlot(i);
            if (child)
                child->Destroy();
        }
    }

    if (this->GetIsUnique())
    {
        // TODO:  Delete unique item entry
    }

    if (!this->Delete())
    {
        Error3("Failed to delete item ID %u.  Error '%s'.",this->GetUID(),db->GetLastError());
        return false;
    }

    return true;
}

bool psItem::Delete()
{
    if ( db->Command("DELETE FROM item_instances where id='%u'",this->uid)!=1)
        return false;

    uid = ID_DONT_SAVE_ITEM;  // prevent update attempts when key is -1 unsigned
    return true;
}

void psItem::ScheduleRemoval()
{
    this->SetFlags(this->GetFlags() | PSITEM_FLAG_TRANSIENT);

    int randomized_interval = psserver->rng->Get(REMOVAL_INTERVAL_RANGE);

    psItemRemovalEvent *event = new psItemRemovalEvent(REMOVAL_INTERVAL_MINIMUM + randomized_interval, this->uid );
    psserver->GetEventManager()->Push(event);

    printf("Scheduling removal of object for %d ticks from now.\n",REMOVAL_INTERVAL_MINIMUM + randomized_interval);
}

void psItem::DeleteObjectCallback(iDeleteNotificationObject * object)
{
    if (gItem)
    {
        gItem->UnregisterCallback(this);
        gItem = NULL;
    }
}

void psItem::SetGemObject(gemItem *object)
{
    // Unregister previous callbacks if the current gItem is not NULL
    if (gItem)
        gItem->UnregisterCallback(this);

    // Set the new gItem and register callback
    gItem = object;
    if (gItem)
        gItem->RegisterCallback(this);
}

psDelaySave::psDelaySave(int length, psItemSafe* item)
    : psGameEvent(0,length,"psDelaySave")
{
    this->item = item;

    #if SAVE_DEBUG
        type = typeid(*(item->item)).name();
        name = item->item->GetName();
    #endif
}

void psDelaySave::Trigger()
{
    if (item && item->item)
        item->item->Commit();

    #if SAVE_DEBUG
        else
        {
            printf("%s::Save() for '%s' discarded\n", type.GetData(), name.GetData() );
            return;
        }
        printf("%s::Save() for '%s' committed\n", type.GetData(), name.GetData() );
    #endif
}

