local L = AceLibrary("AceLocale-2.2"):new("Sanity2")
Sanity = AceLibrary("AceAddon-2.0"):new("AceConsole-2.0", "AceEvent-2.0", "AceDB-2.0", "AceHook-2.1", "FuBarPlugin-2.0", "AceDebug-2.0")
local dewdrop = AceLibrary("Dewdrop-2.0")
local tabletop = AceLibrary("TableTop-1.0")

local function Sanity_IsTBC()
	local version = GetBuildInfo();
	return string.find(version, "^2%.")
end

local function tcopy(t)
	local tt = {}
	for k,v in pairs(t) do
		tt[k] = v
	end
	setmetatable(tt, getmetatable(t))
	return tt
end

function Sanity:HandleSlash(msg)
	Sanity:Print(msg)
end


Sanity:RegisterDB("SanityDB", "SanityDBPC")
Sanity:RegisterDefaults("profile", {
	ItemHeight = 21,
	filters = {
		["SOULBOUND"] = true,
		["NOT_BOUND"] = true,
		["BAGS"] = true,
		["BANK"] = false,
		["MAIL"] = false,
		["CHAR"] = false
	},
	fieldWidths = {},
	tabletop_db = {},
	filters_shown = true,
	HighlightTexture = "Interface\Buttons\UI-Common-MouseHilight",

	UseColors = true,
	ViewBankItems = false,
	
	ToggleOnVendor = false,
	ToggleOnMail = false,
	ToggleOnBank = false,
	SearchTooltips = true,
	UseDangerousItems = false,
	bg_r = 0,
	bg_g = 0.03137,
	bg_b = 0.1333,
	Alpha = 0.86,
	tooltipInfoColor = {r = 0.3, g = 0.75, b = 1, a = 1},
	tooltipCatInfoColor = {r = 0.3, g = 1, b = 0.75, a = 1},
	tooltipTagInfoColor = {r = 0.3, g = 1, b = 0.75, a = 1},
	UseHeaders = true,
	SortOrderBAGS = {1},
	SortOrderBANK = {1},
	SortOrderMAIL = {1},
	SortOrderCHAR = {1},
	SortOrderALL = {1},
	SaveVerbose = false,
	ShowBagIcon = true,
	UseSeparateSorts = false,
	CloseOnCombat = true,
	Scale = 1.0,
	Bagscale = 1,
	SearchOnKey = false,
	ShowCharItemTips = true,
	FocusOnShow = false,
	ItemCategories = {},
	CategoryCategories = {},
	ItemTags = {},
	CollapseStacks = true,
	ScanBag0 = true,
	ScanBag1 = true,
	ScanBag2 = true,
	ScanBag3 = true,
	ScanBag4 = true,
	ScanKeyRing = true,
	FuBarShowFreeSlots = false,
	UseSubTypes = false
})


function Sanity:OnInitialize()
  -- Called when the addon is loaded
  -- self:Register()
	self.SortReverse = false;
	self.FilterCategories = { "BAGS", "BANK", "MAIL", "CHAR" };
	self.qualities = SANITY_QUALITIES;
	self.activeCategory = {"BAGS"}
	self.sortTypes = {SANITY_COLUMNS_LEVEL = "number"}
	
	self.SecondarySortReverse = {
		["Quality"] = 1,
		["MinLevel"] = 1
	}

	self.splitBag = -1;
	self.splitIndex = -1;
	self.lastSort = {"Name"};
	self.filterString = "";
	self.activeItemList = {}
	self.itemList = {}
	self.character = UnitName("player")
	self.defaultColor = {242/255, 198/255, 3/255};
	self.needMailboxScan = false;
	self.ItemCache = SanityItemCache;
	self.filter_soulbound = 0;
	self.viewList = {}
	self.visibleItems = 0
	
	self.BagSlots = 0
	self.BankSlots = 0
	self.UsedBagSlots = 0
	self.UsedBankSlots = 0
	self.tabName = "SanityTabButton"
	self.tabletop = tabletop
	self.TooltipDataCache = {}
	self.displayListDirty = true
	
	Sanity.DraggerWidth = 10
	
	-- From MyBags
	self.SLOTCOLOR     = { 0.5, 0.5, 0.5 }
	self.AMMOCOLOR     = { 0.6, 0.6, 0.1 }
	self.SHARDCOLOR    = { 0.6, 0.3, 0.6 }
	self.ENCHANTCOLOR  = { 0.2, 0.2, 1.0 }
	self.ENGINEERCOLOR = { 0.6, 0.0, 0.0 }
	self.HERBCOLOR     = { 0.0, 0.6, 0.0 }
	self.KEYRINGCOLOR  = { 0.7, 0.4, 0.4 }
	
	self.BankBagsIndexes = { -2, -1, 5, 6, 7, 8, 9, 10, 11, 0, 1, 2, 3, 4 };
	self.BankIndexes = { 11, 10, 9, 8, 7, 6, 5, -1 };
	self.BagsIndexes = { -2, 4, 3, 2, 1, 0 };
	
	self.bagFilters = {
		{L["Bags"], "BAGS", function() end}, 
		{L["Bank"], "BANK"},
		{L["Mailbox"], "MAIL"},
		{L["Wearing"], "CHAR"}
	}
	self.bindFilters = {
		{L["Soulbound"], "SOULBOUND"},
		{L["Not Bound"], "NOT_BOUND"}
	}
	
	self.BUTTONHEIGHT = 16;
	self.ItemColors = {
		{157/255, 157/255, 157/255},
		{255/255, 255/255, 255/255},
		{30/255, 255/255, 0/255},
		{0/255, 112/255, 221/255},
		{163/255, 53/255, 238/255},
		{255/255, 128/255, 0/255}
	};
	
	self.ItemLinkColors = {
		"dddddddd",		-- Trash
		"ffffffff",		-- Common
		"ff0070dd",		-- Rare
		"ff1eff00",		-- Uncommon
		"ffa335ee",		-- Epic
	}
	
	SanityFrame:SetMinResize(340 + SanityFiltersFrame:GetWidth(), 300)

	Sanity:SetupFilters(self.bagFilters, SanityTabsFrame)
	Sanity:SetupFilters(self.bindFilters, SanityBindFrame)

	tabletop:Init(Sanity.db.profile.tabletop_db, SanitySortFrame, SanityDisplayFrame)
	tabletop:SetDataRowOffsets({
		tab_left = 5,
		tab_top = 0,
		tab_right = -4,
		tab_bottom = 0,
		data_left = 5,
		data_top = -5,
		data_right = -4,
		data_bottom = 0
		})
	tabletop:LayoutTabs(self.columns, 
		"SanityHeaderTabTemplate", 
		function()
			Sanity:PrintLockWarning()
			if not InCombatLockdown() then
				Sanity.displayListDirty = true
				if this.tabdata.key == "MinLevel" then
					Sanity:Sort({this.tabdata.key, "Quality", "Name"}, true)
				else
					Sanity:Sort({this.tabdata.key, "Name"}, true)
				end
				self.tabletop:ResizeTabContainer()
			end
		end 
	)
	Sanity:SetupItems()
	tabletop:LayoutInit()
	if not Sanity.db.profile.ShowBagIcon then
		SanityBagButton:Hide()
	end
	Sanity:SetupCategoryBox()
	SanityFrame:SetScale(self.db.profile.Scale)
	SanityBagButton:SetScale(self.db.profile.Bagscale)
end

function Sanity:OnEnable()
	self:RegisterEvent("BAG_UPDATE");
	self:RegisterEvent("BANKFRAME_CLOSED");
	self:RegisterEvent("BANKFRAME_OPENED");
	self:RegisterEvent("MERCHANT_SHOW");
	self:RegisterEvent("MERCHANT_CLOSED");
	self:RegisterEvent("MAIL_SHOW");
	self:RegisterEvent("MAIL_INBOX_UPDATE")
	self:RegisterEvent("MAIL_CLOSED");
	self:RegisterEvent("PLAYER_REGEN_DISABLED");
	self:RegisterEvent("PLAYER_REGEN_ENABLED");
	local funcs = {"SetAuctionItem","SetAuctionSellItem","SetBagItem","SetBuybackItem","SetCraftItem","SetHyperlink",
		"SetInboxItem","SetInventoryItem","SetLootItem","SetLootRollItem","SetMerchantCompareItem","SetMerchantItem",
		"SetQuestItem","SetQuestLogItem","SetSendMailItem","SetTradePlayerItem","SetTradeTargetItem", "SetTradeSkillItem"}
	for k,v in pairs(funcs) do
		if not self:IsHooked(GameTooltip, v) then
			self:SecureHook(GameTooltip, v, Sanity.AddTooltipInfo);
		end
	end
	
	SanityItemCache:ScanAll()
	self.IsEnabled = true
	
	if (not GetBindingAction("CTRL-T") or GetBindingAction("CTRL-T") == "") and (not GetBindingKey("SANITYITEMMENU") or GetBindingKey("SANITYITEMMENU") == "") then
		SetBinding("CTRL-T", "SANITYITEMMENU")
		SaveBindings(GetCurrentBindingSet())
	end
end

function Sanity:OnDisable()
	SanityBagButton:Hide()
	SanityItemCache:UnregisterEvent("BAG_UPDATE")
	SanityItemCache:UnregisterEvent("PLAYER_MONEY")
	SanityItemCache:UnregisterEvent("BANKFRAME_OPENED")
	SanityItemCache:UnregisterEvent("UNIT_INVENTORY_CHANGED")	
	self.IsEnabled = false
end

function Sanity:AddTooltipInfo()
	if not Sanity.db.profile.ShowCharItemTips then return end
	local name = GameTooltipTextLeft1:GetText()
	if not name then return end

	if GetMouseFocus() ~= Sanity.lastFocus then
		Sanity.lastFocus = nil
		Sanity.lastFocusName = nil
		if not GetMouseFocus():GetName() then return end
		local b = getglobal(GetMouseFocus():GetName() .. "IconTexture")
		if b then
			Sanity.lastFocus = GetMouseFocus()
			Sanity.lastFocusTexture = b:GetTexture()
			Sanity.lastFocusName = name
		end
	end

	local owners = Sanity:GetOwnersFor(name)
	if not owners then return end
	local tct = 0
	local ctct = 0
	for char,v in pairs(owners) do
		local str = char .. " " .. L["has"] .. " "
		local i = 0
		for loc, ct in pairs(v) do
			if i ~= 0 then
				str = str .. ", "
			end
			i = i + 1
			tct = tct + ct
			ctct = ctct + 1
			str = str .. "[" .. loc .. ": " .. ct .. "]"
		end
		GameTooltip:AddLine(str, Sanity.db.profile.tooltipInfoColor.r, Sanity.db.profile.tooltipInfoColor.g, Sanity.db.profile.tooltipInfoColor.b, 1)
	end
	if ctct > 1 then
		GameTooltip:AddLine(L["Total"] .. ": " .. tct, Sanity.db.profile.tooltipInfoColor.r, Sanity.db.profile.tooltipInfoColor.g, Sanity.db.profile.tooltipInfoColor.b, 1)
	end
	str = Sanity:GetCategoryFor(name)
	if str then
		GameTooltip:AddLine(L["Category"] .. ": " .. str, Sanity.db.profile.tooltipCatInfoColor.r, Sanity.db.profile.tooltipCatInfoColor.g, Sanity.db.profile.tooltipCatInfoColor.b, 1)
	end
	str = Sanity:GetTagsFor(name)
	if str ~= "" then
		GameTooltip:AddLine(L["Tags"] .. ": " .. str, Sanity.db.profile.tooltipTagInfoColor.r, Sanity.db.profile.tooltipTagInfoColor.g, Sanity.db.profile.tooltipTagInfoColor.b, 1)
	end
	if Sanity.lastFocusName and Sanity.lastFocus and GetBindingKey("SANITYITEMMENU") then
		GameTooltip:AddLine("<" .. GetBindingKey("SANITYITEMMENU") .. "> " .. L["to tag this item."])
	end
	GameTooltip:Show()
end

function Sanity:GetLockedInventoryItem()
	local lockedItem = nil
	for k, v in pairs(self.BankBagsIndexes) do
		for slot = 1, GetContainerNumSlots(v) do
			local texture, itemCount, locked, quality, readable = GetContainerItemInfo(v, slot);
			if locked and not lockedItem then
				lockedItem = {v, slot}
			elseif locked and lockedItem then
				self:Print(L["Unable to categorize cursor item: Multiple items are locked."])
				return
			end
		end
	end
	return lockedItem
end

function Sanity:DropItemOnCategoryLeft(cat)
	
	name = cat:GetText()
	local lockedItem = nil
	lockedItem = Sanity:GetLockedInventoryItem()
	if not lockedItem then return end
	local link = GetContainerItemLink(lockedItem[1], lockedItem[2])
	local itemName, itemString, itemQuality, itemMinLevel, itemType, itemSubType, itemStackCount, itemEquipLoc, itemTexture = GetItemInfo(link)
	if not itemName then
		itemName = L["Unknown"]
	end
	Sanity:Debug("Item name is " .. itemName .. ", custom name is " .. name)
	if cat.dataType == "Tag" then
		Sanity:TagItem(itemName, name)
	else
		self:SetItemCustomCategory(itemName, name)
	end
end

function Sanity:DropItemOnCategory(category)
	local header = getglobal(category:GetName() .. "SECTION_HEADER")
	local lockedItem = nil
	local catName = header:GetText()
	if header:IsVisible() then
		lockedItem = Sanity:GetLockedInventoryItem()
		if not lockedItem then
			return
		end
	end
	if lockedItem then
		local link = GetContainerItemLink(lockedItem[1], lockedItem[2])
		local itemName, itemString, itemQuality, itemMinLevel, itemType, itemSubType, itemStackCount, itemEquipLoc, itemTexture = GetItemInfo(link)
		if not itemName then itemName = L["Unknown"] end
		self:SetItemCustomCategory(itemName, catName)
		ClearCursor()
	end
end
                           
function Sanity:SetTooltipTag(tag)
	Sanity.db.profile.TooltipTag = tag
end

function Sanity:SetItemCustomCategory(item, cat)
	Sanity.db.profile.ItemCategories[item] = cat
	SanityItemCache:UnprepItem(item)
	Sanity.displayListDirty = true
	Sanity:SetupCategoryBox()
	Sanity:Refresh()
	Sanity:Update()
end

function Sanity:GetTagList()
	if Sanity.tagList and not Sanity.tagsDirty then return Sanity.tagList end
	local tags = {}
	for k,v in pairs(Sanity.db.profile.ItemTags) do
		for k1,v1 in pairs(v) do
			tags[k1] = 1
		end
	end
	local tList = {}
	for k,v in pairs(tags) do
		tinsert(tList, tostring(k))
	end
	table.sort(tList)
	Sanity.tagList = tList
	Sanity.tagsDirty = false
	return tList
end

function Sanity:GetLinkID(link)
	if Sanity_IsTBC() then return link end
	local itemId = nil;
	if ( type(link) == "string" ) then
		_,_, itemId = string.find(link, "(item:%d+:%d+:%d+:%d+)");
		return itemId;
	end
end

function Sanity:GetTagsFor(item)
	if not Sanity.db.profile.ItemTags[item] then return "" end
	local s = ""
	local tags = {}
	for k,v in pairs(Sanity.db.profile.ItemTags[item]) do
		tinsert(tags, k)
	end
	table.sort(tags)
	for k,v in pairs(tags) do
		if s == "" then
			s = v
		else
			s = s .. ", " .. v
		end
	end
	return s
end

function Sanity:GetCategoryFor(item)
	local name = self.db.profile.ItemCategories[item]
	if name then
		local catName = self.db.profile.CategoryCategories[name]
		if catName then return catName end
	end
	return name
end

function Sanity:ItemHasTag(item, tag)
	if Sanity.db.profile.ItemTags[item] then
		return Sanity.db.profile.ItemTags[item][tag] ~= nil
	end
	return false
end

function Sanity:TagItem(item, tag)
	if not Sanity.db.profile.ItemTags[item] then
		Sanity.db.profile.ItemTags[item] = {}
	end
	Sanity.db.profile.ItemTags[item][tag] = 1
	SanityItemCache:UnprepItem(item)
	Sanity.tagsDirty = true
	Sanity.displayListDirty = true
	Sanity:GetTagList()
	Sanity:SetupCategoryBox()
	Sanity:Refresh()
	Sanity:Update()
end

function Sanity:UntagItem(item, tag, noRefresh)
	if not Sanity.db.profile.ItemTags[item] then return end
	local tTags = {}
	for k,v in pairs(Sanity.db.profile.ItemTags[item]) do
		if k ~= tag then
			tTags[k] = 1
		end
	end
	Sanity.db.profile.ItemTags[item] = tTags
	if not noRefresh then
		SanityItemCache:UnprepItem(item)
		Sanity.displayListDirty = true
		Sanity.tagsDirty = true
		Sanity:GetTagList()
		Sanity:Refresh()
		Sanity:Update()
	end
end

function Sanity:GetItemsForTag(tag)
	if not Sanity.db.profile.ItemTags then 
		Sanity:Debug("Short-circuiting GetItemsForTag: " .. tag)
		return
	end
	local result = {}
	local resultCount = 0
	for k,v in pairs(Sanity.db.profile.ItemTags) do
		for k2, v2 in pairs(v) do
			if k2 == tag then
				tinsert(result, k)
			end
		end
	end
	return result
end

function Sanity:KillTag(tag)
	if not Sanity.db.profile.ItemTags then return end
	for k,v in pairs(Sanity.db.profile.ItemTags) do
		Sanity:UntagItem(k, tag, true)
	end
	Sanity.tagsDirty = true
	Sanity:GetTagList()
	Sanity:Refresh()
end
 
function Sanity:GetOwnersFor(itemName)
	if itemName == self.lastScanName and not self.tooltipDirty then
		return self.lastScanResults
	end
	self.tooltipDirty = false
	local results = {}
	if not SanityItemCache.db.realm.chars then
		return results
	end
	Sanity:Debug("Getting owners for " .. itemName)
	for charName, char in pairs(SanityItemCache.db.realm.chars) do
		for bagID, bagData in pairs(char.Bags) do
			for idx, item in ipairs(bagData) do
				if item["Name"] == itemName then
					local bagName = SanityItemCache:GetBagNameFor(bagID)
					results[charName] = results[charName] or {}
					results[charName][bagName] = (results[charName][bagName] or 0) + (item["OrigCount"] or item["Count"])
				end
			end
		end
	end
	self.lastScanResults = results
	self.lastScanName = itemName
	return results
end

function Sanity:UpdateColumns(tabs)
	if type(tabs) ~= "table" then return end 
	for k,tab in pairs(tabs) do
		Sanity.db.profile.fieldWidths[tab[1]] = tab[2]
	end
end

function Sanity:SetupFilters(tabs, parent)
	local name = parent:GetName() .. "FilterCheck"
	for k,v in pairs(tabs) do
		local f = CreateFrame("Button", name .. k, parent, "SanityFilterCheckboxTemplate")
		f:SetText(v[1])
		f.value = v[2]
		f.group_name = name
		f.group_size = table.getn(tabs)
		if k == 1 then
			f:SetPoint("TOPLEFT", parent, "TOPLEFT", 7, -5)	
		else
			f:SetPoint("TOPLEFT", getglobal(name .. (k-1)), "BOTTOMLEFT", 0, 0)
		end
		f:SetScript("OnClick", function()
				Sanity.displayListDirty = true;
				for i=1,this.group_size do
					local checkbox = getglobal(this.group_name .. i .. "Checkbox")
					checkbox:SetChecked(false)
				end
				getglobal(this:GetName() .. "Checkbox"):SetChecked(true)
				Sanity:Refresh()
			end)
		local checkbox = getglobal(name .. k .. "Checkbox")
		checkbox:SetChecked(Sanity.db.profile.filters[v[2]])
		checkbox:SetScript("OnClick", function() Sanity.displayListDirty = true; Sanity:Refresh() end)
	end
	parent:SetHeight(table.getn(tabs) * 18 + 8)
end

function Sanity:GetTooltipString(link)
	if not link then return nil end
	if not Sanity.db.profile.SearchTooltips then return nil end
	local name = GetItemInfo(link)
	if not name then name = "Unknown" end
	if self.TooltipDataCache[name] then
		return self.TooltipDataCache[name]
	end
	Sanity_ItemTooltip:ClearLines()
	Sanity_ItemTooltip:SetOwner(WorldFrame, "ANCHOR_NONE")
	Sanity_ItemTooltip:SetHyperlink(Sanity:GetLinkID(link))
	local s = nil
	for i=1,Sanity_ItemTooltip:NumLines() do
		local lText = getglobal("Sanity_ItemTooltipTextLeft" .. i)
		local rText = getglobal("Sanity_ItemTooltipTextRIght" .. i)
		if lText and lText:GetText() then
			if s then s = s .. " " .. lText:GetText() 
			else s = lText:GetText() end
		end
		if rText and rText:GetText() then
			if s then s = s .. " " .. rText:GetText() 
			else s = rText:GetText() end
		end
	end
	self.TooltipDataCache[name] = s
	return s
end

function Sanity:SetupItems()
	local ht = SanityDisplayFrame:GetHeight() - 9
	Sanity.tabletop:GetColumn("TypeSort"):SetWidth(Sanity.db.profile.ItemHeight)
	Sanity.visibleItems = math.floor(ht / Sanity.db.profile.ItemHeight)
	Sanity.tabletop:SetupRows({
		parent = SanityDisplayFrame,
		num = Sanity.visibleItems,
		height = Sanity.db.profile.ItemHeight,
		template = "SanityItemTemplate"
	})
	Sanity:Refresh(true)
end

function Sanity:SetItemCategory(val)
	if dewdrop:GetOpenedParent() then
		local item = Sanity.viewList[tonumber(getglobal(dewdrop:GetOpenedParent():GetParent():GetName() .. "ItemIndex"):GetText())]
		if item["Header"] then
			local haveSet = false
			for k,v in pairs(Sanity.db.profile.CategoryCategories) do
				if v == item["Header"] then
					Sanity.db.profile.CategoryCategories[k] = val
					haveSet = true
				end
			end
			if not haveSet then
				Sanity.db.profile.CategoryCategories[item["Header"]] = val
			end
			for k,v in pairs(Sanity.db.profile.ItemCategories) do
				if k == item["Header"] then
					Sanity.db.profile.ItemCategories[k] = val
				end
			end
		else
			Sanity.db.profile.ItemCategories[item["Name"]] = val
		end
	end			
	self.displayListDirty = true
	Sanity:Refresh()
	dewdrop:Close()
end

function Sanity:DeleteCategory(cat)
	for k,v in pairs(Sanity.db.profile.ItemCategories) do
		if v == cat then
			Sanity.db.profile.ItemCategories[k] = nil
		end
	end
	for k,v in pairs(Sanity.db.profile.CategoryCategories) do
		if v == cat then
			Sanity.db.profile.CategoryCategories[k] = nil
		end
	end
	self.displayListDirty = true
	Sanity:SetupCategoryBox()
	Sanity:Refresh()
end

function Sanity:GetExistingCategories()
	local existingCategoryNames = {}
	local existingCategories = {}
	local i = 0
	for k,v in pairs(Sanity.db.profile.ItemCategories) do
		if v then
			existingCategoryNames[v] = 1
		end
	end
	for k,v in pairs(Sanity.db.profile.CategoryCategories) do
		if v then
			existingCategoryNames[v] = 1
		end
	end
	local cats = {}
	for k,v in pairs(existingCategoryNames) do
		tinsert(cats, k)
	end
	return cats
end

function Sanity:UpdateBackground()
	local r = Sanity.db.profile.bg_r
	local g = Sanity.db.profile.bg_g
	local b = Sanity.db.profile.bg_b
	local a = Sanity.db.profile.Alpha
	local frames = {SanityFrame, SanityTabsFrame, SanityBindFrame, SanityDisplayFrame, SanityCategoriesFrame, SanitySortFrame,
									SanityHeaderFrame, SanityTitleFrame, SanityStatusBar, SanityFiltersFrame}
	for idx, frame in pairs(frames) do
		frame:SetBackdropColor(r,g,b,a);
	end
end

function Sanity:Toggle()
	if SanityFrame:IsVisible() then
		SanityFrame:Hide();
	else
		-- SanityItemCache.enable_collection = true
		SanityItemCache:PLAYER_MONEY()
		
		SanityFrame:Show()
		if (not Sanity.db.profile.filters_shown) and SanityTabsFrame:IsVisible() then
			Sanity:ToggleFilters(true)
		end
		if Sanity.allCharacters then
			local tm = 0
			for char in pairs(SanityItemCache.db.realm.chars) do
				tm = tm + SanityItemCache.db.realm.chars[char].Money
			end
			MoneyFrame_Update("SanityStatusBarMoneyFrame", tm)
		else
				MoneyFrame_Update("SanityStatusBarMoneyFrame", SanityItemCache.db.realm.chars[Sanity.character].Money)
		end
		Sanity:UpdateBackground()
		Sanity:SetupItems()
		Sanity.tabletop:ResizeTabContainer()
		self:Refresh()
		if self.db.profile.FocusOnShow then
			SanityEditBox:HighlightText();
			SanityEditBox:SetFocus();
		end
	end
end

function Sanity:PrintLockWarning()
	if InCombatLockdown() and not Sanity.printedWarning then
		Sanity:Print(L["Not modifiable while in combat. Try again when OOC."])
		Sanity.printedWarning = true
	end
end

function Sanity:EditBoxUpdate()
	Sanity:PrintLockWarning()

	if not InCombatLockdown() and this:GetText() ~= self.filterString then
		self.displayListDirty = true
		self.filterString = this:GetText();
		self:Refresh();
	end
end

function Sanity:Compress()
	self.DoCompress = true;
end

function Sanity:FindPartialStackFor(name, notBag, notSlot)
	local bank = {11, 10, 9, 8, 7, 6, 5, -1}	
	local bags = {0, 1, 2, 3, 4}
	local set = {bags, bank}
	for k, v in pairs(set) do
		for i, bag in pairs(v) do
			if GetContainerNumSlots(bag) then
				for slot = 1, GetContainerNumSlots(bag) do
					local texture, itemCount, locked, quality, readable = GetContainerItemInfo(bag, slot);
					local link = GetContainerItemLink(bag, slot)
					if link then
						local sName, sLink, iRarity, iLevel, iMinLevel, sType, sSubType, iStackCount = GetItemInfo(link);
						if sName == name and itemCount < iStackCount and not (bag == notBag and slot == notSlot) then
							return bag, slot
						end
					end
				end
			end	
		end
	end
end

function Sanity:CompressInventory()
	local bank = {11, 10, 9, 8, 7, 6, 5, -1}	
	local bags = {4, 3, 2, 1, 0}
	local set = {bags, bank}
	if CursorHasItem() then 
		PickupContainerItem(self.compress_target_bag, self.compress_target_slot);
		return 
	end
	for k, v in pairs(set) do
		for i, bag in pairs(v) do
			if GetContainerNumSlots(bag) then
				for slot = 1, GetContainerNumSlots(bag) do
					local texture, itemCount, locked, quality, readable = GetContainerItemInfo(bag, slot);
					local link = GetContainerItemLink(bag, slot)
					if link then
						local sName, sLink, iRarity, iLevel, iMinLevel, sType, sSubType, iStackCount = GetItemInfo(link);
						if itemCount < iStackCount then
							local nb, ns = Sanity:FindPartialStackFor(sName, bag, slot)
							self.compress_target_bag = nb
							self.compress_target_slot = nb
							if nb then
								PickupContainerItem(bag, slot);
								PickupContainerItem(nb, ns);
								return
							end
						end
					end
				end
			end	
		end
	end
	self.DoCompress = false
end

function Sanity:GetFreeBag()
	for bag_id in self.BagsIndexes do
		if GetContainerNumSlots(bag_id) then
			for slot_id = 1, GetContainerNumSlots(bag_id) do
				if not GetContainerItemLink(bag_id, slot_id) then
					return bag_id, slot_id
				end
			end
		end	
	end
end

function Sanity:GetItemData(index)
	if Sanity.allCharacters then
		results = {}
		for char, list in pairs(SanityItemCache.db.realm.chars) do
			for i, bag in ipairs(index) do
				if list.Bags[bag] then
					for idx, item in pairs(list.Bags[bag]) do
						item.Owner = char
						tinsert(results, item)
					end
				end
			end
		end
		return results
	else
		results = {}
		for k, bag in ipairs(index) do
			if SanityItemCache.db.realm.chars[self.character].Bags[bag] then
				for k2, item in pairs(SanityItemCache.db.realm.chars[self.character].Bags[bag]) do
					tinsert(results, item)
				end
			end
		end
		return results
	end
end

function Sanity:GetBags()
	return Sanity:GetItemData({KEYRING_CONTAINER, 0, 1, 2, 3, 4})
end

function Sanity:GetBank()
	return Sanity:GetItemData({-1, 5, 6, 7, 8, 9, 10, 11})
end

function Sanity:GetEquipment()
	return Sanity:GetItemData({SanityItemCache.INVENTORY_BAG_ID})
end

function Sanity:GetMailbox()
	return Sanity:GetItemData({SanityItemCache.MAILBOX_BAG_ID})
end

function Sanity:Refresh(skip_list_rebuild)
	if not SanityFrame:IsVisible() then return end
	if not skip_list_rebuild or not self.itemList then
		local baglist = {}
		local lists = {}
		Sanity.activeCategory = {}
		local checkCount = 0
		self.filter_soulbound = 0
		for i = 1,table.getn(Sanity.bindFilters) do
			local obj = getglobal(SanityBindFrame:GetName() .. "FilterCheck" .. i)
			local check = getglobal(obj:GetName() .. "Checkbox")
			Sanity.db.profile.filters[obj.value] = false
			if check:GetChecked() then
				Sanity.db.profile.filters[obj.value] = true
				checkCount = checkCount + 1
				if obj.value == "SOULBOUND" then
					self.filter_soulbound = 1
				elseif obj.value == "NOT_BOUND" then
					self.filter_soulbound = 2
				end
			end
		end
		if checkCount == 2 then
			self.filter_soulbound = 0
		end
		
		for i = 1,table.getn(Sanity.bagFilters) do
			local obj = getglobal(SanityTabsFrame:GetName() .. "FilterCheck" .. i)
			local check = getglobal(obj:GetName() .. "Checkbox")
			Sanity.db.profile.filters[obj.value] = false
			if check:GetChecked() then
				Sanity.db.profile.filters[obj.value] = true
				tinsert(Sanity.activeCategory, obj.value)
			end
		end
		for k,v in pairs(Sanity.FilterCategories) do
			for k2,v2 in pairs(Sanity.activeCategory) do
				if v == v2 then
					if v == "BAGS" then
						tinsert(lists, Sanity:GetBags())
					elseif v == "BANK" then
						tinsert(lists, Sanity:GetBank())
					elseif v == "CHAR" then
						tinsert(lists, Sanity:GetEquipment())
					elseif v == "MAIL" then
						tinsert(lists, Sanity:GetMailbox())
					end
				end
			end
		end
		self.itemList = {}
		for index, list in pairs(lists) do
			for index, item in pairs(list) do
				if item then
					if not item["Owner"] then
						item["Owner"] = self.character
					end
					tinsert(self.itemList, item)
				end
			end
		end
	end
	local list = self:GetDisplayList()
	self:UpdateDisplay(list)
end

function Sanity:ResetSanity()
	StaticPopup_Show("SANITY_CONFIRM_DATABASE_WIPE")
end

function Sanity:CollapseList()
	if not Sanity.db.profile.CollapseStacks then
		for k,v in pairs(self.itemList) do
			if v["OrigCount"] then
				v["Count"] = v["OrigCount"]
				v["Stacks"] = v["OrigStacks"]
			end
			while v["AltItems"] and #v["AltItems"] > 0 do
				tremove(v["AltItems"])
			end
		end
		return
	end
	
	local tlist = {}
	local ilist = {}
	for k,item in pairs(self.itemList) do
		if item["OrigCount"] then
			item["Count"] = item["OrigCount"]
			item["Stacks"] = item["OrigStacks"]
		end
		SanityItemCache:PrepareItem(item)
		local itemKey = item["Name"] .. "|" .. (item["Location"] or "INVALID") .. "|" .. item["Owner"]
		if tlist[itemKey] then
			tlist[itemKey]["Count"] = tlist[itemKey]["Count"] + item["Count"]
			tlist[itemKey]["Stacks"] = tlist[itemKey]["Stacks"] + 1
			tinsert(tlist[itemKey]["AltItems"], item)
		else
			item["AltItems"] = {}
			tlist[itemKey] = item
			tlist[itemKey]["OrigCount"] = tlist[itemKey]["Count"]
			tlist[itemKey]["OrigStacks"] = tlist[itemKey]["Stacks"]
			tinsert(ilist, item)
		end
	end
	self.itemList = ilist
end

function Sanity:GetDisplayList()
	if Sanity.lastDisplayList and table.getn(Sanity.lastDisplayList) > 0 and not Sanity.displayListDirty then
		return Sanity.lastDisplayList
	end
	self.TooltipDataCache = {}
	
	self:CollapseList()

	Sanity.displayListDirty = false
	if (self.filterString and self.filterString ~= "") or self.filter_soulbound > 0 then
		local tempList = {}
		for idx, item in pairs(self.itemList) do
			local cSort = item[Sanity.lastSort[1]]
			if Sanity.lastSort[1] == "Quality" then
				cSort = SANITY_QUALITIES[cSort+1]
			end
			
			if string.find(string.upper(item["Name"]), string.upper(self.filterString), 0, true) ~= nil or
				string.find(string.upper(cSort), string.upper(self.filterString), 0, true) ~= nil or 
				(item["Tags"] and string.find(string.upper(item["Tags"]), string.upper(self.filterString), 0, true) ~= nil) or 
				(Sanity.db.profile.SearchTooltips and string.find(string.upper(item["Tooltip"]), string.upper(self.filterString), 0, true) ~= nil) or 
				(item["TypeSort"] and string.find(string.upper(item["TypeSort"]), string.upper(self.filterString), 0, true) ~= nil) then
				if self.filter_soulbound == 0 or (self.filter_soulbound == 1 and item["Soulbound"] == 1) or (self.filter_soulbound == 2 and item["Soulbound"] == 0) then
					tinsert(tempList, item)
				end
			end			
		end
		self.itemList = tempList
	end
	
	local headers = {}
	if Sanity.db.profile.UseHeaders then
		headers = self:GetHeaders()
	end

	local displayList = {}
	for k,v in pairs(self.itemList) do
		tinsert(displayList, v)
	end
	for k,v in pairs(headers) do
		tinsert(displayList, v)
	end
	
	local result = self:SortList(displayList)

	Sanity.lastDisplayList = result
	return result
end

function Sanity:GetHeaders()
	local lastSortHeading = "";
	local sort = self.lastSort[1]
	local headers = {}
	setmetatable(headers, {__mode = "v"})
	if sort == "Name" then return headers end
	
	if sort == "TypeSort" then
		local categories = {}
		for k,v in pairs(self.itemList) do
			categories[tostring(v["TypeSort"])] = 1
		end
		for k,v in pairs(categories) do
			tinsert(headers, {
				["Name"] = "AAAAAAAAA",
				["Type"] = k,
				["Header"] = k,
				["TypeSort"] = k,
				["Priority"] = 1
			})
		end
		return headers
	elseif sort == "Quality" then
		local categories = {}
		for k,v in pairs(self.itemList) do
			categories[self.qualities[v["Quality"]+1]] = v["Quality"]
		end
		for k,v in pairs(categories) do
			tinsert(headers, {
				["Name"] = "AAAAAAAAA",
				["Quality"] = v,
				["Header"] = k,
				["Priority"] = 1
			})
		end
		return headers		
	else
		local categories = {}
		for k,v in pairs(self.itemList) do
			categories[tostring(v[sort])] = 1
		end
		for k,v in pairs(categories) do
			local name = "AAAAAAAAAAAAA"
			if self.sortTypes[k] == "number" then
				name = "000000000000000000000"
			end
			tinsert(headers, {
				["Name"] = name,
				[sort] = k,
				["Header"] = k,
				["Priority"] = 1
			})
		end
		return headers		
	end
	
	return headers
end

-- This is called from Refresh, so we don't need to call it directly. Just modify the sort indexes and call Refresh.
function Sanity:SortList(list)
	if (SanityFrame:IsVisible() == false) or self.lastSort == nil then
		return list;
	end
	local indexT = self.lastSort;
	table.sort(list, 
		function (t1, t2)
			local total = 0;
			local r1 = 0;
			local r2 = 0;
			for x,j in pairs(indexT) do
				local i = j;
				local s = math.pow(2, table.getn(indexT)-x);
				if not (t1 == nil or t2 == nil or t1[i] == nil or t2[i] == nil) then
					local i1 = t1[j]
					local i2 = t2[j]
					if tonumber(i1) and tonumber(i2) then
						i1 = tonumber(i1)
						i2 = tonumber(i2)
					end
					if (self.SecondarySortReverse[j] and x ~= 1) or
						 (x == 1 and self.SortReverse and not self.SecondarySortReverse[j]) or 
						 (x == 1 and not self.SortReverse and self.SecondarySortReverse[j]) then
						if i1 > i2 then
							r1 = r1 + s
						elseif i1 < i2 then
							r2 = r2 + s;
						end
					else
						if i1 < i2 then
							r1 = r1 + s
						elseif i1 > i2 then
							r2 = r2 + s;
						end
					end
				end
			end
			return (r1 > r2);
		end
	);	
	return list
end

function Sanity:Sort(indexT, reverse)
	if indexT == nil then indexT = lastSort; end
	if type(indexT) ~= "table" then
		indexT = {}
	end
	
	if indexT[1] == "Name" and table.getn(indexT) > 1 then
		table.remove(indexT, 1);
	end
	
	if self.lastSort[1] == indexT[1] then
		self.SortReverse = not self.SortReverse
	else
		self.SortReverse = false
	end
	
	self.lastSort = indexT;
	Sanity:Refresh();
end

function Sanity:GetHeaderForSortType(sortType, list, index)
	if sortType == "Quality" then
		return quality[list[index][sortType]];
	end
	return list[index][sortType]
end
