#ifndef GAME_MWWORLD_CONTAINERSTORE_H
#define GAME_MWWORLD_CONTAINERSTORE_H

#include <iterator>
#include <map>
#include <memory>
#include <utility>

#include <components/esm/loadalch.hpp>
#include <components/esm/loadappa.hpp>
#include <components/esm/loadarmo.hpp>
#include <components/esm/loadbook.hpp>
#include <components/esm/loadclot.hpp>
#include <components/esm/loadingr.hpp>
#include <components/esm/loadlock.hpp>
#include <components/esm/loadligh.hpp>
#include <components/esm/loadmisc.hpp>
#include <components/esm/loadprob.hpp>
#include <components/esm/loadrepa.hpp>
#include <components/esm/loadweap.hpp>

#include <components/misc/rng.hpp>

#include "ptr.hpp"
#include "cellreflist.hpp"

namespace ESM
{
    struct InventoryList;
    struct InventoryState;
}

namespace MWClass
{
    class Container;
}

namespace MWWorld
{
    class ContainerStore;

    template<class PtrType>
    class ContainerStoreIteratorBase;

    typedef ContainerStoreIteratorBase<Ptr> ContainerStoreIterator;
    typedef ContainerStoreIteratorBase<ConstPtr> ConstContainerStoreIterator;

    class ResolutionListener
    {
            ContainerStore& mStore;
        public:
            ResolutionListener(ContainerStore& store) : mStore(store) {}
            ~ResolutionListener();
    };

    class ResolutionHandle
    {
            std::shared_ptr<ResolutionListener> mListener;
        public:
            ResolutionHandle(std::shared_ptr<ResolutionListener> listener) : mListener(listener) {}
            ResolutionHandle() = default;
    };
    
    class ContainerStoreListener
    {
        public:
            virtual void itemAdded(const ConstPtr& item, int count) {}
            virtual void itemRemoved(const ConstPtr& item, int count) {}
            virtual ~ContainerStoreListener() = default;
    };

    class ContainerStore
    {
        public:

            static constexpr int Type_Potion = 0x0001;
            static constexpr int Type_Apparatus = 0x0002;
            static constexpr int Type_Armor = 0x0004;
            static constexpr int Type_Book = 0x0008;
            static constexpr int Type_Clothing = 0x0010;
            static constexpr int Type_Ingredient = 0x0020;
            static constexpr int Type_Light = 0x0040;
            static constexpr int Type_Lockpick = 0x0080;
            static constexpr int Type_Miscellaneous = 0x0100;
            static constexpr int Type_Probe = 0x0200;
            static constexpr int Type_Repair = 0x0400;
            static constexpr int Type_Weapon = 0x0800;

            static constexpr int Type_Last = Type_Weapon;

            static constexpr int Type_All = 0xffff;

            static const std::string sGoldId;

        protected:
            ContainerStoreListener* mListener;

            // (item, max charge)
            typedef std::vector<std::pair<ContainerStoreIterator, float> > TRechargingItems;
            TRechargingItems mRechargingItems;

            bool mRechargingItemsUpToDate;

        private:

            MWWorld::CellRefList<ESM::Potion>            potions;
            MWWorld::CellRefList<ESM::Apparatus>         appas;
            MWWorld::CellRefList<ESM::Armor>             armors;
            MWWorld::CellRefList<ESM::Book>              books;
            MWWorld::CellRefList<ESM::Clothing>          clothes;
            MWWorld::CellRefList<ESM::Ingredient>        ingreds;
            MWWorld::CellRefList<ESM::Light>             lights;
            MWWorld::CellRefList<ESM::Lockpick>          lockpicks;
            MWWorld::CellRefList<ESM::Miscellaneous>     miscItems;
            MWWorld::CellRefList<ESM::Probe>             probes;
            MWWorld::CellRefList<ESM::Repair>            repairs;
            MWWorld::CellRefList<ESM::Weapon>            weapons;

            mutable float mCachedWeight;
            mutable bool mWeightUpToDate;

            bool mModified;
            bool mResolved;
            unsigned int mSeed;
            MWWorld::Ptr mPtr;
            std::weak_ptr<ResolutionListener> mResolutionListener;

            ContainerStoreIterator addImp (const Ptr& ptr, int count, bool markModified = true);
            void addInitialItem (const std::string& id, const std::string& owner, int count, Misc::Rng::Seed* seed, bool topLevel=true);
            void addInitialItemImp (const MWWorld::Ptr& ptr, const std::string& owner, int count, Misc::Rng::Seed* seed, bool topLevel=true);

            template<typename T>
            ContainerStoreIterator getState (CellRefList<T>& collection,
                const ESM::ObjectState& state);

            template<typename T>
            void storeState (const LiveCellRef<T>& ref, ESM::ObjectState& state) const;

            template<typename T>
            void storeStates (const CellRefList<T>& collection,
                ESM::InventoryState& inventory, int& index,
                bool equipable = false) const;

            void updateRechargingItems();

            virtual void storeEquipmentState (const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const;

            virtual void readEquipmentState (const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory);

        public:

            ContainerStore();

            virtual ~ContainerStore();

            virtual std::unique_ptr<ContainerStore> clone() { return std::make_unique<ContainerStore>(*this); }

            ConstContainerStoreIterator cbegin (int mask = Type_All) const;
            ConstContainerStoreIterator cend() const;
            ConstContainerStoreIterator begin (int mask = Type_All) const;
            ConstContainerStoreIterator end() const;
            
            ContainerStoreIterator begin (int mask = Type_All);
            ContainerStoreIterator end();

            bool hasVisibleItems() const;

            virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip = true, bool resolve = true);
            ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed)
            ///
            /// \note The item pointed to is not required to exist beyond this function call.
            ///
            /// \attention Do not add items to an existing stack by increasing the count instead of
            /// calling this function!
            ///
            /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item.

            ContainerStoreIterator add(const std::string& id, int count, const Ptr& actorPtr);
            ///< Utility to construct a ManualRef and call add(ptr, count, actorPtr, true)

            int remove(const std::string& itemId, int count, const Ptr& actor, bool equipReplacement = 0, bool resolve = true);
            ///< Remove \a count item(s) designated by \a itemId from this container.
            ///
            /// @return the number of items actually removed

            virtual int remove(const Ptr& item, int count, const Ptr& actor, bool equipReplacement = 0, bool resolve = true);
            ///< Remove \a count item(s) designated by \a item from this inventory.
            ///
            /// @return the number of items actually removed

            void rechargeItems (float duration);
            ///< Restore charge on enchanted items. Note this should only be done for the player.

            ContainerStoreIterator unstack (const Ptr& ptr, const Ptr& container, int count = 1);
            ///< Unstack an item in this container. The item's count will be set to count, then a new stack will be added with (origCount-count).
            ///
            /// @return an iterator to the new stack, or end() if no new stack was created.

            MWWorld::ContainerStoreIterator restack (const MWWorld::Ptr& item);
            ///< Attempt to re-stack an item in this container.
            /// If a compatible stack is found, the item's count is added to that stack, then the original is deleted.
            /// @return If the item was stacked, return the stack, otherwise return the old (untouched) item.

            int count (const std::string& id) const;
            ///< @return How many items with refID \a id are in this container?

            ContainerStoreListener* getContListener() const;
            void setContListener(ContainerStoreListener* listener);
        protected:
            ContainerStoreIterator addNewStack (const ConstPtr& ptr, int count);
            ///< Add the item to this container (do not try to stack it onto existing items)

            virtual void flagAsModified();

            /// + and - operations that can deal with negative stacks
            /// Note that negativity is infectious
            static int addItems(int count1, int count2);
            static int subtractItems(int count1, int count2);
        public:

            virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const;
            ///< @return true if the two specified objects can stack with each other

            void fill (const ESM::InventoryList& items, const std::string& owner, Misc::Rng::Seed& seed = Misc::Rng::getSeed());
            ///< Insert items into *this.

            void fillNonRandom (const ESM::InventoryList& items, const std::string& owner, unsigned int seed);
            ///< Insert items into *this, excluding leveled items

            virtual void clear();
            ///< Empty container.

            float getWeight() const;
            ///< Return total weight of the items contained in *this.

            static int getType (const ConstPtr& ptr);
            ///< This function throws an exception, if ptr does not point to an object, that can be
            /// put into a container.

            Ptr findReplacement(const std::string& id);
            ///< Returns replacement for object with given id. Prefer used items (with low durability left).

            Ptr search (const std::string& id);

            virtual void writeState (ESM::InventoryState& state) const;

            virtual void readState (const ESM::InventoryState& state);

            bool isResolved() const;

            void resolve();
            ResolutionHandle resolveTemporarily();
            void unresolve();

            friend class ContainerStoreIteratorBase<Ptr>;
            friend class ContainerStoreIteratorBase<ConstPtr>;
            friend class ResolutionListener;
            friend class MWClass::Container;
    };

    
    template<class PtrType>
    class ContainerStoreIteratorBase
        : public std::iterator<std::forward_iterator_tag, PtrType, std::ptrdiff_t, PtrType *, PtrType&>
    {
        template<class From, class To, class Dummy>
        struct IsConvertible
        {
            static constexpr bool value = true;
        };

        template<class Dummy>
        struct IsConvertible<ConstPtr, Ptr, Dummy>
        {
            static constexpr bool value = false;
        };

        template<class T, class U>
        struct IteratorTrait
        {
            typedef typename MWWorld::CellRefList<T>::List::iterator type;
        };

        template<class T>
        struct IteratorTrait<T, ConstPtr>
        {
            typedef typename MWWorld::CellRefList<T>::List::const_iterator type;
        };

        template<class T>
        struct Iterator : IteratorTrait<T, PtrType>
        {
        };

        template<class T, class Dummy>
        struct ContainerStoreTrait
        {
            typedef ContainerStore* type;
        };
        
        template<class Dummy>
        struct ContainerStoreTrait<ConstPtr, Dummy>
        {
            typedef const ContainerStore* type;
        };

        typedef typename ContainerStoreTrait<PtrType, void>::type ContainerStoreType;

        int mType;
        int mMask;
        ContainerStoreType mContainer;
        mutable PtrType mPtr;

        typename Iterator<ESM::Potion>::type mPotion;
        typename Iterator<ESM::Apparatus>::type mApparatus;
        typename Iterator<ESM::Armor>::type mArmor;
        typename Iterator<ESM::Book>::type mBook;
        typename Iterator<ESM::Clothing>::type mClothing;
        typename Iterator<ESM::Ingredient>::type mIngredient;
        typename Iterator<ESM::Light>::type mLight;
        typename Iterator<ESM::Lockpick>::type mLockpick;
        typename Iterator<ESM::Miscellaneous>::type mMiscellaneous;
        typename Iterator<ESM::Probe>::type mProbe;
        typename Iterator<ESM::Repair>::type mRepair;
        typename Iterator<ESM::Weapon>::type mWeapon;

        ContainerStoreIteratorBase (ContainerStoreType container);
        ///< End-iterator

        ContainerStoreIteratorBase (int mask, ContainerStoreType container);
        ///< Begin-iterator

        // construct iterator using a CellRefList iterator
        ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator<ESM::Potion>::type);
        ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator<ESM::Apparatus>::type);
        ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator<ESM::Armor>::type);
        ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator<ESM::Book>::type);
        ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator<ESM::Clothing>::type);
        ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator<ESM::Ingredient>::type);
        ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator<ESM::Light>::type);
        ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator<ESM::Lockpick>::type);
        ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator<ESM::Miscellaneous>::type);
        ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator<ESM::Probe>::type);
        ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator<ESM::Repair>::type);
        ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator<ESM::Weapon>::type);

        template<class T>
        void copy (const ContainerStoreIteratorBase<T>& src);
        
        void incType ();
        
        void nextType ();

        bool resetIterator ();
        ///< Reset iterator for selected type.
        ///
        /// \return Type not empty?

        bool incIterator ();
        ///< Increment iterator for selected type.
        ///
        /// \return reached the end?

        public:
            template<class T>
            ContainerStoreIteratorBase (const ContainerStoreIteratorBase<T>& other)
            {
                char CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR[IsConvertible<T, PtrType, void>::value ? 1 : -1];
                ((void)CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR);
                copy (other);
            }

            template<class T>
            bool isEqual(const ContainerStoreIteratorBase<T>& other) const;

            PtrType *operator->() const;
            PtrType operator*() const;

            ContainerStoreIteratorBase& operator++ ();
            ContainerStoreIteratorBase operator++ (int);
            ContainerStoreIteratorBase& operator= (const ContainerStoreIteratorBase& rhs);
            ContainerStoreIteratorBase (const ContainerStoreIteratorBase& rhs) = default;

            int getType() const;
            const ContainerStore *getContainerStore() const;

            friend class ContainerStore;
            friend class ContainerStoreIteratorBase<Ptr>;
            friend class ContainerStoreIteratorBase<ConstPtr>;
    };

    template<class T, class U>
    bool operator== (const ContainerStoreIteratorBase<T>& left, const ContainerStoreIteratorBase<U>& right);
    template<class T, class U>
    bool operator!= (const ContainerStoreIteratorBase<T>& left, const ContainerStoreIteratorBase<U>& right);
}
#endif
