SWEP.StatCache = {}
SWEP.HookCache = {}
SWEP.StatScoreCache = {} -- used by cust menu
SWEP.MiscCache = {}

SWEP.ExcludeFromRawStats = {
    ["FullName"] = true,
    ["PrintName"] = true,
    ["Description"] = true,
}

SWEP.IntegerStats = {
    ["ClipSize"] = true,
    ["Num"] = true,
}

SWEP.AllowNegativeStats = {
    ["RecoilKick"] = true,
}

function SWEP:InvalidateCache()
    self.StatCache = {}
    self.HookCache = {}
    self.StatScoreCache = {}
    self.MiscCache = {}
    self.RecoilPatternCache = {}

    self.AutoSightPos = nil
    self.AutoSightAng = nil
end

function SWEP:RunHook(val, data)
    if !self.HookCache[val] then
        self.HookCache[val] = {}

        if self:GetTable()[val] then
            table.insert(self.HookCache[val], self:GetTable()[val])
        end

        for slot, slottbl in pairs(self.Attachments) do
            if !slottbl.Installed then continue end

            local atttbl = TacRP.GetAttTable(slottbl.Installed)

            if atttbl[val] then
                table.insert(self.HookCache[val], atttbl[val])
            end
        end
    end

    for _, chook in pairs(self.HookCache[val]) do
        local d = chook(self, data)
        if d != nil then
            data = d
        end
    end

    data = hook.Run("TacRP_" .. val, self, data) or data

    return data
end

function SWEP:GetBaseValue(val)
    local stat = self:GetTable()[val]

    local b = TacRP.GetBalanceMode()
    if b > 0 and self.BalanceStats != nil then
        if TacRP.BalanceDefaults[b] and TacRP.BalanceDefaults[b][val] != nil then
            stat = TacRP.BalanceDefaults[b][val]
        end
        for j = b, 1, -1 do
            if self.BalanceStats[b] and self.BalanceStats[b][val] != nil then
                stat = self.BalanceStats[b][val]
                break
            end
        end
    end

    if isnumber(stat) then
        if self.IntegerStats[val] then
            stat = math.ceil(stat)
        end
        if !self.AllowNegativeStats[val] then
            stat = math.max(stat, 0)
        end
    end

    return stat
end

function SWEP:GetValue(val, static, invert)

    local cachei = invert and 2 or 1

    if static == nil then
        static = self.StaticStats
    end

    local stat = nil

    -- Generate a cache if it doesn't exist already
    if !self.StatCache[val] or !self.StatCache[val][cachei] then

        self.StatCache[val] = self.StatCache[val] or {}

        stat = self:GetBaseValue(val)

        local modifiers = {
            ["stat"] = nil, -- return this unless hook is set
            ["hook"] = nil, -- if set, always call hook and use the following values
            ["func"] = {}, -- modifying functions
            ["set"] = stat, -- override and no prefix
            ["prio"] = 0, -- override priority
            ["add"] = 0,
            ["mul"] = 1,
        }

        -- local priority = 0

        if !self.ExcludeFromRawStats[val] then
            for slot, slottbl in pairs(self.Attachments) do
                if !slottbl.Installed then continue end

                local atttbl = TacRP.GetAttTable(slottbl.Installed)

                local att_priority = atttbl["Priority_" .. val] or 1

                if atttbl[val] != nil and att_priority > modifiers.prio then
                    -- stat = atttbl[val]
                    -- priority = att_priority
                    modifiers.set = atttbl[val]
                    modifiers.prio = att_priority
                end
            end
        end

        for slot, slottbl in pairs(self.Attachments) do
            if !slottbl.Installed then continue end

            local atttbl = TacRP.GetAttTable(slottbl.Installed)

            local att_priority = atttbl["Override_Priority_" .. val] or 1

            if atttbl["Override_" .. val] != nil and att_priority > modifiers.prio then
                -- stat = atttbl["Override_" .. val]
                -- priority = att_priority
                modifiers.set = atttbl["Override_" .. val]
                modifiers.prio = att_priority
            end

            if atttbl["Add_" .. val] then -- isnumber(stat) and
                -- stat = stat + atttbl["Add_" .. val] * (invert and -1 or 1)
                modifiers.add = modifiers.add + atttbl["Add_" .. val] * (invert and -1 or 1)
            end

            if atttbl["Mult_" .. val] then -- isnumber(stat) and
                if invert then
                    -- stat = stat / atttbl["Mult_" .. val]
                    modifiers.mul = modifiers.mul / atttbl["Mult_" .. val]
                else
                    -- stat = stat * atttbl["Mult_" .. val]
                    modifiers.mul = modifiers.mul * atttbl["Mult_" .. val]
                end
            end

            if atttbl["Func_" .. val] then
                table.insert(modifiers.func, atttbl["Func_" .. val])
            end
        end

        if isfunction(self["Func_" .. val]) then
            table.insert(modifiers.func, self["Func_" .. val])
        end

        -- Check for stat hooks. If any exist, we must call it whenever we try to get the stat.
        -- Cache this check so we don't unnecessarily call hook.Run a million times when nobody wants to hook us.
        if table.Count(hook.GetTable()["TacRP_Stat_" .. val] or {}) > 0 then
            modifiers.hook = true
        end

        -- Calculate the final value
        if isnumber(modifiers.set) then
            modifiers.stat = (modifiers.set + modifiers.add) * modifiers.mul
            if self.IntegerStats[val] then
                modifiers.stat = math.ceil(modifiers.stat)
            end
            if !self.AllowNegativeStats[val] then
                modifiers.stat = math.max(modifiers.stat, 0)
            end
        else
            modifiers.stat = modifiers.set
        end

        -- Cache our final value, presence of hooks, and summed modifiers
        self.StatCache[val][cachei] = modifiers
    end

    local cache = self.StatCache[val][cachei]
    if !static and (cache.hook or #cache.func > 0) then
        -- Run the hook
        -- Hooks are expected to modify "set", "prio", "add" and "mul", so we can do all calculations in the right order.
        local modifiers = {set = nil, prio = 0, add = 0, mul = 1}

        if #cache.func > 0 then
            for _, f in ipairs(cache.func) do
                f(self, modifiers)
            end
        end
        if cache.hook then
            hook.Run("TacRP_Stat_" .. val, self, modifiers)
            if !istable(modifiers) then modifiers = {set = nil, prio = 0, add = 0, mul = 1} end -- some hook isn't cooperating!
        end

        if modifiers.prio > cache.prio then
            stat = modifiers.set
        else
            stat = cache.set
        end

        if isnumber(stat) then
            if invert then
                stat = (stat - modifiers.add - cache.add) / modifiers.mul / cache.mul
            else
                stat = (stat + modifiers.add + cache.add) * modifiers.mul * cache.mul
            end

            if self.IntegerStats[val] then
                stat = math.ceil(stat)
            end
            if !self.AllowNegativeStats[val] then
                stat = math.max(stat, 0)
            end
        end
    else
        stat = cache.stat
    end

    return stat
end
