--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| 	Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]

ArcCW.AttachmentBlacklistTable = ArcCW.AttachmentBlacklistTable or {}

function ArcCW:PlayerCanAttach(ply, wep, attname, slot, detach)
    -- The global variable takes priority over everything
    if !ArcCW.EnableCustomization then return false end

    -- Attach and Detach require a player (usually the owner)
    if !IsValid(ply) then return false end

    -- Spectators taking off your attachments is funny, but also cursed
    if wep:GetOwner() != ply then return false end

    -- Allow hooks to block or force allow attachment usage
    local ret = hook.Run("ArcCW_PlayerCanAttach", ply, wep, attname, slot, detach)

    if ret == nil and engine.ActiveGamemode() == "terrortown" then
        local mode = ArcCW.ConVars["ttt_customizemode"]:GetInt()
        if mode == 1 and !ply.ArcCW_AllowCustomize then return false
        elseif mode == 2 and !ply.ArcCW_AllowCustomize and GetRoundState() == ROUND_ACTIVE then return false
        elseif mode == 3 and !ply.ArcCW_AllowCustomize and !ply:IsActiveTraitor() and !ply:IsActiveDetective() then return false end
    elseif ret == nil and ArcCW.ConVars["enable_customization"]:GetInt() <= 0 then
        return false
    end

    return (ret == nil and true) or ret
end

function ArcCW:GetAttsForSlot(slot, wep, random)
    local ret = {}

    for id, atttbl in pairs(ArcCW.AttachmentTable) do

        if !ArcCW:SlotAcceptsAtt(slot, wep, id) then continue end
        if random and (atttbl.NoRandom or (atttbl.RandomWeight or 1) <= 0) then continue end

        table.insert(ret, id)
    end

    return ret
end

function ArcCW:GetAttList(name, filter)
    if self.AttachmentCachedLists[name] then return self.AttachmentCachedLists[name] end
    self.AttachmentCachedLists[name] = {}
    for k, v in pairs(self.AttachmentTable) do
        local k2, v2 = filter(k, v)
        if k2 then
            self.AttachmentCachedLists[name][k2] = v2
        end
    end
    return self.AttachmentCachedLists[name]
end

local function weighted_random(tbl, amt)
    amt = amt or 1
    local max = 0
    for k, v in pairs(tbl) do max = max + v end
    local ret = {}
    for i = 1, amt do
        local rng = math.random() * max
        for k, v in pairs(tbl) do
            rng = rng - v
            if rng <= 0 then
                ret[k] = (ret[k] or 0) + 1
                break
            end
        end
    end
    return ret
end

function ArcCW:RollRandomAttachment(all, wep, slot)
    for k, v in pairs(self:RollRandomAttachments(1, all, wep, slot)) do return k end
end

function ArcCW:RollRandomAttachments(amt, all, wep, slot)
    if wep == nil then
        -- cache the list results and randomly get one
        local tbl = self:GetAttList("random" .. (all and "_all" or ""), function(k, v)
            if ((!v.Free and !v.InvAtt) or all) and !v.NoRandom and (v.RandomWeight or 1) >= 0 then
                return k, v.RandomWeight or 1
            end
        end)
        return weighted_random(tbl, amt)
    else
        -- can't cache this because it is weapon-dependent
        local tbl = {}
        for id, atttbl in pairs(ArcCW.AttachmentTable) do
            if ((!atttbl.Free and !atttbl.InvAtt) or all) and (atttbl.NoRandom or (atttbl.RandomWeight or 1) <= 0) then continue end
            if !wep:CheckFlags(atttbl.ExcludeFlags, atttbl.RequireFlags) then continue end
            if slot != nil and !ArcCW:SlotAcceptsAtt(slot.Slot, wep, id) then continue end
            tbl[id] = atttbl.RandomWeight or 1
        end
        return weighted_random(tbl, amt)
    end
end

function ArcCW:SlotAcceptsAtt(slot, wep, att)
    local slots = {}

    if isstring(slot) then
        slots[slot] = true
    elseif istable(slot) then
        for _, i in pairs(slot) do
            slots[i] = true
        end
    end

    local atttbl = ArcCW.AttachmentTable[att]
    if !atttbl then return false end

    if atttbl.Hidden or atttbl.Blacklisted or ArcCW.AttachmentBlacklistTable[att] then return false end

    local Owner = wep.GetOwner and wep:GetOwner()
    if (atttbl.NotForNPC or atttbl.NotForNPCs) and Owner and Owner:IsNPC() then
        return false
    end
    if atttbl.AdminOnly and IsValid(Owner) and !(Owner:IsPlayer() and Owner:IsAdmin()) then return false end

    if wep.RejectAttachments and wep.RejectAttachments[att] then return false end

    if isstring(atttbl.Slot) then
        if !slots[atttbl.Slot] then return false end
    elseif istable(atttbl.Slot) then
        local yeah = false

        for _, i in pairs(atttbl.Slot) do
            if slots[i] then
                yeah = true
                break
            end
        end

        if !yeah then
            return false
        end
    end

    if wep and atttbl.Hook_Compatible then
        local compat = atttbl.Hook_Compatible(wep, {slot = slot, att = att})
        if compat == true then
            return true
        elseif compat == false then
            return false
        end
    end

    return true
end

function ArcCW:WeaponAcceptsAtt(wep, att)
    if wep.ArcCW and wep.Attachments then
        local tbl = {}
        for i, v in pairs(wep.Attachments) do
            table.insert(tbl, i)
        end
        return ArcCW:SlotAcceptsAtt(wep, wep, att)
    end
    return false
end

function ArcCW:PlayerGetAtts(ply, att)
    if !IsValid(ply) then return 0 end
    if ArcCW.ConVars["attinv_free"]:GetBool() then return 999 end

    if att == "" then return 999 end

    local atttbl = ArcCW.AttachmentTable[att]

    if !atttbl then return 0 end

    if atttbl.Free then return 999 end

    if !IsValid(ply) then return 0 end

    if !ply:IsAdmin() and atttbl.AdminOnly then
        return 0
    end

    if atttbl.InvAtt then att = atttbl.InvAtt end

    if !ply.ArcCW_AttInv then return 0 end

    if !ply.ArcCW_AttInv[att] then return 0 end

    return ply.ArcCW_AttInv[att]
end

function ArcCW:PlayerGiveAtt(ply, att, amt)
    amt = amt or 1

    if !IsValid(ply) then return end

    if !ply.ArcCW_AttInv then
        ply.ArcCW_AttInv = {}
    end

    local atttbl = ArcCW.AttachmentTable[att]

    if !atttbl then print("Invalid att " .. att) return end
    if atttbl.Free then return end -- You can't give a free attachment, silly
    if atttbl.AdminOnly and !(ply:IsPlayer() and ply:IsAdmin()) then return false end

    if atttbl.InvAtt then att = atttbl.InvAtt end

    if ArcCW.ConVars["attinv_lockmode"]:GetBool() then
        if ply.ArcCW_AttInv[att] == 1 then return end
        ply.ArcCW_AttInv[att] = 1
    else
        ply.ArcCW_AttInv[att] = (ply.ArcCW_AttInv[att] or 0) + amt
    end
end

function ArcCW:PlayerTakeAtt(ply, att, amt)
    amt = amt or 1

    if ArcCW.ConVars["attinv_lockmode"]:GetBool() then return end

    if !IsValid(ply) then return end

    if !ply.ArcCW_AttInv then
        ply.ArcCW_AttInv = {}
    end

    local atttbl = ArcCW.AttachmentTable[att]
    if !atttbl or atttbl.Free then return end

    if atttbl.InvAtt then att = atttbl.InvAtt end

    ply.ArcCW_AttInv[att] = ply.ArcCW_AttInv[att] or 0

    if ply.ArcCW_AttInv[att] < amt then
        return false
    end

    ply.ArcCW_AttInv[att] = ply.ArcCW_AttInv[att] - amt
    if ply.ArcCW_AttInv[att] <= 0 then
        ply.ArcCW_AttInv[att] = nil
    end
    return true
end

if CLIENT then

local function postsetup(wpn)
    if wpn.SetupModel then
        wpn:SetupModel(true)
        if wpn:GetOwner() == LocalPlayer() then
            wpn:SetupModel(false)
        end
        wpn:AdjustAtts()
    else
        timer.Simple(0.1, function()
            postsetup(wpn)
        end)
    end
end

net.Receive("arccw_networkatts", function(len, ply)
   local wpn = net.ReadEntity()
    if !IsValid(wpn) then return end
    if !wpn.ArcCW then return end

    local attnum = net.ReadUInt(8)
    wpn.Attachments = wpn.Attachments or {}
    wpn.SubSlotCount = 0

    for i = 1, attnum do
        local attid = net.ReadUInt(ArcCW.GetBitNecessity())

        wpn.Attachments[i] = wpn.Attachments[i] or {}

        if attid == 0 then
            if !istable(wpn.Attachments[i]) then continue end
            wpn.Attachments[i].Installed = nil
            continue
        end

        local att = ArcCW.AttachmentIDTable[attid]
        wpn.Attachments[i].Installed = att

        if wpn.Attachments[i].SlideAmount then
            wpn.Attachments[i].SlidePos = net.ReadFloat()
        end

        if ArcCW.AttachmentTable[att].ToggleStats then
            wpn.Attachments[i].ToggleNum = net.ReadUInt(8)
        end

        wpn:AddSubSlot(i, att)
    end

    wpn.CertainAboutAtts = true

    postsetup(wpn)
end)

net.Receive("arccw_sendattinv", function(len, ply)
    if !IsValid(LocalPlayer()) then return end -- This might be called before we are valid
    LocalPlayer().ArcCW_AttInv = {}

    local count = net.ReadUInt(32)

    for i = 1, count do
        local attid = net.ReadUInt(ArcCW.GetBitNecessity())
        local acount = net.ReadUInt(32)

        local att = ArcCW.AttachmentIDTable[attid]

        LocalPlayer().ArcCW_AttInv[att] = acount
    end

    -- This function will not exist until initialized (by having an ArcCW weapon exist)!
    -- It also obviously needs menu2 open
    if ArcCW.InvHUD_FormAttachmentSelect and IsValid(ArcCW.InvHUD) and IsValid(ArcCW.InvHUD_Menu2) then
        ArcCW.InvHUD_FormAttachmentSelect()
    end
end)

net.Receive("arccw_sendatthp", function(len, ply)
    local wpn = LocalPlayer():GetActiveWeapon()

    while net.ReadBool() do
        local slot = net.ReadUInt(8)
        local hp = net.ReadFloat()

        wpn.Attachments[slot].HP = hp
    end
end)

elseif SERVER then

hook.Add("PlayerDeath", "ArcCW_DeathAttInv", function(ply)
    ply.ArcCW_AttInv = ply.ArcCW_AttInv or {}
    if !table.IsEmpty(ply.ArcCW_AttInv)
            and ArcCW.ConVars["attinv_loseondie"]:GetInt() >= 2
            and !ArcCW.ConVars["attinv_free"]:GetBool() then
        local boxEnt = ents.Create("arccw_att_dropped")
        boxEnt:SetPos(ply:GetPos() + Vector(0, 0, 4))
        boxEnt.GiveAttachments = ply.ArcCW_AttInv
        boxEnt:Spawn()
        boxEnt:SetNWString("boxname", ply:GetName() .. "'s Death Box")
        local count = 0
        for i, v in pairs(boxEnt.GiveAttachments) do count = count + v end
        boxEnt:SetNWInt("boxcount", count)
    end
end)

hook.Add("PlayerSpawn", "ArcCW_SpawnAttInv", function(ply, trans)
    if trans then return end

    if ArcCW.ConVars["attinv_loseondie"]:GetInt() >= 1 then
        ply.ArcCW_AttInv = {}
    end
    local amt = ArcCW.ConVars["attinv_giveonspawn"]:GetInt()
    if amt > 0 then
        local giv = ArcCW:RollRandomAttachments(amt)
        for k, v in pairs(giv) do
            ArcCW:PlayerGiveAtt(ply, k, v)
        end
    end
    ArcCW:PlayerSendAttInv(ply)
end)

net.Receive("arccw_rqwpnnet", function(len, ply)
    local wpn = net.ReadEntity()

    if !wpn.ArcCW then return end

    wpn:RecalcAllBuffs()
    wpn:NetworkWeapon(ply)
end)

net.Receive("arccw_slidepos", function(len, ply)
    local wpn = ply:GetActiveWeapon()

    local slot = net.ReadUInt(8)
    local pos = net.ReadFloat()

    if !wpn.ArcCW then return end

    if !wpn.Attachments[slot] then return end

    wpn.Attachments[slot].SlidePos = pos
end)


net.Receive("arccw_togglenum", function(len, ply)
    local wpn = ply:GetActiveWeapon()

    local slot = net.ReadUInt(8)
    local num = net.ReadUInt(8)

    if !wpn.ArcCW then return end

    if !wpn.Attachments[slot] then return end

    wpn.Attachments[slot].ToggleNum = num

    wpn:AdjustAtts()
    wpn:NetworkWeapon()
    wpn:SetupModel(false)
    wpn:SetupModel(true)
end)


net.Receive("arccw_asktoattach", function(len, ply)
    local wpn = ply:GetActiveWeapon()

    local slot = net.ReadUInt(8)
    local attid = net.ReadUInt(24)

    local att = ArcCW.AttachmentIDTable[attid]

    if !wpn.ArcCW then return end
    if !wpn.Attachments[slot] then return end
    if !att then return end

    wpn:Attach(slot, att)
end)

net.Receive("arccw_asktodetach", function(len, ply)
    local wpn = ply:GetActiveWeapon()

    local slot = net.ReadUInt(8)

    if !wpn.ArcCW then return end
    if !wpn.Attachments[slot] then return end

    wpn:Detach(slot)
end)

net.Receive("arccw_asktodrop", function(len, ply)

    local attid = net.ReadUInt(24)
    local att = ArcCW.AttachmentIDTable[attid]

    if ArcCW.ConVars["attinv_free"]:GetBool() then return end
    if ArcCW.ConVars["attinv_lockmode"]:GetBool() then return end
    if ArcCW.ConVars["enable_customization"]:GetInt() < 0 then return end
    if !ArcCW.ConVars["enable_dropping"]:GetBool() then return end

    if !att then return end

    local atttbl = ArcCW.AttachmentTable[att]

    if !atttbl then return end
    if atttbl.Free then return end
    if ArcCW:PlayerGetAtts(ply, att) < 1 then return end

    -- better to do it like this in case you don't want to generate the attachment entities
    local ent = ents.Create("arccw_att_base")
    if !IsValid(ent) then return end
    ent:SetPos(ply:EyePos() + ply:EyeAngles():Forward() * 32)

    ent:SetNWInt("attid", attid)

    ent.GiveAttachments = {[att] = 1}
    ent.Model = atttbl.DroppedModel or atttbl.Model or "models/Items/BoxSRounds.mdl"
    ent.Icon = atttbl.Icon
    ent.PrintName = atttbl.PrintName or att

    ent:Spawn()
    timer.Simple(0, function()
        local phys = ent:GetPhysicsObject()
        if phys:IsValid() then
            phys:SetVelocity(ply:EyeAngles():Forward() * 32 * math.max(phys:GetMass(), 4))
        end
    end)
    ArcCW:PlayerTakeAtt(ply, att, 1)
    ArcCW:PlayerSendAttInv(ply)
end)

if SERVER then
    net.Receive("arccw_applypreset", function(len, ply)
        local wpn = net.ReadEntity()

        if wpn:GetOwner() != ply or !wpn.ArcCW then return end
        if ply.ArcCW_DisableAutosave or ply.ArcCW_Sandbox_RandomAtts then
            ply.ArcCW_Sandbox_RandomAtts = nil
            return
        end

        for k, v in pairs(wpn.Attachments) do
            wpn:Detach(k, true, true)
        end

        wpn.Attachments.BaseClass = nil -- AGHHHHHHHHHH
        for k, v in SortedPairs(wpn.Attachments) do
            local attid = net.ReadUInt(ArcCW.GetBitNecessity())

            local attname = ArcCW.AttachmentIDTable[attid or 0] or ""
            local atttbl = ArcCW.AttachmentTable[attname]
            if !atttbl then continue end

            wpn:Attach(k, attname, true, true)

            if net.ReadBool() then
                v.SlidePos = net.ReadFloat()
                v.SlidePos = atttbl.MountPositionOverride or v.SlidePos
            else
                v.SlidePos = 0.5
            end

            if atttbl.ToggleStats then
                v.ToggleNum = math.Clamp(net.ReadUInt(8), 1, #atttbl.ToggleStats)
            else
                v.ToggleNum = 1
            end
        end

        wpn:AdjustAtts()
        wpn:RefreshBGs()

        if ply.ArcCW_Sandbox_FirstSpawn then
            -- Curiously, RestoreAmmo has a sync delay only in singleplayer
            ply.ArcCW_Sandbox_FirstSpawn = nil
            wpn:RestoreAmmo()
        end

        wpn:NetworkWeapon()
        wpn:SetupModel(false)
        wpn:SetupModel(true)

        net.Start("arccw_applypreset")
            net.WriteEntity(wpn)
        net.Send(ply)
    end)
else
    net.Receive("arccw_applypreset", function()
        local wpn = net.ReadEntity()
        if !IsValid(wpn) then return end
        wpn:SavePreset("autosave")
    end)
end

function ArcCW:PlayerSendAttInv(ply)
    if ArcCW.ConVars["attinv_free"]:GetBool() then return end

    if !IsValid(ply) then return end

    if !ply.ArcCW_AttInv then return end

    net.Start("arccw_sendattinv")

    net.WriteUInt(table.Count(ply.ArcCW_AttInv), 32)

    for att, count in pairs(ply.ArcCW_AttInv) do
        local atttbl = ArcCW.AttachmentTable[att]
        local attid = atttbl.ID
        net.WriteUInt(attid, ArcCW.GetBitNecessity())
        net.WriteUInt(count, 32)
    end

    net.Send(ply)
end

end
