local addonName, addonTable = ...
local addon = _G[addonName]

local L = LibStub("AceLocale-3.0"):GetLocale(addonName)
local BB = LibStub("LibBabble-Boss-3.0"):GetLookupTable()
local BZ = LibStub("LibBabble-Zone-3.0"):GetLookupTable()
local BI = LibStub("LibBabble-Inventory-3.0"):GetLookupTable()

local WHITE		= "|cFFFFFFFF"
local GREEN		= "|cFF00FF00"

local lootTable, lootTableRev_Single, lootTableRev_Multi
local LootSourceTooltipDB = {}
local LootSourceTooltip_VERSION = 18.033


local DataProviders
local listSearchedID = {}

addon.Loots = {}

local ns = addon.Loots		-- ns = namespace


-- Simplified loot table containing item ID's only, based on AtlasLoot v5.09.00
lootTable = addonTable.LootTableSetup.lootTable



function ns:setupLootTable()
	local playerFaction = UnitFactionGroup("player")
	LootSourceTooltipDB = Altoholic.db.global.LootSourceTooltip[playerFaction]
	
	if not LootSourceTooltipDB.version or LootSourceTooltipDB.version ~= LootSourceTooltip_VERSION then
		
		
		local lootSecondary = { 	-- these items are only considered second after all other sources
			L["Emblem of Frost"],
			L["Emblem of Triumph"],
			L["Emblem of Conquest"],
			L["Emblems of Valor"],
			L["Emblems of Heroism"],
			L["Badge of Justice"],
			BZ["Vault of Archavon"],
		}
		local lootSecondaryCheck = {}
		for k,v in pairs(lootSecondary) do lootSecondaryCheck[v]= true; end
		
		
		
		local lastZone = {}
		local lootTable_names = {}
		
		for zName, _ in pairs(lootTable) do
			tinsert( lootTable_names, zName )
		end
		sort(lootTable_names)
		local zTable
		
		-- lootTableRev_Single = {}
		lootTableRev_Multi = {}
		-- PRIMARY
		for _, zName in pairs(lootTable_names) do
			if not lootSecondaryCheck[zName] then	-- check primary sources first
				zTable = lootTable[zName]
				for encounterName, encounterTable in pairs(zTable) do 
					for _, item_id in pairs(encounterTable) do
						-- if not lootTableRev_Single[item_id] then 	-- insert only once
							-- lootTableRev_Single[item_id] = format("%s\1%s", zName, encounterName)
						-- end
						
						if not lootTableRev_Multi[item_id] then
							lootTableRev_Multi[item_id] = format("%s\1%s", zName, encounterName)
						else
							if lastZone[item_id] and lastZone[item_id] == zName then
								lootTableRev_Multi[item_id] = format("%s\1\1%s", lootTableRev_Multi[item_id], encounterName)
							else
								lootTableRev_Multi[item_id] = format("%s\1\1\1%s\1%s", lootTableRev_Multi[item_id], zName, encounterName)
							end
						end
						lastZone[item_id] = zName
					end
				end
			end
		end
		-- SECONDARY
		for _, zName in pairs(lootTable_names) do
			if lootSecondaryCheck[zName] then	-- check secondary sources next
				zTable = lootTable[zName]
				for encounterName, encounterTable in pairs(zTable) do 
					for _, item_id in pairs(encounterTable) do 
						-- if not lootTableRev_Single[item_id] then 	-- insert only once
							-- lootTableRev_Single[item_id] = format("%s\1%s", zName, encounterName)
						-- end
						
						if not lootTableRev_Multi[item_id] then
							lootTableRev_Multi[item_id] = format("%s\1%s", zName, encounterName)
						else
							if lastZone[item_id] and lastZone[item_id] == zName then
								lootTableRev_Multi[item_id] = format("%s\1\1%s", lootTableRev_Multi[item_id], encounterName)
							else
								lootTableRev_Multi[item_id] = format("%s\1\1\1%s\1%s", lootTableRev_Multi[item_id], zName, encounterName)
							end
						end
						lastZone[item_id] = zName
					end
				end
			end
		end
		
		-- LootSourceTooltip = { version = 0, single={}, multi={}, },
		wipe(LootSourceTooltipDB.db)
		-- wipe(LootSourceTooltipDB.single)
		LootSourceTooltipDB.db  = lootTableRev_Multi
		-- LootSourceTooltipDB.single = lootTableRev_Single
		
		LootSourceTooltipDB.version = LootSourceTooltip_VERSION
		
		wipe(lastZone)
		wipe(lootTable_names)
	else -- ################################################################################################
		
	end
	
	-- lootTable[ BZ["Vault of Archavon"] ] = nil 	-- we ignore this one, cuz its confusing
	wipe(lootTable)
end

function ns:GetSource(searchedID)
	DataProviders = DataProviders or {			-- list of sources that have a :GetSource() method
		DataStore_Reputations,
		DataStore_Crafts,
		DataStore_Inventory,
	}
	local txt, domain, subDomain
	
	-- if LootSourceTooltipDB.multi[searchedID] then
	if LootSourceTooltipDB.db[searchedID] then
		-- txt = LootSourceTooltipDB.multi[searchedID]
		txt = LootSourceTooltipDB.db[searchedID]
		
		local p = strfind(txt, "\1\1\1") 	-- check if multiple zones and take first
		if p then
			txt = strsub(txt, 1, p-1)
		end
		
		p = strfind(txt, "\1\1") 	-- check if multiple bosses and take first
		if p then
			txt = strsub(txt, 1, p-1)
		end
		domain, subDomain = strsplit("\1", txt) 	-- split zone from boss
		
		if domain and subDomain and domain~="" and subDomain~="" then
			return domain, subDomain
		end
	end
	

	-- local domain, subDomain
	for _, provider in pairs(DataProviders) do
		domain, subDomain = provider:GetSource(searchedID)
		if domain and subDomain then
			return domain, subDomain
		end
	end
	
	return nil
end

function ns:GetSource_multi(searchedID)
	-- if LootSourceTooltipDB.multi[searchedID] then
	if LootSourceTooltipDB.db[searchedID] then
		-- local txt = LootSourceTooltipDB.multi[searchedID]
		local txt = LootSourceTooltipDB.db[searchedID]
		txt = gsub(txt, "\1\1\1","; ") 
		txt = gsub(txt, "\1\1","/ ") 
		txt = gsub(txt, "\1",", ") 
		return txt
	end
end

function ns:GetSource_OLD(searchedID) 	-- old version
	DataProviders = DataProviders or {			-- list of sources that have a :GetSource() method
		DataStore_Reputations,
		DataStore_Crafts,
		DataStore_Inventory,
	}
	
	-- extremely fast: takes from 0.3 to 3 ms max, depends on the location of the item in the table (obviously longer if the item is at the end)
	for Instance, BossList in pairs(lootTable) do
		for Boss, LootList in pairs(BossList) do
			for itemID, _ in pairs(LootList) do
				if LootList[itemID] == searchedID then
					return Instance, Boss
				end
			end
		end
	end

	local domain, subDomain
	for _, provider in pairs(DataProviders) do
		domain, subDomain = provider:GetSource(searchedID)
		if domain and subDomain then
			return domain, subDomain
		end
	end
	
	return nil
end

local filters = addon.ItemFilters

local function ParseAltoholicLoots(OnMatch, OnNoMatch)
	assert(type(OnMatch) == "function")
	local count = 0
	-- print("-- DEBUG: CALLED ParseAltoholicLoots(OnMatch, OnNoMatch)")
	
	-- for itemID, txt in pairs(LootSourceTooltipDB.multi) do
	for itemID, txt in pairs(LootSourceTooltipDB.db) do
		if not listSearchedID[itemID] then	-- search only once
			listSearchedID[itemID] = 1
			
			count = count + 1
			filters:SetSearchedItem(itemID)
			
			if filters:ItemPassesFilters() then
				local p = strfind(txt, "\1\1\1") 	-- check if multiple zones and take first
				if p then
					txt = strsub(txt, 1, p-1)
				end
				
				p = strfind(txt, "\1\1") 	-- check if multiple bosses and take first
				if p then
					txt = strsub(txt, 1, p-1)
				end
				local Instance, Boss = strsplit("\1", txt) 	-- split zone from boss
				
				OnMatch(Instance, Boss)
			else
				if OnNoMatch then
					OnNoMatch()
				end
			end
			
		end
	end
	
	filters:ClearSearchedItem()
	return count
end

local function ParseAltoholicLoots_OLD(OnMatch, OnNoMatch)
	assert(type(OnMatch) == "function")
	local count = 0
	
	for Instance, BossList in pairs(lootTable) do
		for Boss, LootList in pairs(BossList) do
			for _, itemID in pairs(LootList) do
				count = count + 1
				filters:SetSearchedItem(itemID)
				
				if filters:ItemPassesFilters() then
					OnMatch(Instance, Boss)
				else
					if OnNoMatch then
						OnNoMatch()
					end
				end
			end
		end
	end
	
	filters:ClearSearchedItem()
	return count
end

local function ParseLPTSet(set, OnMatch, OnNoMatch)
	assert(type(OnMatch) == "function")
	
	local PT = LibStub("LibPeriodicTable-3.1")
	if not PT then return 0 end		-- exit if LPT is not active

	-- LPT stores certain sets twice, but does not offer the possibility to differentiate entries (instances that are part of hubs)
	-- So keep track of the sets we've already parsed, to avoid returning items twice.
	local doneSets = {}
	local count = 0

	for _, list in pairs(PT:GetSetTable(set)) do
		local _, domain, subdomain = strsplit(".", list.set)
		
		if not doneSets[list.set] then			-- if this set hasn't been parsed yet, proceed..
			for itemID, value in pairs(list) do
				if tostring(itemID) ~= "set" then
					if not listSearchedID[itemID] then	-- search only once
						listSearchedID[itemID] = 1
						
						count = count + 1
						filters:SetSearchedItem(itemID)
						
						if filters:ItemPassesFilters() then
							OnMatch(domain, subdomain or value)		-- pass the value in case "subdomain" is nil
						else
							if OnNoMatch then
								OnNoMatch()
							end
						end
					end
				end
			end
		end
		doneSets[list.set] = true
	end
	
	filters:ClearSearchedItem()
	return count
end

local allowedQueries, unknownCount

local function OnMatch(domain, subdomain)
	Altoholic.Search:AddResult( {
		id = filters:GetSearchedItemInfo("itemID"),
		iLvl = filters:GetSearchedItemInfo("itemLevel"),
		dropLocation = domain,
		bossName = subdomain,
	} )
end

local function Currency_OnMatch(domain, subdomain)
	Altoholic.Search:AddResult( {
		id = filters:GetSearchedItemInfo("itemID"),
		iLvl = filters:GetSearchedItemInfo("itemLevel"),
		dropLocation = domain,
		bossName = subdomain.."x",
	} )
end

local function OnNoMatch()
--	if FilterByExistence() then return end 	-- if the item exists, do nothing
	if filters:TryFilter("Existence") then return end 	-- if the item exists, do nothing
	unknownCount = unknownCount + 1
	
	if allowedQueries > 0 then
		if Altoholic.Options:Get("SearchAutoQuery") == 1 then		-- if autoquery is enabled
			local itemID = filters:GetSearchedItemInfo("itemID")
			if not addon:IsItemUnsafe(itemID) then		-- if the item is not known to be unsafe
				GameTooltip:SetHyperlink("item:"..itemID..":0:0:0:0:0:0:0")	-- this line queries the server for an unknown id
				GameTooltip:ClearLines(); -- don't leave residual info in the tooltip after the server query

				-- save ALL tested id's, clean the list in OnEnable during the next session.
				-- the unsafe list will be cleaned in OnEnable, by parsing all ids and testing if getiteminfo returns a nil or not, if so, it's a definite unsafe link
				addon:SaveUnsafeItem(itemID)			-- save id to unsafe list
			end
		end
		allowedQueries = allowedQueries - 1
	end
end

function ns:Find()
	unknownCount = 0
	allowedQueries = 5
	wipe(listSearchedID)
	
	local count = ParseAltoholicLoots(OnMatch, OnNoMatch)
	count = count + ParseLPTSet("InstanceLoot", OnMatch, OnNoMatch)
	count = count + ParseLPTSet("InstanceLootHeroic", OnMatch, OnNoMatch)
	count = count + ParseLPTSet("CurrencyItems", Currency_OnMatch, OnNoMatch)

	addon.Options:Set("TotalLoots", count)
	addon.Options:Set("UnknownLoots", unknownCount)
end

function ns:FindUpgrade()
	local function OnMatch(domain, subdomain)
		addon.Search:AddResult( {
			id = filters:GetSearchedItemInfo("itemID"),
			iLvl = filters:GetSearchedItemInfo("itemLevel"),
			dropLocation = domain,
			bossName = subdomain,
		} )
	end
	
	ParseAltoholicLoots(OnMatch)
	ParseLPTSet("InstanceLoot", OnMatch)
	ParseLPTSet("InstanceLootHeroic", OnMatch)
	ParseLPTSet("CurrencyItems", Currency_OnMatch)
end

local tooltipLines			-- cache containing the text lines of the tooltip "+15 stamina, etc.."
local rawItemStats			-- contains the raw stats of the item currently being searched, placed here to avoid creating/deleting the table during the search
local currentItemStats		-- contains the stats of the item for which we'll try to find upgrades

local classExcludedStats
local classBaseStats

local function AddCurrentlyEquippedItem(itemID, class)

	AltoTooltip:SetOwner(AltoholicFrame, "ANCHOR_LEFT")
	local _, itemLink, _, itemLevel = GetItemInfo(itemID)
	AltoTooltip:SetHyperlink(itemLink)
	
	local statLine = addon.Equipment.FormatStats[class]
	local numLines = AltoTooltip:NumLines()
	
	local j=1
	for _, BaseStat in pairs(classBaseStats) do
		for i = 4, numLines do
			local tooltipText = _G[ "AltoTooltipTextLeft" .. i]:GetText()
			if tooltipText then
				if string.find(tooltipText, BaseStat) ~= nil then
					currentItemStats[BaseStat] = tonumber(string.sub(tooltipText, string.find(tooltipText, "%d+")))
					statLine = string.gsub(statLine, "-s", WHITE .. currentItemStats[BaseStat], 1)
					
					rawItemStats[j] = currentItemStats[BaseStat] .. "|0"
					break
				end
			end
		end
		if not currentItemStats[BaseStat] then
			rawItemStats[j] = "0|0"
		
			currentItemStats[BaseStat] = 0 -- Set the current stat to zero if it was not found on the item
			statLine = string.gsub(statLine, "-s", WHITE .. "0", 1)
		end
		j = j + 1
	end
	AltoTooltip:ClearLines();
	
	-- Save currently equipped item to the results table
	addon.Search:AddResult( {
		id = itemID,
		iLvl = itemLevel,
		dropLocation = "Currently equipped",
		stat1 = rawItemStats[1],
		stat2 = rawItemStats[2],
		stat3 = rawItemStats[3],
		stat4 = rawItemStats[4],
		stat5 = rawItemStats[5],
		stat6 = rawItemStats[6]
	} )
end

local function MatchUpgradeByStats(itemID)
	filters:SetSearchedItem(itemID)
	if not filters:ItemPassesFilters() then 
		filters:ClearSearchedItem()
		return
	end

	AltoTooltip:ClearLines();	
	AltoTooltip:SetOwner(AltoholicFrame, "ANCHOR_LEFT");
	AltoTooltip:SetHyperlink(filters:GetSearchedItemInfo("itemLink"))
	
	-- save some time by trying to find out if the item could be excluded
	wipe(tooltipLines)
	for i = 4, AltoTooltip:NumLines() do	-- parse all tooltip lines, one by one, start at 4 since 1= item name, 2 = binds on.., 3 = type/slot/unique ..etc
		-- in this first pass, save the lines into a cache, reused below
		local tooltipLine = _G[ "AltoTooltipTextLeft" .. i]:GetText()
		if tooltipLine then
			if string.find(tooltipLine, L["Socket"]) == nil then
				for _, v in pairs(classExcludedStats) do
					--if string.find(tooltipLine, v, 1, true) ~= nil then return end
					if string.find(tooltipLine, v) ~= nil then return end
				end
				tooltipLines[i] = tooltipLine
			end
		end
	end
	
	local statFound
	local j=1
	for _, BaseStat in pairs(classBaseStats) do

		statFound = nil
		for i, tooltipText in pairs(tooltipLines) do
			--if string.find(tooltipText, BaseStat, 1, true) ~= nil then
			if string.find(tooltipText, BaseStat) ~= nil then
				--local stat = tonumber(string.sub(tooltipText, string.find(tooltipText, "%d+")))
				local stat = tonumber(string.match(tooltipText, "%d+"))
				
				rawItemStats[j] = stat .. "|" .. (stat - currentItemStats[BaseStat])
				table.remove(tooltipLines, i)	-- remove the current entry, so it won't be parsed in the next loop cycle
				statFound = true
				break
			end
		end
		
		if not statFound then
			rawItemStats[j] = "0|" .. (0 - currentItemStats[BaseStat])
		end
		j = j + 1
	end
	
	local iLvl = filters:GetSearchedItemInfo("itemLevel")
	filters:ClearSearchedItem()
		
	-- All conditions ok ? save it
	return true, iLvl
end

-- modify this one after 3.2, to use GetItemStats
function ns:FindUpgradeByStats(currentID, class)
	classExcludedStats = addon.Equipment.ExcludeStats[class]
	classBaseStats = addon.Equipment.BaseStats[class]
	-- print("-- DEBUG: CALLED ns:FindUpgradeByStats(currentID, class)")
	
	rawItemStats = {}
	currentItemStats = {}
	tooltipLines = {}
	
	AddCurrentlyEquippedItem(currentID, class)
	
	-- for itemID, txt in pairs(LootSourceTooltipDB.multi) do
	for itemID, txt in pairs(LootSourceTooltipDB.db) do
		
		local matches, itemLevel = MatchUpgradeByStats(itemID)
		
		if matches then
			local p = strfind(txt, "\1\1\1") 	-- check if multiple zones and take first
			if p then
				txt = strsub(txt, 1, p-1)
			end
			
			p = strfind(txt, "\1\1") 	-- check if multiple bosses and take first
			if p then
				txt = strsub(txt, 1, p-1)
			end
			local Instance, Boss = strsplit("\1", txt) 	-- split zone from boss
			
			if Instance and Boss then
				addon.Search:AddResult( {
					id = itemID,
					iLvl = itemLevel,
					dropLocation = Instance .. ", " .. GREEN .. Boss,
					stat1 = rawItemStats[1],
					stat2 = rawItemStats[2],
					stat3 = rawItemStats[3],
					stat4 = rawItemStats[4],
					stat5 = rawItemStats[5],
					stat6 = rawItemStats[6]
				} )
			end
		end
		
	end
	
	classExcludedStats = nil
	classBaseStats = nil
	currentItemStats = nil
	tooltipLines = nil
	rawItemStats = nil
end

function ns:FindUpgradeByStats_OLD(currentID, class)
	classExcludedStats = addon.Equipment.ExcludeStats[class]
	classBaseStats = addon.Equipment.BaseStats[class]
	
	rawItemStats = {}
	currentItemStats = {}
	tooltipLines = {}
	
	AddCurrentlyEquippedItem(currentID, class)
	
	for Instance, BossList in pairs(lootTable) do		-- parse the loot table to find an upgrade
		for Boss, LootList in pairs(BossList) do
			for _, itemID in pairs(LootList) do
			
				local matches, itemLevel = MatchUpgradeByStats(itemID)
				
				if matches then
					addon.Search:AddResult( {
						id = itemID,
						iLvl = itemLevel,
						dropLocation = Instance .. ", " .. GREEN .. Boss,
						stat1 = rawItemStats[1],
						stat2 = rawItemStats[2],
						stat3 = rawItemStats[3],
						stat4 = rawItemStats[4],
						stat5 = rawItemStats[5],
						stat6 = rawItemStats[6]
					} )
				end
			end
		end
	end
	
	classExcludedStats = nil
	classBaseStats = nil
	currentItemStats = nil
	tooltipLines = nil
	rawItemStats = nil
end
