local L = AceLibrary("AceLocale-2.2"):new("Sanity2")

SanityItemCache = AceLibrary("AceAddon-2.0"):new("AceConsole-2.0", "AceEvent-2.0", "AceDB-2.0")
SanityItemCache:RegisterDB("SanityItemCacheDB", "SanityItemCacheDBPC")

local default_player_setup = {
	Bags = {},
	BagInfo = {},
	Money = 0
}

local function translate_index(self, k)
	local keys = SanityItemCache.translationKeys
	-- We really don't want to blow our stack on valid numeric indexes
	if tonumber(k) then
		setmetatable(self,nil)
		local v = self[k]
		setmetatable(self, SanityItemCache.translateMeta)
		return v		
	end
	
	if keys[k] then
		return self[keys[k]]
	elseif k ~= "Link" and self[3] and (
		k == "Name" or k == "ID" or k == "Quality" or k == "iLevel" or k == "MinLevel" or k == "Type" or
		k == "SubType" or k == "Stack" or k == "EquipLoc" or k == "Texture") then
		local n = GetItemInfo(self[3])
		if not n then
			self.invalid = true
		end
		local name, idstr, itemQuality, itemLevel, itemMinLevel, itemType, subType, stack, equipLoc, texture  = GetItemInfo(self[3]);
		if k == "Name" then
			self[k] = name or "Unknown"
			return self[k]
		elseif k == "ID" then
			self[k] = idstr or -1
			return self[k]
		elseif k == "Quality" then
			self[k] = itemQuality or 1
			return self[k]
		elseif k == "iLevel" then
			self[k] = itemLevel or 0
			return self[k]
		elseif k == "MinLevel" then
			self[k] = itemMinLevel or 0
			return self[k]
		elseif k == "Type" then
			self[k] = itemType or "Unknown"
			return self[k]
		elseif k == "SubType" then
			self[k] = subType or "Unknown"
			return self[k]
		elseif k == "Stack" then
			self[k] = stack or 1
			return self[k]
		elseif k == "EquipLoc" then
			self[k] = SANITY_CATEGORYNAMES[equipLoc] or equipLoc or L["INVALID"]
			return self[k]
		elseif k == "Texture" then
			self[k] = texture or "Interface\\Icons\\INV_Misc_QuestionMark"
			return self[k]
		end
	end
	return nil
end

SanityItemCache:RegisterDefaults("realm", { chars = {} })

function SanityItemCache:OnInitialize()
	self.char = UnitName("player")
	self:RegisterEvent("BAG_UPDATE")
	self:RegisterEvent("PLAYER_MONEY")
	self:RegisterEvent("BANKFRAME_OPENED")
	self:RegisterEvent("UNIT_INVENTORY_CHANGED")
	self:RegisterEvent("ADDON_LOADED")
	self:RegisterEvent("PLAYER_LOGOUT")
	self.translationKeys = {["Count"] = 1, ["Bag"] = 2, ["Link"] = 3, ["Soulbound"] = 4, ["Slot"] = 5}
	self.translateMeta = {__index = translate_index}

	if not self.db.realm.chars[self.char] then
		self.db.realm.chars[self.char] = {}
		for k,v in pairs(default_player_setup) do
			self.db.realm.chars[self.char][k] = {}
		end
	end
	local upgraded = false
	for k,v in pairs(self.db.realm.chars) do
		if v.BagItems then
			upgraded = true
			v.BagItems = nil
			v.MailItems = nil
			v.EquipmentItems = nil
			v.BankItems = nil
			v.Bags = {}
			v.BagInfo = {}
		end
		if v.Bags and type(v.Bags) == "table" and #v.Bags > 0 then
			for k2,v2 in pairs(v.Bags) do
				if type(v2) ~= "table" then
					v.BagItems = nil
					v.MailItems = nil
					v.EquipmentItems = nil
					v.BankItems = nil
					v.Bags = {}
					v.BagInfo = {}
					upgraded = true
					break
				end
			end
		end
		v.BagInfo = v.BagInfo or {}
	end
	if upgraded then
		Sanity:Print("Please note that Sanity has undergone a database reorganization and your old item data is no longer available. Please check item locations for each character you wish to have item data for.")
	end
	self.INVENTORY_BAG_ID = -99
	self.MAILBOX_BAG_ID = -98
	SanityItemCache:PLAYER_MONEY()
	self.CacheInited = true
end

function SanityItemCache:GetBagNameFor(bagID)
	if bagID == self.INVENTORY_BAG_ID then return L["Wearing"] end
	if bagID == self.MAILBOX_BAG_ID then return L["Mailbox"] end
	
	if bagID == -1 then return L["Bank"] end
	if bagID == 5 then return L["Bank"] end
	if bagID == 6 then return L["Bank"] end
	if bagID == 7 then return L["Bank"] end
	if bagID == 8 then return L["Bank"] end
	if bagID == 9 then return L["Bank"] end
	if bagID == 10 then return L["Bank"] end
	if bagID == 11 then return L["Bank"] end
	
	if bagID == 0 then return L["Bags"] end
	if bagID == 1 then return L["Bags"] end
	if bagID == 2 then return L["Bags"] end
	if bagID == 3 then return L["Bags"] end
	if bagID == 4 then return L["Bags"] end
	
	if bagID == KEYRING_CONTAINER then return L["Keyring"] end
	
	return "INVALID"
end

function SanityItemCache:NukeCharacter(char)
	local tt = {}
	for tchar,dummy in pairs(self.db.realm.chars) do
		if tchar ~= char then
			tt[tchar] = dummy
		end
	end
	self.db.realm.chars = tt	
end

function SanityItemCache:ADDON_LOADED()
	local upgraded = false
	for k,v in pairs(self.db.realm.chars) do
		if v.BagItems then
			upgraded = true
			v.BagItems = nil
			v.MailItems = nil
			v.EquipmentItems = nil
			v.BankItems = nil
			v.Bags = {}
			v.BagInfo = {}
		end
		if v.Bags and type(v.Bags) == "table" and #v.Bags > 0 then
			for k2,v2 in pairs(v.Bags) do
				if type(v2) ~= "table" then
					v.BagItems = nil
					v.MailItems = nil
					v.EquipmentItems = nil
					v.BankItems = nil
					v.Bags = {}
					v.BagInfo = {}
					upgraded = true
					break
				end
			end
		end
		v.BagInfo = v.BagInfo or {}
	end
	if upgraded then
		Sanity:Print("Please note that Sanity has undergone a database reorganization and your old item data is no longer available. Please check item locations for each character you wish to have item data for.")
	end
	
	for char,dummy in pairs(self.db.realm.chars) do
		for k,v in pairs(dummy.Bags) do
			for k2,v2 in pairs(v) do
				setmetatable(v2,{__index = translate_index})
			end
		end
	end
end

function SanityItemCache:PLAYER_LOGOUT()
	for charName, char in pairs(self.db.realm.chars) do
		for k,v in pairs(char.Bags) do
			local t = {}
			for k2,v2 in pairs(v) do
				tinsert(t, {
					[1] = v2[1],
					[2] = v2[2],
					[3] = v2[3],
					[4] = v2[4],
					[5] = v2[5]
				})
			end
			char.Bags[k] = t
		end
	end
end

function SanityItemCache:ItemIsUsable(item)
	if UnitName("player") == item["Owner"] then
		if item["Bag"] == self.MAILBOX_BAG_ID then return false end		-- We can't use items in the mail
		if item["Bag"] == self.INVENTORY_BAG_ID then return true end	-- We can always use items we're wearing
		if item["Bag"] >= 0 and item["Bag"] <= 4 then return true end	-- We can always use items in our bags
		return Sanity.BankIsVisible										-- We can use bank items if the bank is open
	end
	return false
end

function SanityItemCache:BANKFRAME_OPENED()
	SanityItemCache:ScanBag(-1)
	for i = 5, 11 do
		SanityItemCache:ScanBag(i)
	end
	Sanity.displayListDirty = true
	self:ScheduleEvent("SanityRefresh", function() Sanity:Refresh(); Sanity:Update() end, 0.50)
end

function SanityItemCache:UNIT_INVENTORY_CHANGED()
	self.db.realm.chars[self.char].Bags[self.INVENTORY_BAG_ID], self.db.realm.chars[self.char].BagInfo[self.INVENTORY_BAG_ID] = SanityItemCache:ScanEquipment(), {0,0}
	Sanity.displayListDirty = true
	Sanity.tooltipDirty = true
	self:ScheduleEvent("SanityRefresh", function() Sanity:Refresh(); Sanity:Update() end, 0.50)
end

function SanityItemCache:ScanAll()
	SanityItemCache:ScanBags()
	SanityItemCache:UNIT_INVENTORY_CHANGED()
end

function SanityItemCache:BAG_UPDATE()
	if not tonumber(arg1) then return end
	SanityItemCache:ScanBag(arg1)
	Sanity.displayListDirty = true
	Sanity.tooltipDirty = true
	self:ScheduleEvent("SanityRefresh", function() Sanity:Refresh(); Sanity:Update() end, 0.50)
end

function SanityItemCache:UnprepItem(itemName)
	for k, bag in pairs(self.db.realm.chars[self.char].Bags) do
		for k, item in pairs(bag) do
			if item["Name"] == itemName then
				item["Prepped"] = false
			end
		end
	end
end

function SanityItemCache:PLAYER_MONEY()
	self.db.realm.chars[self.char].Money = GetMoney()
end

function SanityItemCache:ScanBag(bagID)
	self.countDirty = true
	local scan = true
	if bagID >= 0 and bagID <= 4 then
		scan = Sanity.db.profile["ScanBag" .. bagID]
	elseif bagID == KEYRING_CONTAINER then
		scan = Sanity.db.profile.ScanKeyRing
	end
	if scan then
		Sanity:Debug("Scanning bag " .. bagID)
		local a, b = SanityItemCache:Scan(bagID)
		if a then
			self.db.realm.chars[self.char].Bags[bagID], self.db.realm.chars[self.char].BagInfo[bagID] = a, b
		end
	end
end

function SanityItemCache:GetBagData(char)
	local bags = {0, 1, 2, 3, 4}
	if not self.CacheInited then return 0,0 end
	return self:CountSlots(bags, char)
end

function SanityItemCache:GetBankData(char)
	local bags = {-1, 5, 6, 7, 8, 9, 10, 11}
	if not self.CacheInited then return 0,0 end
	return self:CountSlots(bags)
end
	
function SanityItemCache:CountSlots(bags, char)
	if not char then char = self.char end
	local c, cf = 0, 0
	for k, bag in ipairs(bags) do
		if self.db.realm.chars[char].BagInfo[bag] then
			c = c + self.db.realm.chars[char].BagInfo[bag].bag_slots
			cf = cf + self.db.realm.chars[char].BagInfo[bag].filled_slots
		end
	end
	return cf, c
end

function SanityItemCache:ScanBags()
	for i = 0, 4 do
		if Sanity.db.profile["ScanBag" .. i] then
			self:ScanBag(i)
		end
	end
	if Sanity.db.profile.ScanKeyRing then
		self:ScanBag(KEYRING_CONTAINER)
	end
end

function SanityItemCache:Scan(bag)
	local items = {}
	local bagData = {bag_slots = 0, filled_slots = 0}
	if GetContainerNumSlots(bag) == 0 then return nil end
	bagData.bag_slots = GetContainerNumSlots(bag)
	for slot = 1, GetContainerNumSlots(bag) do
		local link = GetContainerItemLink(bag, slot)
		if link then
			if bag ~= KEYRING_CONTAINER then
				bagData.filled_slots = (bagData.filled_slots or 0) + 1
			end
			local n, _, itemQuality, itemLevel, itemMinLevel, itemType, subType, stack, equipLoc, texture  = GetItemInfo(link);
			if itemQuality then
				Sanity_ItemTooltip:ClearLines()
				if bag == -1 then
					Sanity_ItemTooltip:SetInventoryItem("player", BankButtonIDToInvSlotID(slot, nil));
				else
					Sanity_ItemTooltip:SetBagItem(bag, slot)
				end

				local soulbound = 0
				if bag ~= KEYRING_CONTAINER then
					for i=1,Sanity_ItemTooltip:NumLines() do
						local obj = getglobal("Sanity_ItemTooltipTextLeft" .. i)
						if obj:GetText() == L["Soulbound"] or obj:GetText() == L["Quest Item"] then
							soulbound = 1
						end
					end
				else
					soulbound = 1
				end
				
				local texture, count, locked = GetContainerItemInfo(bag, slot);
				local _,_,itemID = string.find(link, "H(item:%-?%d+:%-?%d+:%-?%d+:%-?%d+:%-?%d+:%-?%d+:%-?%d+:%-?%d+)")
				local item = {count, bag, itemID, soulbound, slot}
				setmetatable(item,{__index = translate_index})
				item = self:PrepareItem(item)
				tinsert(items, item)
			end
		end
	end
	return items, bagData
end

function SanityItemCache:ScanEquipment()
	local items = {}
	for i = 1,19 do
		Sanity_ItemTooltip:SetInventoryItem("player", i)
		local soulbound = 0
		for i=1,Sanity_ItemTooltip:NumLines() do
			if Sanity_ItemTooltipTextLeft2:GetText() == L["Soulbound"] or Sanity_ItemTooltipTextLeft2:GetText() == L["Quest Item"] then
				soulbound = 1
			end
		end
		local link = GetInventoryItemLink("player", i)
		if link then
			local name = GetItemInfo(link)
			local _,_,itemID = string.find(link, "H(item:%-?%d+:%-?%d+:%-?%d+:%-?%d+:%-?%d+:%-?%d+:%-?%d+:%-?%d+)")
			local item = {GetInventoryItemCount("player", i), SanityItemCache.INVENTORY_BAG_ID, itemID, soulbound, i}
			setmetatable(item,{__index = translate_index})
			item = self:PrepareItem(item)
			tinsert(items, item)
		end
	end
	return items
end

function SanityItemCache:ScanMailbox()
	local inbox_items = GetInboxNumItems();
	local items = {}
	
	for i = 1, GetInboxNumItems() do
		--[[ Sanity_ItemTooltip:SetMailboxItem("player", i)
		local soulbound = 0
		for i=1,Sanity_ItemTooltip:NumLines() do
			if Sanity_ItemTooltipTextLeft2:GetText() == L["Soulbound"] or Sanity_ItemTooltipTextLeft2:GetText() == L["Quest Item"] then
				soulbound = 1
			end
		end
		]]--
		local soulbound = 0
		local link = GetInboxItemLink(i)
		if link then
			local itemName, itemLink, itemRarity, itemLevel, itemMinLevel, itemType, itemSubType, itemStackCount, itemEquipLoc, invTexture = GetItemInfo(link)
			local _,_,itemID = string.find(link, "H(item:%-?%d+:%-?%d+:%-?%d+:%-?%d+:%-?%d+:%-?%d+:%-?%d+:%-?%d+)")
			Sanity:Debug(itemID)
			local name, itemTexture, count, quality, canUse = GetInboxItem(i);
			local item = {count, SanityItemCache.MAILBOX_BAG_ID, itemID, soulbound, i}
			setmetatable(item,{__index = translate_index})
			item = self:PrepareItem(item)
			tinsert(items, item)
		end
	end
	self.db.realm.chars[self.char].Bags[self.MAILBOX_BAG_ID], self.db.realm.chars[self.char].BagInfo[self.MAILBOX_BAG_ID] = items, {0,0}
	-- return items
end

function SanityItemCache:PrepareItem(item)
	if item["Prepped"] then return end
	local skip = false
	if not item["Prepped"] then
		item["Location"] = self:GetBagNameFor(item["Bag"])
		item["Prepped"] = true
		if not skip then
			item["Stacks" ] = 1
			if item["Link"] then
				if item["Dangerous"] then
					item["Dangerous"] = false
				end	
				if not item["Type"] then
					item["Type"] = L["Unknown"]
					item["SubType"] = L["Unknown"]
					item["EquipLoc"] = L["Unknown"]
					item["Dangerous"] = true
				end
			end	
			item["Tags"] = Sanity:GetTagsFor(item["Name"])
			if not item.invalid then
				item["Tooltip"] = Sanity:GetTooltipString(item["Link"]) or ""
			else
				item["Tooltip"] = ""
			end
			item["TypeSort"] = "zzz"
			local catName = Sanity.db.profile.ItemCategories[item["Name"]]
			if catName then
				item["TypeSort"] = catName
			else
				if item["Type"] == item["SubType"] then
					item["TypeSort"] = item["SubType"]
					if item["EquipLoc"] and #item["EquipLoc"] > 0 then
						item["TypeSort"] = item["TypeSort"] .. " - " .. item["EquipLoc"]
					end
				elseif item["EquipLoc"] and #item["EquipLoc"] > 0 and Sanity.db.profile.UseSubTypes then
					item["TypeSort"] = item["Type"] .. " (" .. item["SubType"] .. ") - " .. item["EquipLoc"]
				elseif item["EquipLoc"] and #item["EquipLoc"] > 0 and not Sanity.db.profile.UseSubTypes then
					item["TypeSort"] = item["Type"] .. " - " .. item["EquipLoc"]
				else
					item["TypeSort"] = item["Type"]
				end
			end
			local catCatName = Sanity.db.profile.CategoryCategories[item["TypeSort"]]
			if catCatName then
				item["TypeSort"] = catCatName
			end
			if item["TypeSort"] == nil then
				item["TypeSort"] = "No Type"
			end
		end
	end
	return item
end