#pragma once

// ------------------------------------------------------------------------------------------------
#include "Core/Privilege/Unit.hpp"

// ------------------------------------------------------------------------------------------------
namespace SqMod {

/* ------------------------------------------------------------------------------------------------
 * An individual class/group that can optionally inherit the privileges of another and can
 * change their status to affect their children but not their parent class.
 * A class can extend it's privileges to units and/or other classes.
 * These cost less to query but use more memory because they're fewer and they preallocate it.
 * Each class must have a unique numerical identifier within their associated manager.
*/
struct PvClass
{
    /* --------------------------------------------------------------------------------------------
     * Strong and weak reference types.
    */
    typedef std::shared_ptr< PvClass > Ref;
    typedef std::weak_ptr< PvClass > Ptr;

    /* --------------------------------------------------------------------------------------------
     * Type of container used to store privilege classes.
    */
    typedef VecMap< PvIdentity, Ref, PvIdPred > List;

    /* --------------------------------------------------------------------------------------------
     * User identifier associated with this class instance. Always unique in the same manager!
    */
    const SQInteger     mID;

    /* --------------------------------------------------------------------------------------------
     * The parent class from which we are inheriting privileges, if any.
    */
    Ptr                 mParent;

    /* --------------------------------------------------------------------------------------------
     * A container with unique privilege status values associated with this class.
    */
    PvStatusList        mPrivileges;

    /* --------------------------------------------------------------------------------------------
     * Dedicated callback for privilege query event.
    */
    Function            mOnQuery;

    /* --------------------------------------------------------------------------------------------
     * Dedicated callback for privilege gained event.
    */
    Function            mOnGained;

    /* --------------------------------------------------------------------------------------------
     * Dedicated callback for privilege lost event.
    */
    Function            mOnLost;

    /* --------------------------------------------------------------------------------------------
     * Container that stores all the associated units.
    */
    PvUnit::List        mUnits;

    /* --------------------------------------------------------------------------------------------
     * User tag associated with this instance.
    */
    StackStrF           mTag;

    /* --------------------------------------------------------------------------------------------
     * User data associated with this instance.
    */
    LightObj            mData;

    /* --------------------------------------------------------------------------------------------
     * Pointer to the associated manager. Should always be present.
     * 
    */
    PvManager *         mManager;

    /* -------------------------------------------------------------------------------------------
     * Default constructor.
    */
    PvClass(SQInteger id, PvManager * mgr)
        : mID(id)
        , mParent()
        , mPrivileges()
        , mOnQuery(), mOnGained(), mOnLost()
        , mUnits()
        , mTag(), mData()
        , mManager(mgr)
    {
    }

    /* -------------------------------------------------------------------------------------------
     * Default constructor.
    */
    PvClass(SQInteger id, StackStrF && tag, PvManager * mgr)
        : mID(id)
        , mParent()
        , mPrivileges()
        , mOnQuery(), mOnGained(), mOnLost()
        , mUnits()
        , mTag(std::move(tag)), mData()
        , mManager(mgr)
    {
    }

    /* -------------------------------------------------------------------------------------------
     * Copy constructor (disabled).
    */
    PvClass(const PvClass & o) = delete;

    /* -------------------------------------------------------------------------------------------
     * Move constructor (disabled).
    */
    PvClass(PvClass && o) = delete;

    /* -------------------------------------------------------------------------------------------
     * Destructor.
    */
    ~PvClass() = default;

    /* -------------------------------------------------------------------------------------------
     * Copy assignment operator (disabled).
    */
    PvClass & operator = (const PvClass & o) = delete;

    /* -------------------------------------------------------------------------------------------
     * Move assignment operator (disabled).
    */
    PvClass & operator = (PvClass && o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Update the hash of the specified unit.
    */
    void UpdateUnitHash(SQInteger id, size_t hash)
    {
        auto itr = mUnits.find(PvIdentity(id));
        // Only update if it exists
        if (itr != mUnits.end())
        {
            itr->first.mHash = hash;
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the associated user tag.
    */
    void SetTag(StackStrF & tag);

    /* --------------------------------------------------------------------------------------------
     * Release all script resources. Recursively forward request.
    */
    void Release();

    /* --------------------------------------------------------------------------------------------
     * Make sure the referenced parent class is valid.
    */
    void ValidateParent() const
    {
        if (mParent.expired())
        {
            STHROWF("Class ({} : {}) has invalid parent class reference", mID, mTag.mPtr);
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Make sure the referenced parent class is valid and return a reference to it.
    */
    SQMOD_NODISCARD PvClass & ValidParent() const
    {
        ValidateParent();
        // Acquire a reference and return it
        return *(mParent.lock().get());
    }

    /* --------------------------------------------------------------------------------------------
     * Make sure the referenced manager is valid.
    */
    void ValidateManager() const
    {
        if (!mManager)
        {
            STHROWF("Class ({} : {}) has invalid manager reference", mID, mTag.mPtr);
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Make sure the referenced manger is valid and return a reference to it.
    */
    SQMOD_NODISCARD PvManager & ValidManager() const;

    /* --------------------------------------------------------------------------------------------
     * Find out the callback that must be invoked to handle query events for a certain entry. 
    */
    SQMOD_NODISCARD const Function & GetOnQuery(SQInteger id) const;

    /* --------------------------------------------------------------------------------------------
     * Find out the callback that must be invoked to handle gained events for a certain entry. 
    */
    SQMOD_NODISCARD const Function & GetOnGained(SQInteger id) const;

    /* --------------------------------------------------------------------------------------------
     * Find out the callback that must be invoked to handle lost events for a certain entry. 
    */
    SQMOD_NODISCARD const Function & GetOnLost(SQInteger id) const;

    /* --------------------------------------------------------------------------------------------
     * Retrieve the current value of an entry for this particular class.
    */
    SQMOD_NODISCARD SQInteger GetEntryValue(SQInteger id) const;

    /* --------------------------------------------------------------------------------------------
     * Retrieve the inherited value of an entry for this particular class.
    */
    SQMOD_NODISCARD SQInteger GetInheritedEntryValue(SQInteger id) const;

    /* --------------------------------------------------------------------------------------------
     * Perform the actions necessary to handle a privilege gain event.
    */
    void DoGained(SQInteger id, SQInteger value) const;

    /* --------------------------------------------------------------------------------------------
     * Perform the actions necessary to handle a privilege lost event.
    */
    void DoLost(SQInteger id, SQInteger value) const;

    /* --------------------------------------------------------------------------------------------
     * Perform the actions necessary to handle a privilege change event.
    */
    void DoChanged(SQInteger id, bool status, SQInteger value) const;

    /* --------------------------------------------------------------------------------------------
     * Assign a status value. Does not care if a parent (class or global) has the same status.
     * Later if the parent changes this status, we will keep having this status value.
    */
    void AssignPrivilege(SQInteger id, SQInteger value);

    /* --------------------------------------------------------------------------------------------
     * Remove a status value. If the specified status value is not assigned, nothing happens.
    */
    void RemovePrivilege(SQInteger id);

    /* --------------------------------------------------------------------------------------------
     * Assign a status value. If a parent (class or global) has the same value, nothing changes.
     * Same as AssignStatus but the status will not be enforced if we have it (inherited or not).
    */
    void ModifyPrivilege(SQInteger id, SQInteger value);

    /* --------------------------------------------------------------------------------------------
     * See AssignPrivilege().
    */
    void AssignPrivilege(StackStrF & tag, SQInteger value);

    /* --------------------------------------------------------------------------------------------
     * See RemovePrivilege().
    */
    void RemovePrivilege(StackStrF & tag);

    /* --------------------------------------------------------------------------------------------
     * See ModifyPrivilege().
    */
    void ModifyPrivilege(StackStrF & tag, SQInteger value);

    /* --------------------------------------------------------------------------------------------
     * Remove all status values. Basically it reverts to the parent class privileges.
    */
    void RemoveAllPrivileges();

    /* --------------------------------------------------------------------------------------------
     * Change the parent class.
    */
    void AssignParent(const Ref & parent);

    /* --------------------------------------------------------------------------------------------
     * Check if this class has a certain privilege.
    */
    SQMOD_NODISCARD bool Can(SQInteger id, SQInteger req) const;

    /* --------------------------------------------------------------------------------------------
     * See if a unit with a certain identifier inherits this class.
    */
    SQMOD_NODISCARD LightObj GetUnitWithID(SQInteger id);

    /* --------------------------------------------------------------------------------------------
     * See if a unit with a certain tag inherits this class.
    */
    SQMOD_NODISCARD LightObj GetUnitWithTag(StackStrF & tag);

    /* --------------------------------------------------------------------------------------------
     * See if a unit with a certain identifier inherits this class.
    */
    bool HaveUnitWithID(SQInteger id);

    /* --------------------------------------------------------------------------------------------
     * See if a unit with a certain tag inherits this class.
    */
    bool HaveUnitWithTag(StackStrF & tag);

    /* --------------------------------------------------------------------------------------------
     * Invoke a given callback with every owned entry identifier.
    */
    void EachEntryID(Object & ctx, Function & func) const;

    /* --------------------------------------------------------------------------------------------
     * Invoke a given callback with every parented unit identifier.
    */
    void EachUnitID(Object & ctx, Function & func) const;
};

/* ------------------------------------------------------------------------------------------------
 * Privilege class wrapper. Mostly calls functions of the actual implementation.
 * Used to avoid having to involve hard script references that can persist.
 * Breaks away from usual formatting.
*/
struct SqPvClass
{
    PvClass::Ptr mI;
    // --------------------------------------------------------------------------------------------
    explicit SqPvClass(PvClass::Ptr i) : mI(std::move(i)) { } // NOLINT(modernize-pass-by-value)
    explicit SqPvClass(const PvClass::Ref & i) : mI(i) { }
    SqPvClass(const SqPvClass & o) = default;
    SqPvClass(SqPvClass && o) noexcept = default;
    ~SqPvClass() = default;
    SqPvClass & operator = (const SqPvClass & o) = default;
    SqPvClass & operator = (SqPvClass && o) noexcept = default;
    // --------------------------------------------------------------------------------------------
    void Validate() const { if (mI.expired()) STHROWF("Invalid unit reference"); }
    SQMOD_NODISCARD PvClass & Valid() const { Validate(); return *(mI.lock().get()); }
    // --------------------------------------------------------------------------------------------
    SQMOD_NODISCARD SQInteger GetID() const { return Valid().mID; }
    // --------------------------------------------------------------------------------------------
    SQMOD_NODISCARD auto ToString() const { return Valid().mTag.mObj; }
    SQMOD_NODISCARD auto GetTag() const { return Valid().mTag.mObj; }
    void SetTag(StackStrF & tag) const { Valid().SetTag(tag); }
    SQMOD_NODISCARD SqPvClass & ApplyTag(StackStrF & tag) { SetTag(tag); return *this; }
    SQMOD_NODISCARD LightObj & GetData() const { return Valid().mData; }
    void SetData(LightObj & data) const { Valid().mData = data; }
    // --------------------------------------------------------------------------------------------
    void SetOnQuery(Function & func) const { Valid().mOnQuery = std::move(func); }
    void SetOnLost(Function & func) const { Valid().mOnLost = std::move(func); }
    void SetOnGained(Function & func) const { Valid().mOnGained = std::move(func); }
    // --------------------------------------------------------------------------------------------
    SQMOD_NODISCARD LightObj GetManager() const { return LightObj(Valid().mManager); }
    // --------------------------------------------------------------------------------------------
    SQMOD_NODISCARD LightObj GetParent() const
    {
        return LightObj(SqTypeIdentity< SqPvClass >{}, SqVM(), Valid().mParent);
    }
    void SetParent(const SqPvClass & cls) const
    {
        Validate();
        cls.Validate();
        mI.lock()->AssignParent(cls.mI.lock());
    }
    // --------------------------------------------------------------------------------------------
    bool Can(LightObj & obj, SQInteger req) const;
    // --------------------------------------------------------------------------------------------
    void AssignPrivilegeWithID(SQInteger id, SQInteger value) const { Valid().AssignPrivilege(id, value); }
    void AssignPrivilegeWithTag(StackStrF & tag, SQInteger value) const { Valid().AssignPrivilege(tag, value); }
    void RemovePrivilegeWithID(SQInteger id) const { Valid().RemovePrivilege(id); }
    void RemovePrivilegeWithTag(StackStrF & tag) const { Valid().RemovePrivilege(tag); }
    void ModifyPrivilegeWithID(SQInteger id, SQInteger value) const { Valid().ModifyPrivilege(id, value); }
    void ModifyPrivilegeWithTag(StackStrF & tag, SQInteger value) const { Valid().ModifyPrivilege(tag, value); }
    void RemoveAllPrivileges() const { Valid().RemoveAllPrivileges(); }
    // --------------------------------------------------------------------------------------------
    SQMOD_NODISCARD LightObj GetUnitWithID(SQInteger id) const { return Valid().GetUnitWithID(id); }
    SQMOD_NODISCARD LightObj GetUnitWithTag(StackStrF & tag) const { return Valid().GetUnitWithTag(tag); }
    SQMOD_NODISCARD bool HaveUnitWithID(SQInteger id) const { return Valid().HaveUnitWithID(id); }
    SQMOD_NODISCARD bool HaveUnitWithTag(StackStrF & tag) const { return Valid().HaveUnitWithTag(tag); }
    // --------------------------------------------------------------------------------------------
    void EachEntryID(Object & ctx, Function & func) const { return Valid().EachEntryID(ctx, func); }
    void EachUnitID(Object & ctx, Function & func) const { return Valid().EachUnitID(ctx, func); }
};

} // Namespace:: SqMod