#ifndef GAME_MWMECHANICS_ACTIVESPELLS_H
#define GAME_MWMECHANICS_ACTIVESPELLS_H

#include <functional>
#include <list>
#include <queue>
#include <string>
#include <variant>
#include <vector>

#include <components/esm3/activespells.hpp>

#include "../mwworld/ptr.hpp"
#include "../mwworld/timestamp.hpp"

#include "spellcasting.hpp"

namespace ESM
{
    struct Enchantment;
    struct Spell;
}

namespace MWMechanics
{
    /// \brief Lasting spell effects
    ///
    /// \note The name of this class is slightly misleading, since it also handles lasting potion
    /// effects.
    class ActiveSpells
    {
    public:
        using ActiveEffect = ESM::ActiveEffect;
        class ActiveSpellParams
        {
            ESM::RefId mId;
            std::vector<ActiveEffect> mEffects;
            std::string mDisplayName;
            int mCasterActorId;
            int mSlot;
            ESM::ActiveSpells::EffectType mType;
            int mWorsenings;
            MWWorld::TimeStamp mNextWorsening;

            ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params);

            ActiveSpellParams(const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances = false);

            ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex,
                const MWWorld::Ptr& actor);

            ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor);

            ESM::ActiveSpells::ActiveSpellParams toEsm() const;

            friend class ActiveSpells;

        public:
            ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster);

            const ESM::RefId& getId() const { return mId; }

            const std::vector<ActiveEffect>& getEffects() const { return mEffects; }
            std::vector<ActiveEffect>& getEffects() { return mEffects; }

            ESM::ActiveSpells::EffectType getType() const { return mType; }

            int getCasterActorId() const { return mCasterActorId; }

            int getWorsenings() const { return mWorsenings; }

            const std::string& getDisplayName() const { return mDisplayName; }

            // Increments worsenings count and sets the next timestamp
            void worsen();

            bool shouldWorsen() const;

            void resetWorsenings();
        };

        typedef std::list<ActiveSpellParams>::const_iterator TIterator;

        void readState(const ESM::ActiveSpells& state);
        void writeState(ESM::ActiveSpells& state) const;

        TIterator begin() const;

        TIterator end() const;

        void update(const MWWorld::Ptr& ptr, float duration);

    private:
        using ParamsPredicate = std::function<bool(const ActiveSpellParams&)>;
        using EffectPredicate = std::function<bool(const ActiveSpellParams&, const ESM::ActiveEffect&)>;
        using Predicate = std::variant<ParamsPredicate, EffectPredicate>;

        struct IterationGuard
        {
            ActiveSpells& mActiveSpells;

            IterationGuard(ActiveSpells& spells);
            ~IterationGuard();
        };

        std::list<ActiveSpellParams> mSpells;
        std::vector<ActiveSpellParams> mQueue;
        std::queue<Predicate> mPurges;
        bool mIterating;

        void addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell);

        bool applyPurges(const MWWorld::Ptr& ptr, std::list<ActiveSpellParams>::iterator* currentSpell = nullptr,
            std::vector<ActiveEffect>::iterator* currentEffect = nullptr);

    public:
        ActiveSpells();

        /// Add lasting effects
        ///
        /// \brief addSpell
        /// \param id ID for stacking purposes.
        ///
        void addSpell(const ActiveSpellParams& params);

        /// Bypasses resistances
        void addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor);

        /// Removes the active effects from this spell/potion/.. with \a id
        void removeEffects(const MWWorld::Ptr& ptr, const ESM::RefId& id);

        /// Remove all active effects with this effect id
        void purgeEffect(const MWWorld::Ptr& ptr, short effectId);

        void purge(EffectPredicate predicate, const MWWorld::Ptr& ptr);
        void purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr);

        /// Remove all effects that were cast by \a casterActorId
        void purge(const MWWorld::Ptr& ptr, int casterActorId);

        /// Remove all spells
        void clear(const MWWorld::Ptr& ptr);

        bool isSpellActive(const ESM::RefId& id) const;
        ///< case insensitive

        void skipWorsenings(double hours);

        void unloadActor(const MWWorld::Ptr& ptr);
    };
}

#endif
