/*
 * Licensed under BSD license.  See LICENCE.TXT  
 *
 * Produced by:	Jeff Lait
 *
 *      	7DRL Development
 *
 * NAME:        item.h ( Live Once Library, C++ )
 *
 * COMMENTS:
 */

#ifndef __item__
#define __item__

#include "glbdef.h"

#include "dpdf.h"
#include "grammar.h"
#include "map.h"
#include "buf.h"

#include <iostream>
using namespace std;

// Generates a unique id
int glb_allocUID();
// Reports that the given UID was loaded from disk, ensures we don't
// repeat it.
void glb_reportUID(int uid);

#define INVALID_UID -1

class ITEM
{
public:
		~ITEM();

    // This will create ITEM_NONE so be wary.
    static ITEM *create(const MAP *map, ITEM_NAMES item, int depth = 0);
    static ITEM *createMacGuffin(const MAP *map);

    static ITEM_NAMES chooseItemClass(const MAP *map, ITEMCLASS_NAMES item, int depth);
    static ITEM_NAMES chooseAnyItemClass(MAP *map, ITEMCLASS_NAMES item);
    ITEM_NAMES chooseAnyItemClass(ITEMCLASS_NAMES item) const
    { return chooseAnyItemClass(myMap, item); }
    static ITEM *createItemClass(const MAP *map, ITEMCLASS_NAMES item, int depth);

    // This can return 0 if no valid items exist at this depth!
    static ITEM *createRandom(const MAP *map, int depth);

    // This respects our unlocks so *will* give zeros!
    static ITEM_NAMES chooseRandomBalanced(const MAP *map, int depth);

    // Constructs a item from the balance list
    static ITEM *createRandomBalanced(const MAP *map, int depth);

    static ITEM_NAMES itemFromHash(const MAP *map, unsigned hash, int depth);

    static void	initSystem();
    static void saveGlobal(ostream &os);
    static void loadGlobal(istream &is);

    // Setups up $ITEMNAME $AN_ITEMNAME
    void		 setupTextVariables() const;

    // Makes an identical copy
    ITEM	*copy() const;
    // Makes a new item with all the same properties, but new UID, etc.
    ITEM	*createCopy() const;

    ITEM_NAMES	 getDefinition() const { return myDefinition; }
    void	 setDefinition(ITEM_NAMES name) { myDefinition = name; }

    int		 getMagicClass() const;
    static int	 getMagicClass(const MAP *map, ITEM_NAMES item); 
    void	 markMagicClassKnown(bool silent = false);
    bool	 isMagicClassKnown() const;

    POTION_NAMES         asPotion() const { return asPotion(myMap, getDefinition()); }
    static POTION_NAMES  asPotion(const MAP *map, ITEM_NAMES item);
    SPELL_NAMES          asSpell() const { return asSpell(myMap, getDefinition()); }
    static SPELL_NAMES   asSpell(const MAP *map, ITEM_NAMES item);
    SCROLL_NAMES         asScroll() const { return asScroll(myMap, getDefinition()); }
    static SCROLL_NAMES  asScroll(const MAP *map, ITEM_NAMES item);
    WAND_NAMES         asWand() const { return asWand(myMap, getDefinition()); }
    static WAND_NAMES  asWand(const MAP *map, ITEM_NAMES item);
    RING_NAMES           asRing() const { return asRing(myMap, getDefinition()); }
    static RING_NAMES    asRing(const MAP *map, ITEM_NAMES item);

    static ITEM_NAMES	 lookupPotion(const MAP *map, POTION_NAMES potion);
    static ITEM_NAMES	 lookupSpell(const MAP *map, SPELL_NAMES potion);
    static ITEM_NAMES	 lookupRing(const MAP *map, RING_NAMES potion);
    static ITEM_NAMES	 lookupScroll(const MAP *map, SCROLL_NAMES potion);
    static ITEM_NAMES	 lookupWand(const MAP *map, WAND_NAMES potion);
		
    VERB_PERSON	 getPerson() const;
    BUF		 getName() const;
    BUF		 getSingleName() const;
    BUF		 getSingleArticleName() const;
    BUF		 getRawName() const;
    BUF		 getArticleName() const;

    // Returns a comma separated list of the items.
    static BUF   buildList(const ITEMLIST &list, bool *isplural = nullptr);

    // Returns "" if no detailed description.  Returns a multi-line
    // buffer prefixed with +-
    BUF		 getDetailedDescription() const;

    // Detailed Description + the text lookup.
    BUF		 getLongDescription() const;

    bool	 isBroken() const { return myBroken; }
    void	 setBroken(bool isbroken) { myBroken = isbroken; }

    // Did the avatar abandon it?
    bool	 wasDiscarded() const { return myDiscarded; }
    void	 markDiscarded(bool discard) { myDiscarded = discard; }

    int		 breakChance() const { return breakChance(MOB_NONE); }
    int		 breakChance(MOB_NAMES victim) const;

    bool	 isEquipped() const { return myEquipped; }
    void	 setEquipped(bool isequipped) { myEquipped = isequipped; }

    bool	 isWielded() const { return myWielded; }
    void	 setWielded(bool iswielded) { myWielded = iswielded; }

    const ITEM_DEF	&defn() const { return defn(getDefinition()); }
    static const ITEM_DEF &defn(ITEM_NAMES item) { return *GAMEDEF::itemdef(item); }

    ATTACK_NAMES	getMeleeAttack() const;
    const ATTACK_DEF	&attackdefn() const { return *GAMEDEF::attackdef(getMeleeAttack()); }
    static const ATTACK_DEF &attackdefn(ITEM_NAMES item) { return *GAMEDEF::attackdef((ATTACK_NAMES) defn(item).melee_attack); }
    const ATTACK_DEF	&rangedefn() const { return *GAMEDEF::attackdef(getRangeAttack()); }
    static const ATTACK_DEF &rangedefn(ITEM_NAMES item) { return *GAMEDEF::attackdef((ATTACK_NAMES) defn(item).range_attack); }
    ITEM_DEF	&edefn() const { return edefn(getDefinition()); }
    static ITEM_DEF &edefn(ITEM_NAMES item) { return *GAMEDEF::itemdef(item); }

    void	 getLook(u8 &symbol, ATTR_NAMES &attr) const;

    const POS   &pos() const { return myPos; }

    // Warning: This can delete this
    void	 move(POS pos);

    // Silent move, does not update maps!  Do not use.
    void	 silentMove(POS pos) { myPos = pos; }

    // Unlinks our position, dangerous so only use if you are sure you
    // will be removing from the map!
    void	 clearAllPos();

    void	 setMap(MAP *map) { myPos.setMap(map); myMap = map; }

    // Copies inevntory & stats into a stash.
    void	 stashInto(ITEM *dst);

    bool	 canStackWith(const ITEM *stack) const;
    void	 combineItem(const ITEM *item);
    int		 getStackCount() const { return myCount; }
    void	 decStackCount(int amt=1) { myCount -= amt; }
    void	 setStackCount(int count) { myCount = count; }

    // -1 for things without a count down.
    int		 getTimer() const { return myTimer; }
    void	 addTimer(int add) { myTimer += add; }
    void	 setTimer(int timer) { myTimer = timer; }

    // Returns true if should self destruct.
    bool	 runHeartbeat(MOB *owner);

    int		 getDepth() const { return getDepth(myMap, getDefinition()); }
    static int	 getDepth(const MAP *map, ITEM_NAMES item);

    // Magic bonus
    int		 getBonus() const { return myBonus; }
    int		 getKnownBonus() const { return isBonusKnown() ? myBonus: 0; }
    void	 setBonus(int bonus) { myBonus = bonus; }
    bool	 isBonusKnown() const { return myBonusKnown; }
    void	 markBonusKnown() { myBonusKnown = true; }

    // Returns true if it worked, on fail you should destroy.
    bool	 enchant(int bonus = 1);

    // Fade any writing
    bool	 fadeWriting();
    // Apply writing, true if interesting.
    bool	 waterSoak(POTION_NAMES liquid);

    // Determines if it is at all considerable as a weapon
    bool	 isWeapon() const;	 // melee
    bool	 isArmour() const;
    bool	 isRanged() const;	// range weapon
    bool	 isPotion() const;
    bool	 isSpellbook() const;
    bool	 isScroll() const;
    bool	 isWand() const;
    bool	 isRing() const;
    bool	 isFood() const;
    bool	 isTool() const;		// includes daggers!
    bool	 isAmmo() const;
    bool	 isTreasure() const;
    bool	 isFlag() const;

    // Has it been duped via a dupe bug?
    bool         isDuped() const;
    void         markDuped(bool dupestate);

    // Was this generated with the creature?
    bool	 isStartingItem() const;
    void         markStartingItem(bool starttate);

    ITEMCLASS_NAMES	 itemClass() const { return (ITEMCLASS_NAMES) defn().itemclass; }

    int		 getRangeRange() const;
    int		 getRangeArea() const;
    void	 getRangeStats(int &range, int &area) const;
    ATTACK_NAMES getRangeAttack() const;

    int		 getDamageReduction() const { return defn().damagereduction; }
    int		 getBaseArmourClass(DAMAGECLASS_NAMES damageclass) const;
    int		 getArmourClass(DAMAGECLASS_NAMES damageclass) const
    { return getBaseArmourClass(damageclass) + getBonus(); }
    int		 getTotalArmourClass() const;

    int		 getFoodVal() const;

    void	 save(ostream &os) const;
    static ITEM	*load(istream &is);

    int		 getUID() const { return myUID; }

    void	 setMobType(MOB_NAMES mob) { myMobType = mob; }
    MOB_NAMES	 mobType() const { return myMobType; }

    void	 setElement(ELEMENT_NAMES element) { myElement = element; }
    ELEMENT_NAMES element() const { return myElement; }

    ROLELIST_NAMES roles() const { return defn().roles; }

    SIZE_NAMES		 size() const;

    // Shatters the item, assumes it is on ground.  returns true if
    // destroys self.
    bool		 shatter();
    bool		 mend(MOB *owner);

    bool		 cook();
    bool		 polymorph(bool silent);

    /// Is it sensible to resurect this?
    bool		 canResurrect() const;

    /// Attempts resurrects the item, returns true if should delete
    /// If fails will be unchanged (no space, etc)  Will return false
    /// if succeeds but stack got deced.
    bool	 	 resurrect(MOB *owner);

    EFFECT_NAMES  	 corpseEffect() const;
    EFFECT_NAMES	 potionEffect() const;
    static EFFECT_NAMES	 potionEffect(const MAP *map, ITEM_NAMES item);

    MATERIAL_NAMES	material() const { return myMaterial; }
    void		setMaterial(MATERIAL_NAMES material) { myMaterial = material; }
    MATERIAL_NAMES	giltMaterial() const { return myGiltMaterial; }
    bool		isGilt() const { return myGiltMaterial != MATERIAL_NONE; }

    u8			inventoryLetter() const { return myLetter; }
    void		setInventoryLetter(u8 sym) { myLetter = sym; }

    // Filters
    static auto		filterAll() { return [](ITEM *item) -> bool
				    { return true; }; }
    static auto		filterNone() { return [](ITEM *item) -> bool
				    { return false; }; }
    static auto		filterThrowable() { return [](ITEM *item) -> bool
				    { return item->defn().throwable; }; }
    static auto		filterAppliable() { return [](ITEM *item) -> bool
				    { return item->isTool() || item->isWand(); }; }
    static auto		filterEdible() { return [](ITEM *item) -> bool
				    { return item->isFood(); }; }
    static auto		filterDrinkable() { return [](ITEM *item) -> bool
				    { return item->isPotion(); }; }
    static auto		filterReadable() { return [](ITEM *item) -> bool
				    { return item->isSpellbook()
					|| item->isScroll(); }; }
    static auto		filterSpellbooks() { return [](ITEM *item) -> bool
				    { return item->isSpellbook(); }; }
    static auto		filterScrolls() { return [](ITEM *item) -> bool
				    { return item->isScroll(); }; }
    static auto		filterWands() { return [](ITEM *item) -> bool
				    { return item->isWand(); }; }
    static auto		filterDroppable() { return [](ITEM *item) -> bool
				    { return true; }; }
    static auto		filterWearable() { return [](ITEM *item) -> bool
				    { return item->isArmour() || item->isRing(); }; }
    static auto		filterArmour() { return [](ITEM *item) -> bool
				    { return item->isArmour(); }; }
    static auto		filterAmmo() { return [](ITEM *item) -> bool
				    { return item->isAmmo(); }; }
    static auto		filterRings() { return [](ITEM *item) -> bool
				    { return item->isRing(); }; }
    static auto		filterWriteable() { return [](ITEM *item) -> bool
				    { return item->getDefinition() == ITEM_SPELLBOOK_BLANK ||
                                             item->getDefinition() == ITEM_SCROLL_BLANK; }; }
    static auto		filterWieldable() { return [](ITEM *item) -> bool
				    { return item->isWeapon(); }; }
    static auto		filterRanged() { return [](ITEM *item) -> bool
				    { return item->isRanged(); }; }
    static auto		filterWeaponOrRanged() { return [](ITEM *item) -> bool
				    { return item->isWeapon() || item->isRanged(); }; }
    static auto		filterFuseable() { return [](ITEM *item) -> bool
				    { return item->isWeapon()
					|| item->isArmour()
					|| item->isRanged()
					|| item->isRing(); }; }
    static auto		filterWieldedOrWorn() 
    { return [](ITEM *item) -> bool
	    { return item->isEquipped() || item->isWielded(); }; }
    static auto		filterTreasure() { return [](ITEM *item) -> bool
				    { return item->isTreasure(); }; }

protected:
		 ITEM();

    ITEM_NAMES	 myDefinition;
    ELEMENT_NAMES	myElement;

    MOB_NAMES	 myMobType;

    POS		 myPos;
    MAP		*myMap;		// needed when pos is null...
    int		 myCount;
    int		 myTimer;
    int		 myUID;

    int		 myBonus;

    MATERIAL_NAMES	myMaterial, myGiltMaterial;

    u8		 myLetter;

    // Flags
    int		 mySeed;
    bool	 myBroken : 1;
    bool	 myEquipped : 1;
    bool	 myWielded : 1;
    bool	 myBonusKnown : 1;
    bool         myDuped : 1;
    bool	 myStartingItem : 1;
    bool	 myDiscarded : 1;
};

#endif

