--[[----------------------------------------------------
	LibItemString - Easy Access to ItemString Properties
	----------------------------------------------------
	LibItemString:New(itemLink)
	* Returns an itemStringTable instance
	* Can easily be accessed using property field names
	* No need to remember which index a certain field has
	----------------------------------------------------
	Creating an ItemString Instance - Examples:
		is = LibItemString:New(itemLink);
		if (is.enchant ~= 0) then print("item is enchanted"); end
		itemID = is[1]; itemID = is.itemID;
		itemStringLabel:SetText(tostring(is));
	----------------------------------------------------
	LibItemString:GetFieldName(fieldindex)
	* Returns the name of the ItemString field
	----------------------------------------------------
	LibItemString:GetTrueItemLevel(itemLink)
	* Returns the true itemLevel, based on a tooltip scan
	----------------------------------------------------
	GetUpgradedItemLevelFromItemLink(itemLink)
	* Now obsolete - included for compatibility reason
	* Use LibItemString:GetTrueItemLevel(itemLink) instead
	----------------------------------------------------
	Changelog:
	## REV-01 (16.08.30) - Patch 7.0.3 ##
	- Replaces "GetUpgradedItemLevel.lua", but stays compatible
	## REV-02 (18.08.04) - 8.0/BfA ##
	- Added LIS:GetFieldName() to get the field name from index
	- Accessing the IS table using a negative index now works
	## REV-03 (22.11.20) - 10.0.2/DF ## #frozn45
	- Replaced hidden scanning tooltip with new C_TooltipInfo function for df
	## REV-04 (24.05.07) - 10.2.6/DF ## #frozn45
	- replaced deprecated function GetItemInfo() with new function C_Item.GetItemInfo()
	## REV-05 (24.08.18) - 11.0.2/TWW ## #frozn45
	- removed calling TooltipUtil.SurfaceArgs() because it's not necessary any longer (since df 10.1.0)
--]]----------------------------------------------------

-- Abort if library has already loaded with the same or newer revision
local REVISION = 5;
if (type(LibItemString) == "table") and (REVISION <= LibItemString.REVISION) then
	return;
end

LibItemString = LibItemString or {};
local LIS = LibItemString;			-- local shortcut
LIS.REVISION = REVISION;
LIS.ScanTip = LIS.ScanTip or CreateFrame("GameTooltip","LibItemStringScanTip",nil,"GameTooltipTemplate");
LIS.ScanTip:SetOwner(UIParent,"ANCHOR_NONE");

-- this replaces REV-11 of "GetUpgradedItemLevel.lua"
GET_UPGRADED_ITEM_LEVEL_REV = 12;

--------------------------------------------------------------------------------------------------------
--                                      Constants / Data Tables                                       --
--------------------------------------------------------------------------------------------------------

-- The number of tooltip lines to scan for the level text
LIS.TOOLTIP_MAXLINE_LEVEL = 5;

-- Pattern to extract the actual itemLevel from the tooltip text line
LIS.ITEM_LEVEL_PATTERN = ITEM_LEVEL:gsub("%%d","(%%d+)");

-- Extraction pattern for the exact itemString, including all its properties
LIS.ITEMSTRING_PATTERN = "(item:[^|]+)";	-- replace with "(%w+:[^|]+)" to catch all hyperlinks

-- Index of the named itemString property
LIS.ITEMSTRING_PROPERTY_INDEX = {
	-- item --
	itemID					= 1,
	enchant					= 2,
	gemID1					= 3,
	gemID2					= 4,
	gemID3					= 5,
	gemID4					= 6,
	suffixID				= 7,
	uniqueID				= 8,
	linkLevel				= 9,
	specializationID		= 10,
	upgradeTypeID			= 11,
	instanceDifficultyID	= 12,
	numBonusIDs				= 13,
--	bonusID1				= 14,
--	bonusID2				= 15,
--	...
	upgradeValue			= -1,	-- negative value means the absolute index is relative to the last bonusID index
	unknown1				= -2,
	unknown2				= -3,
	unknown3				= -4,

--	relic1NumBonusIDs		= -10,
--	relic1BonusID1			= -11,
--	relic1BonusID2			= -12,
}

-- Table for adjustment of levels due to upgrade -- Source: http://www.wowinterface.com/forums/showthread.php?t=45388
LIS.UPGRADED_LEVEL_ADJUST = {
	[001] = 8, -- 1/1
	-- Patch 5.1 --
	[373] = 4, -- 1/2
	[374] = 8, -- 2/2
	[375] = 4, -- 1/3
	[376] = 4, -- 2/3
	[377] = 4, -- 3/3
	[379] = 4, -- 1/2
	[380] = 4, -- 2/2
--	[445] = 0, -- 0/2
	[446] = 4, -- 1/2
	[447] = 8, -- 2/2
--	[451] = 0, -- 0/1
	[452] = 8, -- 1/1
--	[453] = 0, -- 0/2
	[454] = 4, -- 1/2
	[455] = 8, -- 2/2
--	[456] = 0, -- 0/1
	[457] = 8, -- 1/1
--	[458] = 0, -- 0/4
	[459] = 4, -- 1/4
	[460] = 8, -- 2/4
	[461] = 12, -- 3/4
	[462] = 16, -- 4/4
	-- Patch 5.3 --
--	[465] = 0,
	[466] = 4,
	[467] = 8,
	-- Patch ?? --
--	[468] = 0,
	[469] = 4,
	[470] = 8,
	[471] = 12,
	[472] = 16,
	-- Patch 5.4 --
--	[491] = 0,
	[492] = 4,
	[493] = 8,
--	[494] = 0,
	[495] = 4,
	[496] = 8,
	[497] = 12,
	[498] = 16,
	-- Patch 5.4.8 --
	[504] = 12,	-- US/EU upgrade 3/4
	[505] = 16,	-- US/EU upgrade 4/4
	[506] = 20,	-- Asia upgrade 5/6
	[507] = 24,	-- Asis upgrade 6/6
	-- Patch 6.2.3 --
--	[529] = 0,	-- WoD upgrade 0/2
	[530] = 5,	-- WoD upgrade 1/2
	[531] = 10,	-- WoD upgrade 2/2
	-- Patch ?? --
	[535] = 15,
	[536] = 30,
	[537] = 45,
};

-- Table for adjustment of levels due to Timewarped. These are fixed itemLevels, not upgrade amounts.
LIS.TIMEWARPED_LEVEL_ADJUST = {
	-- Patch 6.2 --
	[615] = 660, -- Dungeon drops
	[692] = 675, -- Timewarped badge vendors
};

-- Table for adjustment of levels due to Timewarped Warforged. These are fixed itemLevels, not upgrade amounts.
LIS.TIMEWARPED_WARFORGED_LEVEL_ADJUST = {
	-- Patch 6.2 --
	-- Cidrei: Yes, this really is a table of all levels between 71 and 99. The scaling matches Heirlooms up to level 97, where they diverge for... reasons.
	[071] = 151,
	[072] = 155,
	[073] = 159,
	[074] = 163,
	[075] = 167,
	[076] = 171,
	[077] = 175,
	[078] = 179,
	[079] = 183,
	[080] = 187,
	[081] = 279,
	[082] = 293,
	[083] = 306,
	[084] = 320,
	[085] = 333,
	[086] = 384,
	[087] = 404,
	[088] = 424,
	[089] = 443,
	[090] = 463,
	[091] = 530,
	[092] = 540,
	[093] = 550,
	[094] = 560,
	[095] = 570,
	[096] = 580,
	[097] = 590,
	[098] = 598,
	[099] = 605,
	[656] = 675, -- Dungeon drops
};

--------------------------------------------------------------------------------------------------------
--                                          Metatable Methods                                         --
--------------------------------------------------------------------------------------------------------

-- basic access; allow for LibItemString access, otherwise fall back to property name array index access
LIS.__index = function(tbl,k)
	if (LIS[k]) then
		return LIS[k];
	elseif (type(k) == "string") then
		-- reference by name
		local propIndex = LIS.ITEMSTRING_PROPERTY_INDEX[k];
		if (propIndex) then
			if (propIndex < 0) then
				propIndex = LIS.ITEMSTRING_PROPERTY_INDEX.numBonusIDs + tbl.numBonusIDs + abs(propIndex);
			end
			return tbl[propIndex] or 0;
		end
		-- bonusIDs
		local bonusIdIndex = tonumber(k:match("bonusID(%d+)"));
		if (bonusIdIndex) then
			local propIndex = LIS.ITEMSTRING_PROPERTY_INDEX.numBonusIDs;
			local numBonusIDs = tbl.numBonusIDs;
			return (numBonusIDs > 0) and (bonusIdIndex <= numBonusIDs) and tbl[propIndex + bonusIdIndex] or nil;
		end
	elseif (type(k) == "number") then
		if (k < 0) then
			local propIndex = LIS.ITEMSTRING_PROPERTY_INDEX.numBonusIDs + tbl.numBonusIDs + abs(k);
			return tbl[propIndex];
		end
	end
end

-- converts it back to an itemString using tostring()
LIS.__tostring = function(tbl)
	local itemLink = tbl.linkType;
	for index, value in ipairs(tbl) do
		itemLink = itemLink .. ":" .. (value == 0 and "" or tostring(value));
	end
	return itemLink;
end

--------------------------------------------------------------------------------------------------------
--                                             Functions                                              --
--------------------------------------------------------------------------------------------------------

-- Creates new itemString table instance
-- * itemLink			Example: item:128955:::-55:::::99:577::11:2:69:96:3
-- * itemStringTable	If you want to recycle the same table, pass it along here, otherwise a new table is constructed
function LIS:New(itemLink,itemStringTable)
	if (type(itemLink) == "string") then
		itemStringTable = setmetatable(itemStringTable or {},LIS);

		local itemString = itemLink:match(LIS.ITEMSTRING_PATTERN);
		itemStringTable:Parse(itemString);

		return itemStringTable;
	end
end

-- Parses the itemString properties into array entries -- To merge back, use tostring(itemStringTable)
function LIS:Parse(itemString)
	wipe(self);
	self.source = itemString;

	if (type(itemString) ~= "string") then
		return;
	end

	local index = 0;
	for value in self.source:gmatch("([^:]*):?") do
		index = (index + 1);
		if (index == 1) then
			self.linkType = value;		-- will normally just be "item", but we keep it in case we want to expand this lib later
		else
			self[#self + 1] = tonumber(value) or 0;
		end
	end
	self[#self] = nil;	-- removes the last invalid capure we get from matching the optional ":"
end

-- returns the name of the itemString field at the given index
function LIS:GetFieldName(fieldindex)
	local fieldIndices = self.ITEMSTRING_PROPERTY_INDEX;
	if (fieldindex > fieldIndices.numBonusIDs) then
		local bonusIndex = (fieldindex - fieldIndices.numBonusIDs);
		return format("bonusID%d",bonusIndex)
	end
	for name, index in next, fieldIndices do
		if (fieldindex == index) then
			return name;
		end
	end
	return UNKNOWN;
end

-- Analyses the itemString and checks for upgrades that affects itemLevel -- Only itemLevel 450 and above will have this
-- As new upgrades are added all the time, this function is rather unreliable, and its therefore not recommended to use
-- WARNING: Use the LibItemString:GetTrueItemLevel() function instead, which scans the tooltip for a 100% correct itemLevel
function LIS:GetUpgradedItemLevel()
	local _, _, _, itemLevel = C_Item.GetItemInfo(self.source);
	if not (itemLevel) then
		return nil;
	end

	-- obtain the itemString upgrade and bonusValue
	local timewarp = self.bonusID1;
	local warforged = self.bonusID2;
	local upgradeValue = self.upgradeValue;

	-- Return the actual itemLevel based on the itemString properties
	if (itemLevel >= 450) and (LIS.UPGRADED_LEVEL_ADJUST[upgradeValue]) then
		return itemLevel + LIS.UPGRADED_LEVEL_ADJUST[upgradeValue];
	else
		return LIS.TIMEWARPED_WARFORGED_LEVEL_ADJUST[warforged] or LIS.TIMEWARPED_LEVEL_ADJUST[timewarp] or itemLevel;
	end
end

-- Scans the tooltip for the proper itemLevel as we cannot get it consistently any other way
-- No ItemString instance is needed to call this function, that is calling LibItemString:New()
function LIS:GetTooltipItemLevel(itemLink)
	if (C_TooltipInfo) then -- since df
		local tooltipData = C_TooltipInfo.GetHyperlink(itemLink);
		
		if (tooltipData) then
			-- Line 1 is item name; Line 2 could simply be the itemLevel, or it could be the upgrade type such as "Mythic Warforged"
			for i = 2, min(#tooltipData.lines, LIS.TOOLTIP_MAXLINE_LEVEL) do
				local line = tooltipData.lines[i];
				
				if line.leftText then
					local itemLevel = tonumber(line.leftText:match(LIS.ITEM_LEVEL_PATTERN));
					
					if (itemLevel) then
						return itemLevel;
					end
				end
			end
		end
	else -- before df
		LIS.ScanTip:ClearLines();
		LIS.ScanTip:SetHyperlink(itemLink);

		-- Line 1 is item name; Line 2 could simply be the itemLevel, or it could be the upgrade type such as "Mythic Warforged"
		for i = 2, min(LIS.ScanTip:NumLines(),LIS.TOOLTIP_MAXLINE_LEVEL) do
			local line = _G["LibItemStringScanTipTextLeft"..i]:GetText();
			local itemLevel = tonumber(line:match(LIS.ITEM_LEVEL_PATTERN));
			if (itemLevel) then
				return itemLevel;
			end
		end
	end
end

-- Returns the true itemLevel for upgraded items, even when C_Item.GetItemInfo() says otherwise
-- This method replaces the old GetUpgradedItemLevelFromItemLink() function
function LIS:GetTrueItemLevel(itemLink)
	return self:GetTooltipItemLevel(itemLink);
end

-- GLOBAL function staying compatible with the old "GetUpgradedItemLevel.lua" unit
-- OBSOLETE: Use LibItemString:GetTrueItemLevel(itemLink) instead
function GetUpgradedItemLevelFromItemLink(itemLink)
	return LIS:GetTrueItemLevel(itemLink);
end