local SLE, T, E, L, V, P, G = unpack(select(2, ...)) 
local BI = SLE:NewModule('BagInfo', 'AceHook-3.0', 'AceEvent-3.0', 'AceTimer-3.0')
local B = E:GetModule('Bags')
local _G = _G
local byte = string.byte

local updateTimer
BI.containers = {}
BI.infoArray = {}
BI.equipmentMap = {}
local EquipmentManager_UnpackLocation = EquipmentManager_UnpackLocation
local C_EquipmentSet = C_EquipmentSet

local function Utf8Sub(str, start, numChars)
	local currentIndex = start
	while numChars > 0 and currentIndex <= #str do
		local char = byte(str, currentIndex)

		if char > 240 then
			currentIndex = currentIndex + 4
		elseif char > 225 then
			currentIndex = currentIndex + 3
		elseif char > 192 then
			currentIndex = currentIndex + 2
		else
			currentIndex = currentIndex + 1
		end

		numChars = numChars -1
	end

	return str:sub(start, currentIndex - 1)
end

local function MapKey(bag, slot)
	return T.format("%d_%d", bag, slot)
end

local quickFormat = {
	[0] = function(font, map) font:SetText() end,
	[1] = function(font, map) font:SetFormattedText("|cffffffaa%s|r", Utf8Sub(map[1], 1, 4)) end,
	[2] = function(font, map) font:SetFormattedText("|cffffffaa%s %s|r", Utf8Sub(map[1], 1, 4), Utf8Sub(map[2], 1, 4)) end,
	[3] = function(font, map) font:SetFormattedText("|cffffffaa%s %s %s|r", Utf8Sub(map[1], 1, 4), Utf8Sub(map[2], 1, 4), Utf8Sub(map[3], 1, 4)) end,
}

local function BuildEquipmentMap(clear)
	-- clear mapped names
	for k, v in T.pairs(BI.equipmentMap) do
		T.twipe(v)
	end

	if clear then return end

	local name, player, bank, bags, slot, bag, key
	local equipmentSetIDs = C_EquipmentSet.GetEquipmentSetIDs()

	for index = 1, C_EquipmentSet.GetNumEquipmentSets() do
		name = C_EquipmentSet.GetEquipmentSetInfo(equipmentSetIDs[index]);
		local equipmentSetID = C_EquipmentSet.GetEquipmentSetID(name)
		if equipmentSetID then
			local SetInfoTable = C_EquipmentSet.GetItemLocations(equipmentSetID)
			for _, location in T.pairs(SetInfoTable) do
				if T.type(location) == "number" and (location < -1 or location > 1) then
					player, bank, bags, _, slot, bag = EquipmentManager_UnpackLocation(location)
					if ((bank or bags) and slot and bag) then
						key = MapKey(bag, slot)
						BI.equipmentMap[key] = BI.equipmentMap[key] or {}
						T.tinsert(BI.equipmentMap[key], name)
					end
				end
			end
		end
	end
end

local function UpdateContainerFrame(frame, bag, slot)
	if (not frame.equipmentinfo) then
		frame.equipmentinfo = frame:CreateFontString(nil, "OVERLAY")
		frame.equipmentinfo:FontTemplate(E.media.font, 12, "THINOUTLINE")
		frame.equipmentinfo:SetWordWrap(true)
		frame.equipmentinfo:SetJustifyH('CENTER')
		frame.equipmentinfo:SetJustifyV('MIDDLE')
	end

	if (frame.equipmentinfo) then
		frame.equipmentinfo:SetAllPoints(frame)

		local key = MapKey(bag, slot)
		if BI.equipmentMap[key] then	
			quickFormat[#BI.equipmentMap[key] < 4 and #BI.equipmentMap[key] or 3](frame.equipmentinfo, BI.equipmentMap[key])
		else
			quickFormat[0](frame.equipmentinfo, nil)
		end
	end
end

local function UpdateBagInformation(clear)
	updateTimer = nil

	BuildEquipmentMap(clear)
	for _, container in T.pairs(BI.containers) do
		for _, bagID in T.ipairs(container.BagIDs) do
			for slotID = 1, T.GetContainerNumSlots(bagID) do
				UpdateContainerFrame(container.Bags[bagID][slotID], bagID, slotID)
			end
		end
	end
end

local function DelayUpdateBagInformation(event)
	-- delay to make sure multiple bag events are consolidated to one update.
	if not updateTimer then
		updateTimer = BI:ScheduleTimer(UpdateBagInformation, .25)
	end
end

function BI:ToggleSettings()
	if updateTimer then
		self:CancelTimer(updateTimer)
	end

	if E.private.sle.equip.setoverlay then
		self:RegisterEvent("EQUIPMENT_SETS_CHANGED", DelayUpdateBagInformation)
		self:RegisterEvent("BAG_UPDATE", DelayUpdateBagInformation)
		UpdateBagInformation()
	else
		self:UnregisterEvent("EQUIPMENT_SETS_CHANGED")
		self:UnregisterEvent("BAG_UPDATE") 
		UpdateBagInformation(true)
	end
end

function BI:Initialize()
	if not SLE.initialized or not E.private.bags.enable then return end

	T.tinsert(BI.containers, _G["ElvUI_ContainerFrame"])
	self:SecureHook(B, "OpenBank", function()
		self:Unhook(B, "OpenBank")
		T.tinsert(BI.containers, _G["ElvUI_BankContainerFrame"])
		BI:ToggleSettings()
	end)

	BI:ToggleSettings()
end

SLE:RegisterModule(BI:GetName())
