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

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

// ------------------------------------------------------------------------------------------------
SQMOD_DECL_TYPENAME(ManagerTn, _SC("SqPrivilegeManager"))

// ------------------------------------------------------------------------------------------------
void TerminatePrivileges()
{
    // Go over all managers and try to close them
    for (PvManager * inst = PvManager::sHead; inst && inst->mNext != PvManager::sHead; inst = inst->mNext)
    {
        inst->Terminate();
    }
}

// ------------------------------------------------------------------------------------------------
void PvManager::Release()
{
    // Release objects from this instance
    m_OnQuery.Release();
    m_OnGained.Release();
    m_OnLost.Release();
    m_Tag.Release();
    m_Data.Release();
}

// ------------------------------------------------------------------------------------------------
void PvManager::Terminate()
{
    // Release script objects held by units
    for (auto & u : m_Units)
    {
        u.second->Release();
    }
    // Release script objects held by classes
    for (auto & c : m_Classes)
    {
        c.second->Release();
    }
    // Release script objects held by entries
    for (auto & e : m_Entries)
    {
        e.second->Release();
    }
    // Release script objects held by the manager
    Release();
    // Clear the containers as well
    m_Classes.clear();
    m_Units.clear();
    m_Entries.clear();
}

// ------------------------------------------------------------------------------------------------
LightObj PvManager::CreateEntry(SQInteger id, StackStrF & name)
{
    ModifyEntries();
    // Look for a similar entry
    auto itr = m_Entries.find(PvIdentity(id));
    // Is this unique?
    if (itr != m_Entries.end())
    {
        STHROWF("Entry ({} : {}) already exists", id, name.mPtr);
    }
    // We backup the name hash now because I don't know if the move will happen before or after i create the identity
    // Compiler optimizations and sh!t. (the name hash is cached in .mRes member variable)
    const auto h = name.CacheHash().GetHash();
    // Create it now
    auto & e = m_Entries.emplace_back(PvIdentity(id, h), std::make_shared< PvEntry >(id, std::move(name), this));
    // Create a wrapper instance and return it
    return LightObj(SqTypeIdentity< SqPvEntry >{}, SqVM(), e);
}

// ------------------------------------------------------------------------------------------------
LightObj PvManager::CreateClass(SQInteger id, StackStrF & name)
{
    ModifyClasses();
    // Look for a similar class
    auto itr = m_Classes.find(PvIdentity(id));
    // Is this unique?
    if (itr != m_Classes.end())
    {
        STHROWF("Class ({} : {}) already exists", id, name.mPtr);
    }
    // We backup the name hash now because I don't know if the move will happen before or after i create the identity
    // Compiler optimizations and sh!t. (the name hash is cached in .mRes member variable)
    const auto h = name.CacheHash().GetHash();
    // Create it now
    auto & e = m_Classes.emplace_back(PvIdentity(id, h), std::make_shared< PvClass >(id, std::move(name), this));
    // Create a wrapper instance and return it
    return LightObj(SqTypeIdentity< SqPvClass >{}, SqVM(), e);
}

// ------------------------------------------------------------------------------------------------
LightObj PvManager::CreateUnit(SQInteger id, const SqPvClass & cls, StackStrF & name)
{
    ModifyUnits();
    // Validate the given class
    cls.Validate();
    // Look for a similar unit
    auto itr = m_Units.find(PvIdentity(id));
    // Is this unique?
    if (itr != m_Units.end())
    {
        STHROWF("Unit ({} : {}) already exists", id, name.mPtr);
    }
    // We backup the name hash now because I don't know if the move will happen before or after i create the identity
    // Compiler optimizations and sh!t. (the name hash is cached in .mRes member variable)
    const auto h = name.CacheHash().GetHash();
    // Create it now
    auto & e = m_Units.emplace_back(PvIdentity(id, h), std::make_shared< PvUnit >(id, std::move(name), cls.mI));
    // Create a wrapper instance and return it
    return LightObj(SqTypeIdentity< SqPvUnit >{}, SqVM(), e);
}

// ------------------------------------------------------------------------------------------------
void PvManager::PropagateParentAssign(const PvClass & cls, const PvClass::Ref & parent)
{
    
}

// ------------------------------------------------------------------------------------------------
void PvManager::PropagateParentChange(const PvClass & cls, const PvClass::Ref & parent)
{
    // Prevent any changes to entries during this operation
    AutoAssign< bool > aag(m_LockEntries, false, true);
    // Go over all entries and see if the specified class will gain or loose any privileges from this change
    for (const auto & e : m_Entries)
    {
        // Owned privileges are not affected regardless of the inherited class
        {
            // See if this class inherits the current entry
            auto itr = cls.mPrivileges.find(e.second->mID);
            // Is this entry inherited or owned?
            if (itr != cls.mPrivileges.end())
            {
                continue; // Nothing new will happen here
            }
        }
        // We know we inherit this entry value so let's get that value instead
        SQInteger inherited = cls.GetInheritedEntryValue(e.second->mID);
        // Get the value that will be inherited for this entry
        SQInteger current = parent->GetEntryValue(e.second->mID);
        // Are they literally the same?
        if (inherited == current)
        {
            continue; // Don't even bother
        }
        // Find out who can identify this change
        const Function & modify = e.second->mOnModify.IsNull() ? m_OnModify : e.second->mOnModify;
        // Is there someone that can identify this change?
        if (!modify.IsNull())
        {
            LightObj r = modify.Eval(inherited, current);
            // Was this considered a change?
            if (!r.IsNull())
            {
                cls.DoChanged(e.second->mID, r.Cast< bool >(), current);
            }
        }
        else
        {
            // By default we use > comparison to decide upgrades
            cls.DoChanged(e.second->mID, current > inherited, current);
        }
    }
}

// ------------------------------------------------------------------------------------------------
void PvManager::PropagateClassChange(const PvUnit & unit, const PvClass::Ref & cls)
{
    // Prevent any changes to entries during this operation
    AutoAssign< bool > aag(m_LockEntries, false, true);
    // Go over all entries and see if the specified unit will gain or loose any privileges from this change
    for (const auto & e : m_Entries)
    {
        // Owned privileges are not affected regardless of the inherited class
        {
            // See if this unit inherits the current entry
            auto itr = unit.mPrivileges.find(e.second->mID);
            // Is this entry inherited or owned?
            if (itr != unit.mPrivileges.end())
            {
                continue; // Nothing new will happen here
            }
        }
        // We know we inherit this entry value so let's get that value instead
        SQInteger inherited = unit.GetInheritedEntryValue(e.second->mID);
        // Get the value that will be inherited for this entry
        SQInteger current = cls->GetEntryValue(e.second->mID);
        // Are they literally the same?
        if (inherited == current)
        {
            continue; // Don't even bother
        }
        // Find out who can identify this change
        const Function & modify = e.second->mOnModify.IsNull() ? m_OnModify : e.second->mOnModify;
        // Is there someone that can identify this change?
        if (!modify.IsNull())
        {
            LightObj r = modify.Eval(inherited, current);
            // Was this considered a change?
            if (!r.IsNull())
            {
                unit.DoChanged(e.second->mID, r.Cast< bool >(), current);
            }
        }
        else
        {
            // By default we use > comparison to decide upgrades
            unit.DoChanged(e.second->mID, current > inherited, current);
        }
    }
}

// ------------------------------------------------------------------------------------------------
SQMOD_NODISCARD LightObj PvManager::GetEntryWithID(SQInteger id)
{
    return LightObj(SqTypeIdentity< SqPvEntry >{}, SqVM(), GetValidEntry(id));
}

// ------------------------------------------------------------------------------------------------
SQMOD_NODISCARD LightObj PvManager::GetEntryWithTag(StackStrF & tag)
{
    return LightObj(SqTypeIdentity< SqPvEntry >{}, SqVM(), GetValidEntryWithTag(tag.CacheHash()));
}

// ------------------------------------------------------------------------------------------------
bool PvManager::HaveEntryWithID(SQInteger id)
{
    for (const auto & e : m_Entries) // NOLINT(readability-use-anyofallof)
    {
        if (e.first.mID == id)
        {
            return true;
        }
    }
    // Not found
    return false;
}

// ------------------------------------------------------------------------------------------------
bool PvManager::HaveEntryWithTag(StackStrF & tag)
{
    const size_t h = tag.ToHash();
    for (const auto & e : m_Entries) // NOLINT(readability-use-anyofallof)
    {
        if (e.first.mHash == h)
        {
            return true;
        }
    }
    // Not found
    return false;
}

// ------------------------------------------------------------------------------------------------
SQMOD_NODISCARD LightObj PvManager::GetClassWithID(SQInteger id)
{
    return LightObj(SqTypeIdentity< SqPvClass >{}, SqVM(), GetValidClass(id));
}

// ------------------------------------------------------------------------------------------------
SQMOD_NODISCARD LightObj PvManager::GetClassWithTag(StackStrF & tag)
{
    return LightObj(SqTypeIdentity< SqPvClass >{}, SqVM(), GetValidClassWithTag(tag.CacheHash()));
}

// ------------------------------------------------------------------------------------------------
bool PvManager::HaveClassWithID(SQInteger id)
{
    for (const auto & c : m_Classes) // NOLINT(readability-use-anyofallof)
    {
        if (c.first.mID == id)
        {
            return true;
        }
    }
    // Not found
    return false;
}

// ------------------------------------------------------------------------------------------------
bool PvManager::HaveClassWithTag(StackStrF & tag)
{
    const size_t h = tag.ToHash();
    for (const auto & c : m_Classes) // NOLINT(readability-use-anyofallof)
    {
        if (c.first.mHash == h)
        {
            return true;
        }
    }
    // Not found
    return false;
}

// ------------------------------------------------------------------------------------------------
SQMOD_NODISCARD LightObj PvManager::GetUnitWithID(SQInteger id)
{
    return LightObj(SqTypeIdentity< SqPvUnit >{}, SqVM(), GetValidUnit(id));
}

// ------------------------------------------------------------------------------------------------
SQMOD_NODISCARD LightObj PvManager::GetUnitWithTag(StackStrF & tag)
{
    return LightObj(SqTypeIdentity< SqPvUnit >{}, SqVM(), GetValidUnitWithTag(tag.CacheHash()));
}

// ------------------------------------------------------------------------------------------------
bool PvManager::HaveUnitWithID(SQInteger id)
{
    for (const auto & u : m_Units) // NOLINT(readability-use-anyofallof)
    {
        if (u.first.mID == id)
        {
            return true;
        }
    }
    // Not found
    return false;
}

// ------------------------------------------------------------------------------------------------
bool PvManager::HaveUnitWithTag(StackStrF & tag)
{
    const size_t h = tag.ToHash();
    for (const auto & u : m_Units) // NOLINT(readability-use-anyofallof)
    {
        if (u.first.mHash == h)
        {
            return true;
        }
    }
    // Not found
    return false;
}

// ------------------------------------------------------------------------------------------------
void PvManager::RemoveEntryWithID(SQInteger id)
{
    ModifyEntries();

    // Remove this entry from the units
    for (const auto & u : m_Units)
    {
        u.second->mPrivileges.erase(id);
    }
    // Remove this entry from the classes
    for (const auto & c : m_Units)
    {
        c.second->mPrivileges.erase(id);
    }
    // Finally remove it from the list
    m_Entries.erase(PvIdentity(id));
}

// ------------------------------------------------------------------------------------------------
void PvManager::RemoveEntryWithTag(StackStrF & tag)
{
    ModifyEntries();

    PvEntry & e = *GetValidEntryWithTag(tag);
    // Remove this entry from the units
    for (const auto & u : m_Units)
    {
        u.second->mPrivileges.erase(e.mID);
    }
    // Remove this entry from the classes
    for (const auto & c : m_Units)
    {
        c.second->mPrivileges.erase(e.mID);
    }
    // Finally remove it from the list
    auto itr = std::find_if(m_Entries.cbegin(), m_Entries.cend(),
                            [h = tag.CacheHash().GetHash()](PvEntry::List::const_reference e) -> bool { return e.first.mHash == h; });
    // Was this unit found?
    if (itr != m_Entries.end())
    {
        m_Entries.erase(itr);
    }
}

// ------------------------------------------------------------------------------------------------
void PvManager::RemoveClassWithID(SqPvClass & sub, SQInteger id)
{
    ModifyClasses();

    PvClass::Ref cls = sub.mI.lock();
    // Remove this entry from units
    for (const auto & u : m_Units)
    {
        if (u.second->mClass.lock().get() == cls.get())
        {
            AutoAssign< bool > aag(m_LockUnits, false, true);
            u.second->AssignClass(cls);
        }
    }
    // Remove this entry from classes
    for (const auto & c : m_Classes)
    {
        if (!c.second->mParent.expired() && c.second->mParent.lock().get() == cls.get())
        {
            AutoAssign< bool > aag(m_LockClasses, false, true);
            c.second->AssignParent(cls);
        }
    }
    // Finally remove it from the list
    m_Classes.erase(PvIdentity(id));
}

// ------------------------------------------------------------------------------------------------
void PvManager::RemoveClassWithTag(SqPvClass & sub, StackStrF & tag)
{
    ModifyClasses();

    PvClass::Ref cls = sub.mI.lock();
    // Remove this class from units
    for (const auto & u : m_Units)
    {
        if (u.second->mClass.lock().get() == cls.get())
        {
            AutoAssign< bool > aag(m_LockUnits, false, true);
            u.second->AssignClass(cls);
        }
    }
    // Remove this class from classes
    for (const auto & c : m_Classes)
    {
        if (!c.second->mParent.expired() && c.second->mParent.lock().get() == cls.get())
        {
            AutoAssign< bool > aag(m_LockClasses, false, true);
            c.second->AssignParent(cls);
        }
    }
    // Finally remove it from the list
    auto itr = std::find_if(m_Classes.cbegin(), m_Classes.cend(),
                            [h = tag.CacheHash().GetHash()](PvClass::List::const_reference e) -> bool { return e.first.mHash == h; });
    // Was this unit found?
    if (itr != m_Classes.end())
    {
        m_Classes.erase(itr);
    }
}

// ------------------------------------------------------------------------------------------------
void PvManager::RemoveUnitWithID(SQInteger id)
{
    ModifyUnits();

    // Remove this class from classes
    for (const auto & c : m_Classes)
    {
        c.second->mUnits.erase(PvIdentity(id));
    }
    // Finally remove it from the list
    m_Units.erase(PvIdentity(id));
}

// ------------------------------------------------------------------------------------------------
void PvManager::RemoveUnitWithTag(StackStrF & tag)
{
    ModifyUnits();

    PvUnit & u = *GetValidUnitWithTag(tag);
    // Remove this class from classes
    for (const auto & c : m_Classes)
    {
        c.second->mUnits.erase(PvIdentity(u.mID));
    }
    // Finally remove it from the list
    auto itr = std::find_if(m_Units.cbegin(), m_Units.cend(),
                            [h = tag.CacheHash().GetHash()](PvUnit::List::const_reference e) -> bool { return e.first.mHash == h; });
    // Was this unit found?
    if (itr != m_Units.end())
    {
        m_Units.erase(itr);
    }
}

// ------------------------------------------------------------------------------------------------
void PvManager::EachEntryID(Object & ctx, Function & func)
{
    // Prevent any changes to entries during this operation
    AutoAssign< bool > aag(m_LockEntries, false, true);
    // Iterate entries and forward the ID to the callback
    for (const auto & e : m_Entries)
    {
        func(ctx, e.second->mID);
    }
}

// ------------------------------------------------------------------------------------------------
void PvManager::EachClassID(Object & ctx, Function & func)
{
    // Prevent any changes to classes during this operation
    AutoAssign< bool > aag(m_LockClasses, false, true);
    // Iterate classes and forward the ID to the callback
    for (const auto & c : m_Classes)
    {
        func(ctx, c.second->mID);
    }
}

// ------------------------------------------------------------------------------------------------
void PvManager::EachUnitID(Object & ctx, Function & func)
{
    // Prevent any changes to units during this operation
    AutoAssign< bool > aag(m_LockUnits, false, true);
    // Iterate units and forward the ID to the callback
    for (const auto & u : m_Units)
    {
        func(ctx, u.second->mID);
    }
}

// ------------------------------------------------------------------------------------------------
extern void Register_Privilege_Class(HSQUIRRELVM vm, Table & ns);
extern void Register_Privilege_Entry(HSQUIRRELVM vm, Table & ns);
extern void Register_Privilege_Unit(HSQUIRRELVM vm, Table & ns);

// ================================================================================================
void Register_Privilege(HSQUIRRELVM vm)
{
    Table ns(vm);

    // --------------------------------------------------------------------------------------------
    Register_Privilege_Class(vm, ns);
    Register_Privilege_Entry(vm, ns);
    Register_Privilege_Unit(vm, ns);

    // --------------------------------------------------------------------------------------------
    ns.Bind(_SC("Manager"),
        Class< PvManager, NoCopy< PvManager > >(vm, ManagerTn::Str)
        // Constructors
        .Ctor()
        .Ctor< StackStrF & >()
        // Meta-methods
        .SquirrelFunc(_SC("_typename"), &ManagerTn::Fn)
        .Func(_SC("_tostring"), &PvManager::ToString)
        // Core Properties
        .Prop(_SC("Tag"), &PvManager::GetTag, &PvManager::SetTag)
        .Prop(_SC("Data"), &PvManager::GetData, &PvManager::SetData)
        // Core Methods
        .FmtFunc(_SC("SetTag"), &PvManager::ApplyTag)
        .CbFunc(_SC("OnQuery"), &PvManager::SetOnQuery)
        .CbFunc(_SC("OnLost"), &PvManager::SetOnLost)
        .CbFunc(_SC("OnGained"), &PvManager::SetOnGained)
        // Member Methods
        .CbFunc(_SC("CreateEntry"), &PvManager::CreateEntry)
        .CbFunc(_SC("CreateClass"), &PvManager::CreateClass)
        .CbFunc(_SC("CreateUnit"), &PvManager::CreateUnit)
        .Func(_SC("GetEntry"), &PvManager::GetEntryWithID)
        .FmtFunc(_SC("GetEntryWithTag"), &PvManager::GetEntryWithTag)
        .Func(_SC("HaveEntry"), &PvManager::HaveEntryWithID)
        .FmtFunc(_SC("HaveEntryWithTag"), &PvManager::HaveEntryWithTag)
        .Func(_SC("GetClass"), &PvManager::GetClassWithID)
        .FmtFunc(_SC("GetClassWithTag"), &PvManager::GetClassWithTag)
        .Func(_SC("HaveClass"), &PvManager::HaveClassWithID)
        .FmtFunc(_SC("HaveClassWithTag"), &PvManager::HaveClassWithTag)
        .Func(_SC("GetUnit"), &PvManager::GetUnitWithID)
        .FmtFunc(_SC("GetUnitWithTag"), &PvManager::GetUnitWithTag)
        .Func(_SC("HaveUnit"), &PvManager::HaveUnitWithID)
        .FmtFunc(_SC("HaveUnitWithTag"), &PvManager::HaveUnitWithTag)
        .Func(_SC("RemoveUnit"), &PvManager::RemoveUnitWithID)
        .FmtFunc(_SC("RemoveUnitWithTag"), &PvManager::RemoveUnitWithTag)
        .Func(_SC("RemoveEntry"), &PvManager::RemoveEntryWithID)
        .FmtFunc(_SC("RemoveEntryWithTag"), &PvManager::RemoveEntryWithTag)
        .Func(_SC("RemoveClass"), &PvManager::RemoveClassWithID)
        .FmtFunc(_SC("RemoveClassWithTag"), &PvManager::RemoveClassWithTag)
        .FmtFunc(_SC("EachEntryID"), &PvManager::EachEntryID)
        .FmtFunc(_SC("EachClassID"), &PvManager::EachClassID)
        .FmtFunc(_SC("EachUnitID"), &PvManager::EachUnitID)
    );

    RootTable(vm).Bind(_SC("SqPrivilege"), ns);
}

} // Namespace:: SqMod