TFA.Attachments = TFA.Attachments or {}
TFA.Attachments.Atts = {}

TFA.Attachments.Colors = {
	["active"] = Color(252, 151, 50, 255),
	["error"] = Color(225, 0, 0, 255),
	["error_attached"] = Color(140, 80, 30, 255),
	["background"] = Color(15, 15, 15, 64),
	["primary"] = Color(245, 245, 245, 255),
	["secondary"] = Color(153, 253, 220, 255),
	["+"] = Color(128, 255, 128, 255),
	["-"] = Color(255, 128, 128, 255),
	["="] = Color(192, 192, 192, 255)
}

TFA.Attachments.UIPadding = 2
TFA.Attachments.IconSize = 64
TFA.Attachments.CategorySpacing = 128

if SERVER then
	util.AddNetworkString("TFA_Attachment_Set")
	util.AddNetworkString("TFA_Attachment_SetStatus")
	util.AddNetworkString("TFA_Attachment_Reload")
	util.AddNetworkString("TFA_Attachment_Request")

	local function UpdateWeapon(wep, ply)
		for category, data in pairs(wep.Attachments or {}) do
			if type(category) ~= "string" then
				net.Start("TFA_Attachment_Set")
				net.WriteEntity(wep)

				net.WriteUInt(category, 8)
				if data.atts and data.atts[data.sel] then
					net.WriteString(data.atts[data.sel])
				else
					net.WriteString("")
				end

				net.Send(ply)
			end
		end
	end

	net.Receive("TFA_Attachment_Request", function(len, ply)
		if not IsValid(ply) then return end
		local wep = net.ReadEntity()
		if not IsValid(wep) or not wep.IsTFAWeapon or not wep.HasInitAttachments or wep.AttachmentCount < 1 then return end
		local ctime = SysTime()

		local currentScheduleRequest = ply.__TFA_Base_Next_Attachment_Request or ctime
		local nextScheduleRequest = math.max(ctime + 0.2, currentScheduleRequest + 0.2)
		ply.__TFA_Base_Next_Attachment_Request = nextScheduleRequest

		if currentScheduleRequest <= ctime then
			UpdateWeapon(wep, ply)
		-- elseif currentScheduleRequest - ctime >= 10 then
			-- ply:Kick("TFA_Attachment_Request spam")
		else
			timer.Simple(nextScheduleRequest - ctime, function()
				if IsValid(ply) and IsValid(wep) then
					UpdateWeapon(wep, ply)
				end
			end)
		end
	end)

	net.Receive("TFA_Attachment_Set", function(len, ply)
		local wep = ply:GetWeapon(net.ReadString())
		if not IsValid(wep) or not wep.IsTFAWeapon then return end

		local cat = net.ReadUInt(8)
		local ind = net.ReadString()
		local status = wep:SetTFAAttachment(cat, ind, ply)

		net.Start("TFA_Attachment_SetStatus")
		net.WriteEntity(wep)
		net.WriteBool(status)

		if not status then
			if wep.Attachments and wep.Attachments[cat] then
				local data = wep.Attachments[cat]
				net.WriteUInt(cat, 8)

				if data.atts and data.atts[data.sel] then
					net.WriteString(data.atts[data.sel])
				else
					net.WriteString("")
				end
			end
		end

		net.Send(ply)
	end)
else
	sql.Query([[
		CREATE TABLE IF NOT EXISTS tfa_savedattachments (
			class VARCHAR(80) NOT NULL,
			atts TEXT NOT NULL,
			PRIMARY KEY (class)
		)
	]])

	net.Receive("TFA_Attachment_Set", function(len)
		local wep = net.ReadEntity()

		local cat = net.ReadUInt(8)
		local ind = net.ReadString()

		if IsValid(wep) and wep.SetTFAAttachment then
			wep:SetTFAAttachment(cat, ind, false)
		end
	end)

	net.Receive("TFA_Attachment_Reload", function(len)
		TFAUpdateAttachments()
	end)

	net.Receive("TFA_Attachment_SetStatus", function(len)
		local weapon = net.ReadEntity()
		if not IsValid(weapon) then return end
		local status = net.ReadBool()

		if status then
			weapon:SaveAttachments()
			return
		end
		surface.PlaySound("buttons/button2.wav")

		local cat = net.ReadUInt(8)
		local ind = net.ReadString()
		weapon:SetTFAAttachment(cat, ind, false)
	end)

	local function request(self)
		if self._TFA_Attachment_Request then return end
		if not self.HasInitAttachments or self.AttachmentCount < 1 then return end
		net.Start("TFA_Attachment_Request")
		net.WriteEntity(self)
		net.SendToServer()
		self._TFA_Attachment_Request = true
	end

	hook.Add("NotifyShouldTransmit", "TFA_AttachmentsRequest", function(self, notDormant)
		if not self.IsTFAWeapon or not notDormant then return end
		request(self)
	end)

	hook.Add("NetworkEntityCreated", "TFA_AttachmentsRequest", function(self)
		timer.Simple(0, function()
			if not IsValid(self) or not self.IsTFAWeapon then return end
			request(self)
		end)
	end)

	hook.Add("OnEntityCreated", "TFA_AttachmentsRequest", function(self)
		timer.Simple(0, function()
			if not IsValid(self) or not self.IsTFAWeapon then return end
			request(self)
		end)
	end)

	local LoadQuery = [[SELECT atts FROM tfa_savedattachments WHERE class = '%s']]
	function TFA.GetSavedAttachments(Weapon)
		if not IsValid(Weapon) or not Weapon.IsTFAWeapon then return end

		local data = sql.QueryRow(string.format(LoadQuery, sql.SQLStr(Weapon:GetClass(), true)))

		if data and data.atts then
			return util.JSONToTable(data.atts)
		end
	end

	local SaveQuery = [[REPLACE INTO tfa_savedattachments (class, atts) VALUES ('%s', '%s');]]
	function TFA.SetSavedAttachments(Weapon)
		if not IsValid(Weapon) or not Weapon.IsTFAWeapon or not next(Weapon.Attachments or {}) then return end

		local seltbl = {}
		for cat, catTbl in pairs(Weapon.Attachments or {}) do
			if cat ~= "BaseClass" and catTbl.atts then
				seltbl[cat] = catTbl.atts[catTbl.sel or -1] or ""
			end
		end

		return sql.Query(string.format(SaveQuery, sql.SQLStr(Weapon:GetClass(), true), sql.SQLStr(util.TableToJSON(seltbl), true)))
	end
end

local function basefunc(t, k)
	if k == "Base" then return end

	if t.Base then
		local bt = TFA.Attachments.Atts[t.Base]
		if bt then return bt[k] end
	end
end

function TFA.Attachments.SetupBaseTable(id, path)
	local ATTACHMENT = {}

	setmetatable(ATTACHMENT, {
		__index = basefunc
	})

	ATTACHMENT.ID = id

	ProtectedCall(function()
		hook.Run("TFABase_PreBuildAttachment", id, path, ATTACHMENT)
	end)

	return ATTACHMENT
end

function TFA.Attachments.Register(id, ATTACHMENT, path)
	if istable(id) then
		ATTACHMENT = id
		id = ATTACHMENT.ID
	end

	assert(istable(ATTACHMENT), "Invalid attachment argument provided")
	assert(isstring(id), "Invalid attachment ID provided")
	local size = table.Count(ATTACHMENT)

	if size == 0 or size == 1 and ATTACHMENT.ID ~= nil then
		local id2 = id or ATTACHMENT.ID

		if id2 then
			ErrorNoHalt("[TFA Base] Attempt to register an empty attachment " .. id2 .. "\n")
		else
			ErrorNoHalt("[TFA Base] Attempt to register an empty attachment\n")
		end

		ErrorNoHalt(debug.traceback() .. "\n")
		MsgC("\n")
		return
	end

	ProtectedCall(function()
		hook.Run("TFABase_BuildAttachment", id, path, ATTACHMENT)
	end)

	ATTACHMENT.ID = ATTACHMENT.ID or id

	if ATTACHMENT.ID and ATTACHMENT.ID ~= "base" then
		ATTACHMENT.Base = ATTACHMENT.Base or "base"
	end

	--[[if not TFA_ATTACHMENT_ISUPDATING and istable(ATTACHMENT.WeaponTable) then
		TFA.MigrateStructure(ATTACHMENT, ATTACHMENT.WeaponTable, id or "<attachment>", false)
	end]]

	ProtectedCall(function()
		hook.Run("TFABase_RegisterAttachment", id, ATTACHMENT)
	end)

	TFA.Attachments.Atts[ATTACHMENT.ID or ATTACHMENT.Name] = ATTACHMENT
end

function TFA.Attachments.RegisterFromTable(id, tbl, path)
	local status

	ProtectedCall(function()
		status = hook.Run("TFABase_ShouldLoadAttachment", id, path)
	end)

	if status == false then return end

	local ATTACHMENT = TFA.Attachments.SetupBaseTable(id)

	for k, v in pairs(tbl) do
		ATTACHMENT[k] = v
	end

	TFA.Attachments.Register(id, ATTACHMENT)
end

TFARegisterAttachment = TFA.Attachments.Register
TFA.Attachments.Path = "tfa/att/"
TFA.Attachments.Path_Batch = "tfa/attbatch/"
TFA_ATTACHMENT_ISUPDATING = false

local inheritanceCached = {}
local missingBaseWarningShown = {}

local function patchInheritance(t, basetbl)
	if t.Base and t.Base ~= "base" and not TFA.Attachments.Atts[t.Base] then
		if t.ID and not missingBaseWarningShown[t.ID] then
			missingBaseWarningShown[t.ID] = true

			print("[TFA Base] [!] Attachment '" .. t.ID .. "' depends on unknown attachment '" .. t.Base .. "'!")
		end

		t.Base = "base"
	end

	if not basetbl and t.Base then
		basetbl = TFA.Attachments.Atts[t.Base]

		if basetbl and istable(basetbl) and basetbl.ID and not inheritanceCached[basetbl.ID] then
			inheritanceCached[basetbl.ID] = true
			patchInheritance(basetbl)
		end
	end

	if not (basetbl and istable(basetbl)) then return end

	for k, v in pairs(t) do
		local baseT = basetbl[k]

		if istable(v) and baseT then
			patchInheritance(v, baseT)
		end
	end

	for k, v in pairs(basetbl) do
		if rawget(t, k) == nil then
			t[k] = v
		end
	end
end

function TFAUpdateAttachments(network)
	if SERVER and network ~= false then
		net.Start("TFA_Attachment_Reload")
		net.Broadcast()
	end

	TFA.AttachmentColors = TFA.Attachments.Colors --for compatibility
	TFA.Attachments.Atts = {}
	TFA_ATTACHMENT_ISUPDATING = true
	local tbl = file.Find(TFA.Attachments.Path .. "*base*", "LUA")
	local addtbl = file.Find(TFA.Attachments.Path .. "*", "LUA")

	for _, v in ipairs(addtbl) do
		if not string.find(v, "base") then
			table.insert(tbl, #tbl + 1, v)
		end
	end

	table.sort(tbl)

	for _, fname in ipairs(tbl) do
		local path = TFA.Attachments.Path .. fname
		local id = fname:lower():Replace(".lua", "")

		local status

		ProtectedCall(function()
			status = hook.Run("TFABase_ShouldLoadAttachment", id, path)
		end)

		if status ~= false then
			ATTACHMENT = TFA.Attachments.SetupBaseTable(id, path)

			if SERVER then
				AddCSLuaFile(path)
				include(path)
			else
				include(path)
			end

			TFA.Attachments.Register(id, ATTACHMENT, path)
			ATTACHMENT = nil
		end
	end

	local tbl2 = file.Find(TFA.Attachments.Path_Batch .. "*", "LUA")

	for _, fname in ipairs(tbl2) do
		local path = TFA.Attachments.Path_Batch .. fname

		if SERVER then
			AddCSLuaFile(path)
			include(path)
		else
			include(path)
		end
	end

	ProtectedCall(function()
		hook.Run("TFAAttachmentsLoaded")
	end)

	for _, v in pairs(TFA.Attachments.Atts) do
		patchInheritance(v)

		--[[if istable(v.WeaponTable) then
			TFA.MigrateStructure(v, v.WeaponTable, v.ID or "<attachment>", false)
		end]]
	end

	ProtectedCall(function()
		hook.Run("TFAAttachmentsInitialized")
	end)

	TFA_ATTACHMENT_ISUPDATING = false

	TFA.ATTACHMENTS_LOADED = true
end

if not VLL2_FILEDEF and TFA.ATTACHMENTS_LOADED then
	TFAUpdateAttachments(false)
end

concommand.Add("sv_tfa_attachments_reload", function(ply, cmd, args, argStr)
	if SERVER and (not IsValid(ply) or ply:IsAdmin()) then
		TFAUpdateAttachments()
	end
end, function() end, "Reloads all TFA Attachments", {FCVAR_SERVER_CAN_EXECUTE})
