local DGV = DugisGuideViewer
if not DGV then return end
local L = DugisLocals

local LuaUtils = LuaUtils

local dugiSmartSetID = 999999
--Taken from \BlizzardInterfaceCode\Interface\FrameXML\GroupLootFrame.lua
local NUM_GROUP_LOOT_FRAMES = 4

local lastIsStealthed = IsStealthed()

local GA = DGV:RegisterModule("GearAdvisor")
GA.essential = true

DGV.GetCurrentBestInSlot_cache_v2 = {}
DGV.GetSpecDataTable_cache = {}
DGV.GetCurrentRating_cache = {}
DGV.CalculateScore_cache = {}
DGV.IsLegendaryItemEquipped_cached = nil

GA.isDuringEquippingChain = false
GA.makingForAllInProgress = false

local WIN_CRITERIA_CURRENT = "Active Talent Specialization"
local WIN_CRITERIA_INACTIVE_SPEC = "Inactive Talent Specialization"
local WIN_CRITERIA_NONE = "None"

local _
local StatLogic = LibStub("LibStatLogic-1.2-Dugi")
local TipHooker = LibStub("LibTipHooker-1.1")
local select, GetAuctionItemSubClasses, GetExpertise, GetCombatRatingBonus, GetHitModifier, GetSpellHitModifier, GetMeleeHaste, GetRangedHaste, UnitSpellHaste =
	select, C_AuctionHouse.GetAuctionItemSubClasses, GetExpertise, GetCombatRatingBonus, GetHitModifier, GetSpellHitModifier, GetMeleeHaste, GetRangedHaste, UnitSpellHaste
local GetCritChance, GetSpellCritChance, GetRangedCritChance, GetQuestLogChoiceInfo, GetQuestLogRewardInfo, GetQuestLogItemLink, GetQuestItemLink, GetQuestItemInfo =
	GetCritChance, GetSpellCritChance, GetRangedCritChance, GetQuestLogChoiceInfo, GetQuestLogRewardInfo, GetQuestLogItemLink, GetQuestItemLink, GetQuestItemInfo
local GetContainerItemLink, INVSLOT_FIRST_EQUIPPED, INVSLOT_LAST_EQUIPPED, BACKPACK_CONTAINER =
	C_Container.GetContainerItemLink, INVSLOT_FIRST_EQUIPPED, INVSLOT_LAST_EQUIPPED, BACKPACK_CONTAINER
local GetItemUniqueness = C_Item.GetItemUniqueness
local UnitClass, GetInventoryItemLink, GetItemInfo, UnitLevel, GetInventoryItemID, GetCreateTable, QueueInvocation, strformat, RegisterReaction, RegisterFunctionReaction, RegisterFunctionPathReaction, RegisterStopwatchReaction, TryGetCacheReaction, ListContains, PackStrings =
		UnitClass, GetInventoryItemLink, C_Item.GetItemInfo, UnitLevel, GetInventoryItemID, DGV.GetCreateTable, DGV.QueueInvocation, string.format, DGV.RegisterReaction, DGV.RegisterFunctionReaction, DGV.RegisterFunctionPathReaction, DGV.RegisterStopwatchReaction, DGV.TryGetCacheReaction, DGV.ListContains, DGV.PackStrings
local BeginAutoroutine, InterruptAutoroutine, YieldAutoroutine, tPool, DoOutOfCombat, GetRunningAutoroutine =
	DGV.BeginAutoroutine, DGV.InterruptAutoroutine, DGV.YieldAutoroutine, DGV.tPool, DGV.DoOutOfCombat, DGV.GetRunningAutoroutine
local firstTimeload = true

local function Repaint(tooltip)
    tooltip:Show()
    local tipTextLeft = tooltip:GetName().."TextLeft"
    for i = 2, tooltip:NumLines() do
        local fontString = _G[tipTextLeft..i]
        _, relativeTo, _, xOfs, _ = fontString:GetPoint(0)
        --fontString:ClearAllPoints()
        --fontString:SetPoint("TOPLEFT", relativeTo, "BOTTOMLEFT", xOfs, -2)
    end
end

DugisGuideViewer.defaultLevelingSpec = {
    ["DEATHKNIGHT"] = {["index"] = 1, ["orderIndex"] = 1},
    ["MONK"] = {["index"] = 3, ["orderIndex"] = 2},
    ["WARRIOR"] = {["index"] = 1, ["orderIndex"] = 3},
    ["PALADIN"] = {["index"] = 3, ["orderIndex"] = 4},
    ["DRUID"] = {["index"] = 2, ["orderIndex"] = 5},
    ["ROGUE"] = {["index"] = 1, ["orderIndex"] = 6},
    ["SHAMAN"] = {["index"] = 2, ["orderIndex"] = 7},
    ["HUNTER"] = {["index"] = 1, ["orderIndex"] = 8},
    ["MAGE"] = {["index"] = 1, ["orderIndex"] = 9},
    ["PRIEST"] = {["index"] = 3, ["orderIndex"] = 10},
    ["WARLOCK"] = {["index"] = 2, ["orderIndex"] = 11},
    ["DEMONHUNTER"] = {["index"] = 1, ["orderIndex"] = 12},
	["EVOKER"] = {["index"] = 1, ["orderIndex"] = 13}
}

local weightIdentifier2weightLabelMap = {
    ["AGI"] = "Agility"
    ,["AP"] = "Attack Power"
    ,["ARMOR"] = "Armor"
	--    ,["BONUS_ARMOR"] = "Bonus Armor"
    ,["AVOIDANCE_RATING"] = "Avoidance"
    ,["DPS"] = "DPS"
    ,["DPS|MAIN"] = "DPS - Main"
    ,["DPS|OFF"] = "DPS - Off"
    ,["INT"] = "Intellect"
    ,["LEECH_RATING"] = "Leech"
    ,["MASTERY_RATING"] = "Mastery"
    ,["MELEE_CRIT_RATING"] = "Critical Rating"
    ,["MELEE_HASTE_RATING"] = "Haste Rating"
	--    ,["MULTISTRIKE_RATING"] = "Multistrike"
    ,["RANGED_CRIT_RATING"] = "Critical Rating"
    ,["RANGED_HASTE_RATING"] = "Haste Rating"
    ,["SPELL_CRIT_RATING"] = "Critical Rating"
    ,["SPELL_HASTE_RATING"] = "Haste Rating"
    ,["SPELL_POWER"] = "Spell Power"
    ,["SPELL_DMG"] = "Spell Power"
	--    ,["SPI"] = "Spirit"
    ,["STA"] = "Stamina"
    ,["STR"] = "Strength"
    ,["VERSATILITY_RATING"] = "Versatility"
    ,["XP_BONUS"] = "XP Bonus"
}

function GA:Initialize()

    --itemId - numeric item id
    function GA.IsUniqueEquippedGear(itemId)
        local _, max = GetItemUniqueness(itemId)
        return max == 1
    end
	
	--The assumption is that itemLink1, itemLink2 are related to the same slot
	function GA.CanBeEquippedAtTheSameTime(itemLink1, itemLink2)
		if not itemLink1 or not itemLink2 then
			return true
		end

		local parts1 = {strsplit(":", itemLink1)}
		local item1Id = tonumber(parts1[2])  
		local parts2 = {strsplit(":", itemLink2)}
		local item2Id = tonumber(parts2[2])  

		local isUniqueEquipped1 = GA.IsUniqueEquippedGear(itemLink1)
		local isUniqueEquipped2 = GA.IsUniqueEquippedGear(itemLink2)

		if isUniqueEquipped1 and isUniqueEquipped2 and item1Id == item2Id then
			return false
		end

		return true
	end

    GA.AutoEquipEnabled = function()
        return DGV:UserSetting(DGV_AUTOEQUIPSMARTSET) and not DGV:IsEquippedOneOfExcludedSets()
    end

    --In case some gear was added/changed etc in gear set. Fired before PLAYER_EQUIPMENT_CHANGED
	RegisterReaction("PLAYER_EQUIPMENT_CHANGED"):WithAction(function(_,_,slot)
        if GA.makingForAllInProgress then
           return
        end

        DGV.GetCurrentBestInSlot_cache_v2 = {}
        DGV.GetSpecDataTable_cache = {}
        
        if DGV:UserSetting(DGV_GASMARTSETTARGET)==WIN_CRITERIA_NONE or not GA.AutoEquipEnabled() then
            return
        end
        
        if GA.AutoEquipSmartSet and  GA.EquipmentChangedContinueEquipAction then
            if not GA.isDuringEquippingChain then
                GA.isDuringEquippingChain = true
                if GA.AutoEquipSmartSet then GA.AutoEquipSmartSet() end
                if GA.RefreshCheckIconOnSmartSet then GA.RefreshCheckIconOnSmartSet() end
            else
                if GA.EquipmentChangedContinueEquipAction then GA.EquipmentChangedContinueEquipAction(slot) end
            end
        end
	end)

    --In case some item in bags is moved, added etc. Fired after PLAYER_EQUIPMENT_CHANGED
	RegisterReaction("BAG_UPDATE"):WithAction(function(_,_,slot)
        if not GA.isDuringEquippingChain and not GA.makingForAllInProgress then
            DGV.GetCurrentBestInSlot_cache_v2 = {}
            DGV.GetSpecDataTable_cache = {}
            if GA.AutoEquipSmartSet then
                GA.AutoEquipSmartSet()
            end
        end
	end)
    
    function GA.RefreshCheckIconOnSmartSet()
        if PaperDollFrame and PaperDollFrame:IsShown() then
            LuaUtils:Delay(1, function()
                if PaperDollFrame.EquipmentManagerPane.equipmentSetIDs then
                    PaperDollEquipmentManagerPane_Update()
                end
            end)
        end  
    end
	
	--Gets all owned items from bags
	function GA.GetAllOwnedItems()
		local result = {}
		for bag = -4, 20 do
			for slot = 1,C_Container.GetContainerNumSlots(bag) do
				local item = GetContainerItemLink(bag,slot)
				if item then
					local link = GetContainerItemLink(bag,slot)
					result[#result + 1] = link
				end
			end
		end
		
		result = LuaUtils:RemoveDuplicates(result)
		LuaUtils:SortTable(result)
		
		return result
	end
	
    if Storyline_NPCFrameRewardsItem then
        local oldOnClickScript = Storyline_NPCFrameRewardsItem:GetScript("OnClick")
        Storyline_NPCFrameRewardsItem:SetScript("OnClick", function()
            oldOnClickScript(Storyline_NPCFrameRewardsItem)
            if GA.EvaluateRewards then
                GA.EvaluateRewards()
            end
        end)

        Storyline_NPCFrameRewards.Content:SetScript("OnShow", function()
            if GA.EvaluateRewards then
                GA.EvaluateRewards()
            end
        end)
    end
    
    --Immersion addon
    if ImmersionFrame then
        ImmersionFrame.TalkBox.Elements.Content.RewardsFrame:SetScript("OnShow", function()
            if GA.EvaluateRewards then
                GA.EvaluateRewards()
            end
        end)
    end

		local LE_ITEM_CLASS_WEAPON, LE_ITEM_CLASS_ARMOR = Enum.ItemClass.Weapon, Enum.ItemClass.Armor
		local LE_ITEM_ARMOR_COSMETIC, LE_ITEM_ARMOR_SHIELD = Enum.ItemArmorSubclass.Cosmetic, Enum.ItemArmorSubclass.Shield
		local LE_ITEM_WEAPON_FISHINGPOLE = Enum.ItemWeaponSubclass.Fishingpole
		local LE_ITEM_WEAPON_BOWS, LE_ITEM_WEAPON_GUNS, LE_ITEM_WEAPON_CROSSBOW = Enum.ItemWeaponSubclass.Bows, Enum.ItemWeaponSubclass.Guns, Enum.ItemWeaponSubclass.Crossbow
		local LE_ITEM_WEAPON_POLEARM, LE_ITEM_WEAPON_STAFF = Enum.ItemWeaponSubclass.Polearm, Enum.ItemWeaponSubclass.Staff
		local LE_ITEM_WEAPON_AXE2H, LE_ITEM_WEAPON_MACE2H, LE_ITEM_WEAPON_SWORD2H = Enum.ItemWeaponSubclass.Axe2H, Enum.ItemWeaponSubclass.Mace2H, Enum.ItemWeaponSubclass.Sword2H

		--LE_ITEM_WEAPON_AXE1H=0,
		--LE_ITEM_WEAPON_AXE2H=1,
		--LE_ITEM_WEAPON_BOWS=2
		--LE_ITEM_WEAPON_GUNS=3
		--LE_ITEM_WEAPON_MACE1H=4
		--LE_ITEM_WEAPON_MACE2H=5,
		--LE_ITEM_WEAPON_POLEARM=6,
		--LE_ITEM_WEAPON_SWORD1H=7
		--LE_ITEM_WEAPON_SWORD2H=8
		--LE_ITEM_WEAPON_WARGLAIVE=9
		--LE_ITEM_WEAPON_STAFF=10
		--LE_ITEM_WEAPON_UNARMED=13
		--LE_ITEM_WEAPON_GENERIC=14
		--LE_ITEM_WEAPON_DAGGER=15
		--LE_ITEM_WEAPON_THROWN=16
		--LE_ITEM_WEAPON_CROSSBOW=18
		--LE_ITEM_WEAPON_WAND=19

		--LE_ITEM_ARMOR_GENERIC=0
		--LE_ITEM_ARMOR_CLOTH=1
		--LE_ITEM_ARMOR_LEATHER=2
		--LE_ITEM_ARMOR_MAIL=3
		--LE_ITEM_ARMOR_PLATE=4
		--LE_ITEM_ARMOR_COSMETIC=5
		--LE_ITEM_ARMOR_SHIELD=6

		local scoring = {

	        ["DEATHKNIGHT:1"] = "ARMOR_SPECIALIZATION_STAT,STA:LE_ITEM_CLASS_WEAPON,1,5,6,8:LE_ITEM_CLASS_ARMOR,0,1,2,3,4:XP_BONUS,1000:DPS,6.21:STR,56.23:STA,63.09:MASTERY_RATING,23.25:VERSATILITY_RATING,41.33:ARMOR,49.60:MELEE_CRIT_RATING,20.77:AP,0.01:AVOIDANCE_RATING,0.02:LEECH_RATING,2.89:MELEE_HASTE_RATING,21.45:AGI,0:INT,0:SPELL_DMG,0",		
	        ["DEATHKNIGHT:2"] = "ARMOR_SPECIALIZATION_STAT,STR:LE_ITEM_CLASS_WEAPON,0,4,7:LE_ITEM_CLASS_ARMOR,0,1,2,3,4:XP_BONUS,1000:DPS,2.03:DPS|MAIN,2.03:DPS|OFF,0.67:STR,2.76:VERSATILITY_RATING,0.96:MASTERY_RATING,1.23:MELEE_HASTE_RATING,0.82:AP,0.01:MELEE_CRIT_RATING,1.40:STA,0:AGI,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:INT,0:LEECH_RATING,0.01:SPELL_DMG,0",	
	        ["DEATHKNIGHT:3"] = "ARMOR_SPECIALIZATION_STAT,STR:LE_ITEM_CLASS_WEAPON,1,5,6,8:LE_ITEM_CLASS_ARMOR,0,1,2,3,4:XP_BONUS,1000:DPS,2.27:STR,3.14:VERSATILITY_RATING,1.10:MELEE_HASTE_RATING,1.34:MELEE_CRIT_RATING,1.17:AP,0.01:MASTERY_RATING,1.39:STA,0:AGI,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:INT,0:LEECH_RATING,0.01:SPELL_DMG,0",    
	        ["MONK:1"] = "ARMOR_SPECIALIZATION_STAT,STA:LE_ITEM_CLASS_WEAPON,6,10:LE_ITEM_CLASS_ARMOR,0,1,2:XP_BONUS,1000:DPS,14.55:DPS|MAIN,14.55:DPS|OFF,1.45:AGI,84.63:VERSATILITY_RATING,42.41:MELEE_HASTE_RATING,6.52:MELEE_CRIT_RATING,17.32:ARMOR,76.10:MASTERY_RATING,36.27:AVOIDANCE_RATING,0.03:AP,0.01:STA,69.94:INT,0:LEECH_RATING,11.93:SPELL_DMG,0:STR,0",
			["MONK:2"] = "ARMOR_SPECIALIZATION_STAT,INT:LE_ITEM_CLASS_WEAPON,0,4,6,7,10,13:LE_ITEM_CLASS_ARMOR,0,1,2:XP_BONUS,1000:INT,2.54:SPELL_DMG,0:SPELL_HASTE_RATING,0.90:SPELL_CRIT_RATING,0.94:MASTERY_RATING,0.45:VERSATILITY_RATING,0.93:DPS,0.01:STA,0:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:LEECH_RATING,0.41:STR,0",
	        ["MONK:3"] = "ARMOR_SPECIALIZATION_STAT,AGI:LE_ITEM_CLASS_WEAPON,0,4,7,13:LE_ITEM_CLASS_ARMOR,0,1,2:XP_BONUS,1000:DPS,13.77:DPS|MAIN,1.81:DPS|OFF,0.18:AGI,2.32:VERSATILITY_RATING,0.90:MELEE_HASTE_RATING,0.68:MELEE_CRIT_RATING,0.88:MASTERY_RATING,0.81:AP,0.01:STA,0.14:ARMOR,0.01:AVOIDANCE_RATING,0.02:INT,0:LEECH_RATING,0.01:SPELL_DMG,0:STR,0",
	        ["WARRIOR:1"] = "ARMOR_SPECIALIZATION_STAT,STR:LE_ITEM_CLASS_WEAPON,1,5,6,8,10:LE_ITEM_CLASS_ARMOR,0,1,2,3,4:XP_BONUS,1000:DPS,2.25:STR,2.84:VERSATILITY_RATING,0.99:MELEE_CRIT_RATING,1.30:AP,0.01:MASTERY_RATING,1.14:MELEE_HASTE_RATING,1.01:STA,0:AGI,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:INT,0:LEECH_RATING,0.01:SPELL_DMG,0",
	        ["WARRIOR:2"] = "ARMOR_SPECIALIZATION_STAT,STR:LE_ITEM_CLASS_WEAPON,0,1,4,5,6,7,8,10,13,15:LE_ITEM_CLASS_ARMOR,0,1,2,3,4:XP_BONUS,1000:DPS,1.45:DPS|MAIN,1.45:DPS|OFF,0.75:STR,2.67:VERSATILITY_RATING,0.50:MELEE_CRIT_RATING,1.02:MASTERY_RATING,0.80:MELEE_HASTE_RATING,1.07:AP,0.01:STA,0:AGI,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:INT,0:LEECH_RATING,0.01:SPELL_DMG,0",
	        ["WARRIOR:3"] = "ARMOR_SPECIALIZATION_STAT,STA:LE_ITEM_CLASS_WEAPON,0,4,7,13,15:LE_ITEM_CLASS_ARMOR,0,1,2,3,4,6:XP_BONUS,1000:DPS,17.20:STA,87.38:MASTERY_RATING,39.00:STR,101.76:AVOIDANCE_RATING,18.82:ARMOR,54.48:MELEE_HASTE_RATING,0.66:MELEE_CRIT_RATING,26.05:AP,0.01:LEECH_RATING,26.51:VERSATILITY_RATING,22.99:AGI,0:INT,0:SPELL_DMG,0",
	        ["PALADIN:1"] = "ARMOR_SPECIALIZATION_STAT,INT:LE_ITEM_CLASS_WEAPON,0,4,6,7:LE_ITEM_CLASS_ARMOR,0,1,2,3,4,6:XP_BONUS,1000:INT,3.11:SPELL_DMG,0:MASTERY_RATING,1.44:SPELL_CRIT_RATING,1.02:SPELL_HASTE_RATING,0.78:VERSATILITY_RATING,1.10:DPS,0.01:STA,0:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:LEECH_RATING,0.68:STR,0",
	        ["PALADIN:2"] = "ARMOR_SPECIALIZATION_STAT,STA:LE_ITEM_CLASS_WEAPON,0,4,6,7:LE_ITEM_CLASS_ARMOR,0,1,2,3,4,6:XP_BONUS,1000:DPS,59.80:STA,68.56:MELEE_CRIT_RATING,22.96:VERSATILITY_RATING,39.99:MASTERY_RATING,51.79:ARMOR,42.73:MELEE_HASTE_RATING,18.46:STR,48.67:AVOIDANCE_RATING,5.48:LEECH_RATING,18.62:AP,0.01:AGI,0:INT,0:SPELL_DMG,0",
		    ["PALADIN:3"] = "ARMOR_SPECIALIZATION_STAT,STR:LE_ITEM_CLASS_WEAPON,1,5,8:LE_ITEM_CLASS_ARMOR,0,1,2,3,4:XP_BONUS,1000:DPS,2.11:STR,2.54:VERSATILITY_RATING,0.96:MELEE_HASTE_RATING,1.04:MASTERY_RATING,0.95:AP,0.1:MELEE_CRIT_RATING,0.98:STA,0:AGI,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:INT,0:LEECH_RATING,0.01:SPELL_DMG,0",
			["DRUID:1"] = "ARMOR_SPECIALIZATION_STAT,INT:LE_ITEM_CLASS_WEAPON,4,5,6,10,13,14,15:LE_ITEM_CLASS_ARMOR,0,1,2:XP_BONUS,1000:INT,2.81:SPELL_DMG,0:SPELL_HASTE_RATING,1.15:SPELL_CRIT_RATING,1.06:MASTERY_RATING,1.18:VERSATILITY_RATING,1.01:DPS,0.01:STA,0:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.03:LEECH_RATING,0.01:STR,0",
	        ["DRUID:2"] = "ARMOR_SPECIALIZATION_STAT,AGI:LE_ITEM_CLASS_WEAPON,4,5,6,10,13,14,15:LE_ITEM_CLASS_ARMOR,0,1,2:XP_BONUS,1000:DPS,1.97:AGI,2.66:AP,0.1:VERSATILITY_RATING,0.95:MASTERY_RATING,1.20:MELEE_CRIT_RATING,1.21:MELEE_HASTE_RATING,0.91:STA,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:INT,0:LEECH_RATING,0.01:SPELL_DMG,0:STR,0",
	        ["DRUID:3"] = "ARMOR_SPECIALIZATION_STAT,STA:LE_ITEM_CLASS_WEAPON,4,5,6,10,13,14,15:LE_ITEM_CLASS_ARMOR,0,1,2:XP_BONUS,1000:DPS,32.97:AGI,82.31:STA,130.01:MELEE_CRIT_RATING,23.85:VERSATILITY_RATING,56.79:MASTERY_RATING,46.02:AVOIDANCE_RATING,0.02:MELEE_HASTE_RATING,34.94:ARMOR,201.28:AP,0.01:LEECH_RATING,34.26:INT,0:SPELL_DMG,0:STR,0",
	        ["DRUID:4"] = "ARMOR_SPECIALIZATION_STAT,INT:LE_ITEM_CLASS_WEAPON,4,5,6,10,13,14,15:LE_ITEM_CLASS_ARMOR,0,1,2:XP_BONUS,1000:INT,2.67:SPELL_DMG,0:SPELL_HASTE_RATING,1.15:MASTERY_RATING,0.87:SPELL_CRIT_RATING,0.92:VERSATILITY_RATING,0.97:DPS,0.01:STA,0:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:LEECH_RATING,0.51:STR,0",	        
			["ROGUE:1"] = "ARMOR_SPECIALIZATION_STAT,AGI:LE_ITEM_CLASS_WEAPON,15:LE_ITEM_CLASS_ARMOR,0,1,2:XP_BONUS,1000:DPS,10.65:DPS|MAIN,10.65:DPS|OFF,1.32:AGI,2.14:VERSATILITY_RATING,0.74:MASTERY_RATING,0.72:MELEE_HASTE_RATING,0.90:AP,0.01:MELEE_CRIT_RATING,0.83:STA,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:INT,0:LEECH_RATING,0.01:SPELL_DMG,0:STR,0",	
	        ["ROGUE:2"] = "ARMOR_SPECIALIZATION_STAT,AGI:LE_ITEM_CLASS_WEAPON,0,4,7,13:LE_ITEM_CLASS_ARMOR,0,1,2:XP_BONUS,1000:DPS,12.55:DPS|MAIN,12.55:DPS|OFF,1.84:AGI,2.57:VERSATILITY_RATING,0.86:MELEE_HASTE_RATING,0.80:MASTERY_RATING,0.65:MELEE_CRIT_RATING,0.79:AP,0.01:STA,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:INT,0:LEECH_RATING,0.01:SPELL_DMG,0:STR,0",
	        ["ROGUE:3"] = "ARMOR_SPECIALIZATION_STAT,AGI:LE_ITEM_CLASS_WEAPON,0,4,7,13,15:LE_ITEM_CLASS_ARMOR,0,1,2:XP_BONUS,1000:DPS,15.24:DPS|MAIN,15.24:DPS|OFF,0.96:AGI,2.90:VERSATILITY_RATING,0.97:MASTERY_RATING,0.77:MELEE_HASTE_RATING,0.73:AP,0.01:MELEE_CRIT_RATING,0.94:STA,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:INT,0:LEECH_RATING,0.01:SPELL_DMG,0:STR,0",	
	        ["SHAMAN:1"] = "ARMOR_SPECIALIZATION_STAT,INT:LE_ITEM_CLASS_WEAPON,0,1,4,5,10,13,15:LE_ITEM_CLASS_ARMOR,0,1,2,3,6:XP_BONUS,1000:INT,2.78:SPELL_DMG,0:SPELL_HASTE_RATING,0.73:MASTERY_RATING,0.72:SPELL_CRIT_RATING,0.85:VERSATILITY_RATING,0.83:DPS,0.01:STA,0:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.03:LEECH_RATING,0.01:STR,0",
	        ["SHAMAN:2"] = "ARMOR_SPECIALIZATION_STAT,AGI:LE_ITEM_CLASS_WEAPON,0,4,13:LE_ITEM_CLASS_ARMOR,0,1,2,3:XP_BONUS,1000:DPS,11.27:DPS|MAIN,11.27:DPS|OFF,1.52:AGI,2.75:VERSATILITY_RATING,0.95:MELEE_HASTE_RATING,1.12:MASTERY_RATING,0.94:MELEE_CRIT_RATING,0.91:AP,0.01:STA,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:INT,0:LEECH_RATING,0.01:SPELL_DMG,0:STR,0",
	        ["SHAMAN:3"] = "ARMOR_SPECIALIZATION_STAT,INT:LE_ITEM_CLASS_WEAPON,0,1,4,5,10,13,15:LE_ITEM_CLASS_ARMOR,0,1,2,3,6:XP_BONUS,1000:INT,5.58:SPELL_DMG,0:MASTERY_RATING,3.67:SPELL_CRIT_RATING,4.08:VERSATILITY_RATING,4.08:DPS,0.01:SPELL_HASTE_RATING,3.67:LEECH_RATING,0.01:STA,0:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:STR,0",
			["HUNTER:1"] = "ARMOR_SPECIALIZATION_STAT,AGI:LE_ITEM_CLASS_WEAPON,2,3,18:LE_ITEM_CLASS_ARMOR,0,1,2,3:XP_BONUS,1000:DPS,1.86:AGI,2.82:VERSATILITY_RATING,0.91:AP,0.01:RANGED_CRIT_RATING,0.96:RANGED_HASTE_RATING,1.15:MASTERY_RATING,0.78:STA,0:ARMOR,0.01:AVOIDANCE_RATING,0.03:INT,0:LEECH_RATING,0.01:SPELL_DMG,0:STR,0",
	        ["HUNTER:2"] = "ARMOR_SPECIALIZATION_STAT,AGI:LE_ITEM_CLASS_WEAPON,2,3,18:LE_ITEM_CLASS_ARMOR,0,1,2,3:XP_BONUS,1000:DPS,2.54:AGI,2.60:VERSATILITY_RATING,0.92:RANGED_CRIT_RATING,1.09:RANGED_HASTE_RATING,0.93:AP,0.01:MASTERY_RATING,1.19:STA,0:ARMOR,0.01:AVOIDANCE_RATING,0.03:INT,0:LEECH_RATING,0.01:SPELL_DMG,0:STR,0",
	        ["HUNTER:3"] = "ARMOR_SPECIALIZATION_STAT,AGI:LE_ITEM_CLASS_WEAPON,1,6,8,10:LE_ITEM_CLASS_ARMOR,0,1,2,3:XP_BONUS,1000:DPS,1.55:AGI,2.42:VERSATILITY_RATING,0.81:AP,0.01:RANGED_CRIT_RATING,0.85:RANGED_HASTE_RATING,1.06:MASTERY_RATING,0.65:STA,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:INT,0:LEECH_RATING,0.01:SPELL_DMG,0:STR,0",
			["MAGE:1"] = "ARMOR_SPECIALIZATION_STAT,INT:LE_ITEM_CLASS_WEAPON,7,10,14,15,19:LE_ITEM_CLASS_ARMOR,0,1:XP_BONUS,1000:INT,2.76:SPELL_DMG,0:SPELL_HASTE_RATING,0.78:SPELL_CRIT_RATING,1.06:VERSATILITY_RATING,0.99:MASTERY_RATING,1.22:DPS,0.01:STA,0:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.03:LEECH_RATING,0.01:STR,0",
	        ["MAGE:2"] = "ARMOR_SPECIALIZATION_STAT,INT:LE_ITEM_CLASS_WEAPON,7,10,14,15,19:LE_ITEM_CLASS_ARMOR,0,1:XP_BONUS,1000:INT,2.69:SPELL_DMG,0:SPELL_HASTE_RATING,0.97:SPELL_CRIT_RATING,0.78:VERSATILITY_RATING,0.95:MASTERY_RATING,0.82:DPS,0.01:STA,0:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.03:LEECH_RATING,0.01:STR,0",
	        ["MAGE:3"] = "ARMOR_SPECIALIZATION_STAT,INT:LE_ITEM_CLASS_WEAPON,7,10,14,15,19:LE_ITEM_CLASS_ARMOR,0,1:XP_BONUS,1000:INT,2.48:SPELL_DMG,0:SPELL_HASTE_RATING,1.06:SPELL_CRIT_RATING,0.91:VERSATILITY_RATING,0.89:MASTERY_RATING,0.79:DPS,0.01:STA,0:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.03:LEECH_RATING,0.01:STR,0",	
	        ["PRIEST:1"] = "ARMOR_SPECIALIZATION_STAT,INT:LE_ITEM_CLASS_WEAPON,10,15,4,19:LE_ITEM_CLASS_ARMOR,0,1:XP_BONUS,1000:INT,2.03:SPELL_DMG,0:SPELL_HASTE_RATING,0.16:SPELL_CRIT_RATING,0.72:MASTERY_RATING,0.60:VERSATILITY_RATING,0.79:DPS,0.01:STA,0:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:LEECH_RATING,0.40:STR,0",
	        ["PRIEST:2"] = "ARMOR_SPECIALIZATION_STAT,INT:LE_ITEM_CLASS_WEAPON,10,15,4,19:LE_ITEM_CLASS_ARMOR,0,1:XP_BONUS,1000:INT,3.28:SPELL_DMG,0:SPELL_HASTE_RATING,0.60:SPELL_CRIT_RATING,1.09:MASTERY_RATING,1.13:VERSATILITY_RATING,1.07:DPS,0.01:STA,0:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:LEECH_RATING,0.65:STR,0",
	        ["PRIEST:3"] = "ARMOR_SPECIALIZATION_STAT,INT:LE_ITEM_CLASS_WEAPON,10,15,4,19:LE_ITEM_CLASS_ARMOR,0,1:XP_BONUS,1000:INT,2.92:SPELL_DMG,0:SPELL_HASTE_RATING,1.31:SPELL_CRIT_RATING,1.12:MASTERY_RATING,1.28:VERSATILITY_RATING,1.02:DPS,0.01:STA,0:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.03:LEECH_RATING,0.02:STR,0",
	        ["WARLOCK:1"] = "ARMOR_SPECIALIZATION_STAT,INT:LE_ITEM_CLASS_WEAPON,6,10,14,15,19:LE_ITEM_CLASS_ARMOR,0,1:XP_BONUS,1000:INT,2.68:SPELL_DMG,0:MASTERY_RATING,1.06:SPELL_HASTE_RATING,0.96:SPELL_CRIT_RATING,1.21:VERSATILITY_RATING,0.96:DPS,0.01:STA,0:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.03:LEECH_RATING,0.01:STR,0",
	        ["WARLOCK:2"] = "ARMOR_SPECIALIZATION_STAT,INT:LE_ITEM_CLASS_WEAPON,7,10,14,15,19:LE_ITEM_CLASS_ARMOR,0,1:XP_BONUS,1000:INT,1.97:SPELL_DMG,0:MASTERY_RATING,0.69:SPELL_HASTE_RATING,1.63:SPELL_CRIT_RATING,0.71:VERSATILITY_RATING,0.73:DPS,0.01:STA,0:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.03:LEECH_RATING,0.01:STR,0",
	        ["WARLOCK:3"] = "ARMOR_SPECIALIZATION_STAT,INT:LE_ITEM_CLASS_WEAPON,7,10,14,15,19:LE_ITEM_CLASS_ARMOR,0,1:XP_BONUS,1000:INT,2.40:SPELL_DMG,0:SPELL_CRIT_RATING,0.88:SPELL_HASTE_RATING,0.72:MASTERY_RATING,0.97:VERSATILITY_RATING,0.91:DPS,0.01:STA,0:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.03:LEECH_RATING,0.01:STR,0",
	        ["DEMONHUNTER:1"] = "ARMOR_SPECIALIZATION_STAT,AGI:LE_ITEM_CLASS_WEAPON,0,7,9,13:LE_ITEM_CLASS_ARMOR,0,1,2:XP_BONUS,1000:DPS,2.31:DPS|MAIN,2.31:DPS|OFF,0.46:AGI,2.63:VERSATILITY_RATING,0.91:MASTERY_RATING,0.76:MELEE_HASTE_RATING,0.85:AP,0.01:MELEE_CRIT_RATING,0.87:STA,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:INT,0:LEECH_RATING,0.01:SPELL_DMG,0:STR,0",				
	        ["DEMONHUNTER:2"] = "ARMOR_SPECIALIZATION_STAT,AGI:LE_ITEM_CLASS_WEAPON,0,7,9,13:LE_ITEM_CLASS_ARMOR,0,1,2:XP_BONUS,1000:DPS,77.87:DPS|MAIN,77.87:DPS|OFF,15.57:AGI,78.20:VERSATILITY_RATING,76.74:MELEE_HASTE_RATING,48.72:MASTERY_RATING,28.97:MELEE_CRIT_RATING,36.32:AP,0:STA,68.82:ARMOR,192.99:AVOIDANCE_RATING,30.74:INT,0:LEECH_RATING,3.16:SPELL_DMG,0:STR,0",	
	        ["EVOKER:1"] = "ARMOR_SPECIALIZATION_STAT,AGI:LE_ITEM_CLASS_WEAPON,0,4,7,15,13,10:LE_ITEM_CLASS_ARMOR,0,1,2,3:XP_BONUS,1000:INT,2.03:SPELL_DMG,0:SPELL_HASTE_RATING,0.16:SPELL_CRIT_RATING,0.72:MASTERY_RATING,0.60:VERSATILITY_RATING,0.79:DPS,0.01:STA,0.05:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.02:LEECH_RATING,0.40:STR,0",
	        ["EVOKER:2"] = "ARMOR_SPECIALIZATION_STAT,AGI:LE_ITEM_CLASS_WEAPON,0,4,7,15,13,10:LE_ITEM_CLASS_ARMOR,0,1,2,3:XP_BONUS,1000:INT,2.48:SPELL_DMG,0:SPELL_HASTE_RATING,1.06:SPELL_CRIT_RATING,0.91:VERSATILITY_RATING,0.89:MASTERY_RATING,0.79:DPS,0.01:STA,0.05:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.03:LEECH_RATING,0.01:STR,0",	
			["EVOKER:3"] = "ARMOR_SPECIALIZATION_STAT,AGI:LE_ITEM_CLASS_WEAPON,0,4,7,15,13,10:LE_ITEM_CLASS_ARMOR,0,1,2,3:XP_BONUS,1000:INT,2.51:SPELL_DMG,2.29:SPELL_HASTE_RATING,0.85:SPELL_CRIT_RATING,0.75:VERSATILITY_RATING,0.55:MASTERY_RATING,1.00:DPS,0.01:STA,0.05:AGI,0:AP,0:ARMOR,0.01:AVOIDANCE_RATING,0.03:LEECH_RATING,0.01:STR,0",			
			["tiebreaker"] = {{"ARMOR",1},{"DPS",1},{"STA",2}}
		}

		--source: simulationcraft (https://github.com/simulationcraft/simc/blob/master/COPYING)
		--  Combat rating values for level 1 - 110, wow build 22248
		local combat_ratings = {
		  -- Dodge
		  ["DODGE_RATING"] = {
			3.013875853,	3.013875853,	3.013875853,	3.013875853,	3.013875853,	--    5
			3.013875853,	3.013875853,	3.013875853,	3.013875853,	3.013875853,	--   10
			3.013875853,	3.164569645,	3.315263438,	3.46595723,	3.616651023,	--   15
			3.767344816,	3.918038608,	4.068732401,	4.219426194,	4.370119986,	--   20
			4.520813779,	4.671507572,	4.822201364,	4.972895157,	5.123588949,	--   25
			5.278731223,	5.440049249,	5.607820368,	5.782335738,	5.96390108,	--   30
			6.152837466,	6.349482151,	6.554189456,	6.767331697,	6.989300177,	--   35
			7.220506227,	7.461382314,	7.712383215,	7.973987254,	8.246697618,	--   40
			8.554045819,	8.872848655,	9.203533033,	9.546541768,	9.90233418,	--   45
			10.27138671,	10.65419354,	11.0512673,	11.4631397,	11.89036227,	--   50
			13.12756609,	14.49350218,	16.00156526,	17.66654377,	19.50476493,	--   55
			21.5342548,	23.77491507,	26.24871824,	28.97992304,	39.0000001,	--   60
		  },
		  -- Parry
		  ["PARRY_RATING"] = {
			3.013875853,	3.013875853,	3.013875853,	3.013875853,	3.013875853,	--    5
			3.013875853,	3.013875853,	3.013875853,	3.013875853,	3.013875853,	--   10
			3.013875853,	3.164569645,	3.315263438,	3.46595723,	3.616651023,	--   15
			3.767344816,	3.918038608,	4.068732401,	4.219426194,	4.370119986,	--   20
			4.520813779,	4.671507572,	4.822201364,	4.972895157,	5.123588949,	--   25
			5.278731223,	5.440049249,	5.607820368,	5.782335738,	5.96390108,	--   30
			6.152837466,	6.349482151,	6.554189456,	6.767331697,	6.989300177,	--   35
			7.220506227,	7.461382314,	7.712383215,	7.973987254,	8.246697618,	--   40
			8.554045819,	8.872848655,	9.203533033,	9.546541768,	9.90233418,	--   45
			10.27138671,	10.65419354,	11.0512673,	11.4631397,	11.89036227,	--   50
			13.12756609,	14.49350218,	16.00156526,	17.66654377,	19.50476493,	--   55
			21.5342548,	23.77491507,	26.24871824,	28.97992304,	39.0000001,	--   60
		  },
		  -- Block
		  ["BLOCK_RATING"] = {
			1.35238019,	1.35238019,	1.35238019,	1.35238019,	1.35238019,	--    5
			1.35238019,	1.35238019,	1.35238019,	1.35238019,	1.35238019,	--   10
			1.35238019,	1.4199992,	1.487618209,	1.555237219,	1.622856228,	--   15
			1.690475238,	1.758094247,	1.825713257,	1.893332266,	1.960951276,	--   20
			2.028570285,	2.096189295,	2.163808304,	2.231427314,	2.299046323,	--   25
			2.368661446,	2.44104774,	2.516329652,	2.594637831,	2.676109459,	--   30
			2.760888607,	2.849126606,	2.940982448,	3.036623197,	3.136224438,	--   35
			3.239970743,	3.348056167,	3.460684776,	3.578071204,	3.700441239,	--   40
			3.838353893,	3.981406448,	4.129790463,	4.283704639,	4.443355081,	--   45
			4.608955573,	4.780727872,	4.958901994,	5.143716531,	5.335418967,	--   50
			5.890574529,	6.503494569,	7.180189539,	7.927295283,	8.75213811,	--   55
			9.66280664,	10.66823112,	11.778271,	13.00381162,	17.50000005,	--   60
		  },
		  -- Hit - Melee
		  ["MELEE_HIT_RATING"] = {
			1.487618209,	1.487618209,	1.487618209,	1.487618209,	1.487618209,	--    5
			1.487618209,	1.487618209,	1.487618209,	1.487618209,	1.487618209,	--   10
			1.487618209,	1.56199912,	1.63638003,	1.710760941,	1.785141851,	--   15
			1.859522762,	1.933903672,	2.008284583,	2.082665493,	2.157046403,	--   20
			2.231427314,	2.305808224,	2.380189135,	2.454570045,	2.528950956,	--   25
			2.605527591,	2.685152514,	2.767962617,	2.854101614,	2.943720405,	--   30
			3.036977467,	3.134039267,	3.235080693,	3.340285517,	3.449846882,	--   35
			3.563967817,	3.682861783,	3.806753254,	3.935878324,	4.070485363,	--   40
			4.222189282,	4.379547093,	4.54276951,	4.712075103,	4.887690589,	--   45
			5.069851131,	5.258800659,	5.454792193,	5.658088184,	5.868960864,	--   50
			6.479631981,	7.153844026,	7.898208493,	8.720024812,	9.627351921,	--   55
			10.6290873,	11.73505423,	12.9560981,	14.30419278,	19.25000005,	--   60
		  },
		  -- Hit - Ranged
		  ["RANGED_HIT_RATING"] = {
			1.487618209,	1.487618209,	1.487618209,	1.487618209,	1.487618209,	--    5
			1.487618209,	1.487618209,	1.487618209,	1.487618209,	1.487618209,	--   10
			1.487618209,	1.56199912,	1.63638003,	1.710760941,	1.785141851,	--   15
			1.859522762,	1.933903672,	2.008284583,	2.082665493,	2.157046403,	--   20
			2.231427314,	2.305808224,	2.380189135,	2.454570045,	2.528950956,	--   25
			2.605527591,	2.685152514,	2.767962617,	2.854101614,	2.943720405,	--   30
			3.036977467,	3.134039267,	3.235080693,	3.340285517,	3.449846882,	--   35
			3.563967817,	3.682861783,	3.806753254,	3.935878324,	4.070485363,	--   40
			4.222189282,	4.379547093,	4.54276951,	4.712075103,	4.887690589,	--   45
			5.069851131,	5.258800659,	5.454792193,	5.658088184,	5.868960864,	--   50
			6.479631981,	7.153844026,	7.898208493,	8.720024812,	9.627351921,	--   55
			10.6290873,	11.73505423,	12.9560981,	14.30419278,	19.25000005,	--   60
		  },
		  -- Hit - Spell
		  ["SPELL_HIT_RATING"] = {
			1.487618209,	1.487618209,	1.487618209,	1.487618209,	1.487618209,	--    5
			1.487618209,	1.487618209,	1.487618209,	1.487618209,	1.487618209,	--   10
			1.487618209,	1.56199912,	1.63638003,	1.710760941,	1.785141851,	--   15
			1.859522762,	1.933903672,	2.008284583,	2.082665493,	2.157046403,	--   20
			2.231427314,	2.305808224,	2.380189135,	2.454570045,	2.528950956,	--   25
			2.605527591,	2.685152514,	2.767962617,	2.854101614,	2.943720405,	--   30
			3.036977467,	3.134039267,	3.235080693,	3.340285517,	3.449846882,	--   35
			3.563967817,	3.682861783,	3.806753254,	3.935878324,	4.070485363,	--   40
			4.222189282,	4.379547093,	4.54276951,	4.712075103,	4.887690589,	--   45
			5.069851131,	5.258800659,	5.454792193,	5.658088184,	5.868960864,	--   50
			6.479631981,	7.153844026,	7.898208493,	8.720024812,	9.627351921,	--   55
			10.6290873,	11.73505423,	12.9560981,	14.30419278,	19.25000005,	--   60
		  },
		  -- Crit - Melee
		  ["MELEE_CRIT_RATING"] = {
			2.704760381,	2.704760381,	2.704760381,	2.704760381,	2.704760381,	--    5
			2.704760381,	2.704760381,	2.704760381,	2.704760381,	2.704760381,	--   10
			2.704760381,	2.8399984,	2.975236419,	3.110474438,	3.245712457,	--   15
			3.380950476,	3.516188495,	3.651426514,	3.786664533,	3.921902552,	--   20
			4.057140571,	4.19237859,	4.327616609,	4.462854628,	4.598092647,	--   25
			4.737322892,	4.88209548,	5.032659304,	5.189275662,	5.352218918,	--   30
			5.521777213,	5.698253213,	5.881964896,	6.073246395,	6.272448877,	--   35
			6.479941485,	6.696112333,	6.921369552,	7.156142407,	7.400882478,	--   40
			7.676707786,	7.962812896,	8.259580927,	8.567409279,	8.886710161,	--   45
			9.217911147,	9.561455743,	9.917803988,	10.28743306,	10.67083793,	--   50
			11.78114906,	13.00698914,	14.36037908,	15.85459057,	17.50427622,	--   55
			19.32561328,	21.33646224,	23.55654201,	26.00762324,	35.00000009,	--   60
		  },
		  -- Crit - Ranged
		  ["RANGED_CRIT_RATING"] = {
			2.704760381,	2.704760381,	2.704760381,	2.704760381,	2.704760381,	--    5
			2.704760381,	2.704760381,	2.704760381,	2.704760381,	2.704760381,	--   10
			2.704760381,	2.8399984,	2.975236419,	3.110474438,	3.245712457,	--   15
			3.380950476,	3.516188495,	3.651426514,	3.786664533,	3.921902552,	--   20
			4.057140571,	4.19237859,	4.327616609,	4.462854628,	4.598092647,	--   25
			4.737322892,	4.88209548,	5.032659304,	5.189275662,	5.352218918,	--   30
			5.521777213,	5.698253213,	5.881964896,	6.073246395,	6.272448877,	--   35
			6.479941485,	6.696112333,	6.921369552,	7.156142407,	7.400882478,	--   40
			7.676707786,	7.962812896,	8.259580927,	8.567409279,	8.886710161,	--   45
			9.217911147,	9.561455743,	9.917803988,	10.28743306,	10.67083793,	--   50
			11.78114906,	13.00698914,	14.36037908,	15.85459057,	17.50427622,	--   55
			19.32561328,	21.33646224,	23.55654201,	26.00762324,	35.00000009,	--   60
		  },
		  -- Crit - Spell
		  ["SPELL_CRIT_RATING"] = {
			2.704760381,	2.704760381,	2.704760381,	2.704760381,	2.704760381,	--    5
			2.704760381,	2.704760381,	2.704760381,	2.704760381,	2.704760381,	--   10
			2.704760381,	2.8399984,	2.975236419,	3.110474438,	3.245712457,	--   15
			3.380950476,	3.516188495,	3.651426514,	3.786664533,	3.921902552,	--   20
			4.057140571,	4.19237859,	4.327616609,	4.462854628,	4.598092647,	--   25
			4.737322892,	4.88209548,	5.032659304,	5.189275662,	5.352218918,	--   30
			5.521777213,	5.698253213,	5.881964896,	6.073246395,	6.272448877,	--   35
			6.479941485,	6.696112333,	6.921369552,	7.156142407,	7.400882478,	--   40
			7.676707786,	7.962812896,	8.259580927,	8.567409279,	8.886710161,	--   45
			9.217911147,	9.561455743,	9.917803988,	10.28743306,	10.67083793,	--   50
			11.78114906,	13.00698914,	14.36037908,	15.85459057,	17.50427622,	--   55
			19.32561328,	21.33646224,	23.55654201,	26.00762324,	35.00000009,	--   60
		  },
		  -- Resilience - Player Damage
		  ["RESILIENCE_RATING"] = {
			1.35238019,	1.35238019,	1.35238019,	1.35238019,	1.35238019,	--    5
			1.35238019,	1.35238019,	1.35238019,	1.35238019,	1.35238019,	--   10
			1.35238019,	1.4199992,	1.487618209,	1.555237219,	1.622856228,	--   15
			1.690475238,	1.758094247,	1.825713257,	1.893332266,	1.960951276,	--   20
			2.028570285,	2.096189295,	2.163808304,	2.231427314,	2.299046323,	--   25
			2.368661446,	2.44104774,	2.516329652,	2.594637831,	2.676109459,	--   30
			2.760888607,	2.849126606,	2.940982448,	3.036623197,	3.136224438,	--   35
			3.239970743,	3.348056167,	3.460684776,	3.578071204,	3.700441239,	--   40
			3.838353893,	3.981406448,	4.129790463,	4.283704639,	4.443355081,	--   45
			4.608955573,	4.780727872,	4.958901994,	5.143716531,	5.335418967,	--   50
			5.890574529,	6.503494569,	7.180189539,	7.927295283,	8.75213811,	--   55
			9.66280664,	10.66823112,	11.778271,	13.00381162,	17.50000005,	--   60
		  },
		  -- Lifesteal
		  ["LEECH_RATING"] = {
			1.622856228,	1.622856228,	1.622856228,	1.622856228,	1.622856228,	--    5
			1.622856228,	1.622856228,	1.622856228,	1.622856228,	1.622856228,	--   10
			1.622856228,	1.70399904,	1.785141851,	1.866284663,	1.947427474,	--   15
			2.028570285,	2.109713097,	2.190855908,	2.27199872,	2.353141531,	--   20
			2.434284342,	2.515427154,	2.596569965,	2.677712777,	2.758855588,	--   25
			2.842393735,	2.929257288,	3.019595583,	3.113565397,	3.211331351,	--   30
			3.313066328,	3.418951928,	3.529178938,	3.643947837,	3.763469326,	--   35
			3.887964891,	4.0176674,	4.152821731,	4.293685444,	4.440529487,	--   40
			4.606024672,	4.777687737,	4.955748556,	5.140445567,	5.332026097,	--   45
			5.530746688,	5.736873446,	5.950682393,	6.172459837,	6.40250276,	--   50
			7.068689434,	7.804193483,	8.616227447,	9.51275434,	10.50256573,	--   55
			11.59536797,	12.80187735,	14.1339252,	15.60457394,	21.00000006,	--   60
		  },
		  -- Haste - Melee
		  ["MELEE_HASTE_RATING"] = {
			2.550202644,	2.550202644,	2.550202644,	2.550202644,	2.550202644,	--    5
			2.550202644,	2.550202644,	2.550202644,	2.550202644,	2.550202644,	--   10
			2.550202644,	2.677712777,	2.805222909,	2.932733041,	3.060243173,	--   15
			3.187753306,	3.315263438,	3.44277357,	3.570283702,	3.697793835,	--   20
			3.825303967,	3.952814099,	4.080324231,	4.207834363,	4.335344496,	--   25
			4.466618727,	4.603118595,	4.745078773,	4.892745624,	5.046377837,	--   30
			5.206247087,	5.372638744,	5.545852617,	5.726203744,	5.914023226,	--   35
			6.109659115,	6.313477343,	6.525862721,	6.747219984,	6.977974908,	--   40
			7.23803877,	7.507795016,	7.787604874,	8.077843034,	8.378898152,	--   45
			8.691173367,	9.015086844,	9.351072331,	9.699579745,	10.06107577,	--   50
			11.10794054,	12.26373262,	13.53978599,	14.94861396,	16.50403187,	--   55
			18.22129252,	20.11723583,	22.21045389,	24.52147334,	33.00000009,	--   60
		  },
		  -- Haste - Ranged
		  ["RANGED_HASTE_RATING"] = {
			2.550202644,	2.550202644,	2.550202644,	2.550202644,	2.550202644,	--    5
			2.550202644,	2.550202644,	2.550202644,	2.550202644,	2.550202644,	--   10
			2.550202644,	2.677712777,	2.805222909,	2.932733041,	3.060243173,	--   15
			3.187753306,	3.315263438,	3.44277357,	3.570283702,	3.697793835,	--   20
			3.825303967,	3.952814099,	4.080324231,	4.207834363,	4.335344496,	--   25
			4.466618727,	4.603118595,	4.745078773,	4.892745624,	5.046377837,	--   30
			5.206247087,	5.372638744,	5.545852617,	5.726203744,	5.914023226,	--   35
			6.109659115,	6.313477343,	6.525862721,	6.747219984,	6.977974908,	--   40
			7.23803877,	7.507795016,	7.787604874,	8.077843034,	8.378898152,	--   45
			8.691173367,	9.015086844,	9.351072331,	9.699579745,	10.06107577,	--   50
			11.10794054,	12.26373262,	13.53978599,	14.94861396,	16.50403187,	--   55
			18.22129252,	20.11723583,	22.21045389,	24.52147334,	33.00000009,	--   60
		  },
		  -- Haste - Spell
		  ["SPELL_HASTE_RATING"] = {
			2.550202644,	2.550202644,	2.550202644,	2.550202644,	2.550202644,	--    5
			2.550202644,	2.550202644,	2.550202644,	2.550202644,	2.550202644,	--   10
			2.550202644,	2.677712777,	2.805222909,	2.932733041,	3.060243173,	--   15
			3.187753306,	3.315263438,	3.44277357,	3.570283702,	3.697793835,	--   20
			3.825303967,	3.952814099,	4.080324231,	4.207834363,	4.335344496,	--   25
			4.466618727,	4.603118595,	4.745078773,	4.892745624,	5.046377837,	--   30
			5.206247087,	5.372638744,	5.545852617,	5.726203744,	5.914023226,	--   35
			6.109659115,	6.313477343,	6.525862721,	6.747219984,	6.977974908,	--   40
			7.23803877,	7.507795016,	7.787604874,	8.077843034,	8.378898152,	--   45
			8.691173367,	9.015086844,	9.351072331,	9.699579745,	10.06107577,	--   50
			11.10794054,	12.26373262,	13.53978599,	14.94861396,	16.50403187,	--   55
			18.22129252,	20.11723583,	22.21045389,	24.52147334,	33.00000009,	--   60
		  },
		  -- Expertise
		  ["EXPERTISE_RATING"] = {
			1.487618209,	1.487618209,	1.487618209,	1.487618209,	1.487618209,	--    5
			1.487618209,	1.487618209,	1.487618209,	1.487618209,	1.487618209,	--   10
			1.487618209,	1.56199912,	1.63638003,	1.710760941,	1.785141851,	--   15
			1.859522762,	1.933903672,	2.008284583,	2.082665493,	2.157046403,	--   20
			2.231427314,	2.305808224,	2.380189135,	2.454570045,	2.528950956,	--   25
			2.605527591,	2.685152514,	2.767962617,	2.854101614,	2.943720405,	--   30
			3.036977467,	3.134039267,	3.235080693,	3.340285517,	3.449846882,	--   35
			3.563967817,	3.682861783,	3.806753254,	3.935878324,	4.070485363,	--   40
			4.222189282,	4.379547093,	4.54276951,	4.712075103,	4.887690589,	--   45
			5.069851131,	5.258800659,	5.454792193,	5.658088184,	5.868960864,	--   50
			6.479631981,	7.153844026,	7.898208493,	8.720024812,	9.627351921,	--   55
			10.6290873,	11.73505423,	12.9560981,	14.30419278,	19.25000005,	--   60
		  },
		  -- Mastery
		  ["MASTERY_RATING"] = {
			2.704760381,	2.704760381,	2.704760381,	2.704760381,	2.704760381,	--    5
			2.704760381,	2.704760381,	2.704760381,	2.704760381,	2.704760381,	--   10
			2.704760381,	2.8399984,	2.975236419,	3.110474438,	3.245712457,	--   15
			3.380950476,	3.516188495,	3.651426514,	3.786664533,	3.921902552,	--   20
			4.057140571,	4.19237859,	4.327616609,	4.462854628,	4.598092647,	--   25
			4.737322892,	4.88209548,	5.032659304,	5.189275662,	5.352218918,	--   30
			5.521777213,	5.698253213,	5.881964896,	6.073246395,	6.272448877,	--   35
			6.479941485,	6.696112333,	6.921369552,	7.156142407,	7.400882478,	--   40
			7.676707786,	7.962812896,	8.259580927,	8.567409279,	8.886710161,	--   45
			9.217911147,	9.561455743,	9.917803988,	10.28743306,	10.67083793,	--   50
			11.78114906,	13.00698914,	14.36037908,	15.85459057,	17.50427622,	--   55
			19.32561328,	21.33646224,	23.55654201,	26.00762324,	35.00000009,	--   60
		  },
		  -- PvP Power
		  ["PVP_POWER"] = {
			1.190094567,	1.190094567,	1.190094567,	1.190094567,	1.190094567,	--    5
			1.190094567,	1.190094567,	1.190094567,	1.190094567,	1.190094567,	--   10
			1.190094567,	1.249599296,	1.309104024,	1.368608753,	1.428113481,	--   15
			1.487618209,	1.547122938,	1.606627666,	1.666132394,	1.725637123,	--   20
			1.785141851,	1.84464658,	1.904151308,	1.963656036,	2.023160765,	--   25
			2.084422073,	2.148122011,	2.214370094,	2.283281291,	2.354976324,	--   30
			2.429581974,	2.507231414,	2.588064554,	2.672228414,	2.759877506,	--   35
			2.851174254,	2.946289427,	3.045402603,	3.148702659,	3.25638829,	--   40
			3.377751426,	3.503637674,	3.634215608,	3.769660083,	3.910152471,	--   45
			4.055880905,	4.207040527,	4.363833755,	4.526470547,	4.695168691,	--   50
			5.183705585,	5.723075221,	6.318566795,	6.976019849,	7.701881537,	--   55
			8.503269843,	9.388043387,	10.36487848,	11.44335423,	15.40000004,	--   60
		  },
		  -- Versatility - Damage Done
		  ["VERSATILITY_RATING_DAMAGE_DONE"] = {
			3.091154721,	3.091154721,	3.091154721,	3.091154721,	3.091154721,	--    5
			3.091154721,	3.091154721,	3.091154721,	3.091154721,	3.091154721,	--   10
			3.091154721,	3.245712457,	3.400270193,	3.554827929,	3.709385665,	--   15
			3.863943401,	4.018501137,	4.173058873,	4.327616609,	4.482174345,	--   20
			4.636732081,	4.791289817,	4.945847553,	5.100405289,	5.254963025,	--   25
			5.414083305,	5.579537691,	5.751610634,	5.930600757,	6.11682162,	--   30
			6.310602529,	6.512289386,	6.722245596,	6.940853023,	7.168513002,	--   35
			7.405647412,	7.65269981,	7.910136631,	8.178448466,	8.458151403,	--   40
			8.773380327,	9.100357595,	9.439521059,	9.79132489,	10.15624018,	--   45
			10.5347556,	10.92737799,	11.33463313,	11.75706636,	12.19524335,	--   50
			13.46417035,	14.86513044,	16.4118618,	18.11953208,	20.00488711,	--   55
			22.08641518,	24.38452828,	26.92176229,	29.72299799,	40.0000001,	--   60
		  },
		  -- Versatility - Healing Done
		  ["VERSATILITY_RATING_HEALING"] = {
			3.091154721,	3.091154721,	3.091154721,	3.091154721,	3.091154721,	--    5
			3.091154721,	3.091154721,	3.091154721,	3.091154721,	3.091154721,	--   10
			3.091154721,	3.245712457,	3.400270193,	3.554827929,	3.709385665,	--   15
			3.863943401,	4.018501137,	4.173058873,	4.327616609,	4.482174345,	--   20
			4.636732081,	4.791289817,	4.945847553,	5.100405289,	5.254963025,	--   25
			5.414083305,	5.579537691,	5.751610634,	5.930600757,	6.11682162,	--   30
			6.310602529,	6.512289386,	6.722245596,	6.940853023,	7.168513002,	--   35
			7.405647412,	7.65269981,	7.910136631,	8.178448466,	8.458151403,	--   40
			8.773380327,	9.100357595,	9.439521059,	9.79132489,	10.15624018,	--   45
			10.5347556,	10.92737799,	11.33463313,	11.75706636,	12.19524335,	--   50
			13.46417035,	14.86513044,	16.4118618,	18.11953208,	20.00488711,	--   55
			22.08641518,	24.38452828,	26.92176229,	29.72299799,	40.0000001,	--   60
		  },
		  -- Versatility - Damage Taken
		  ["VERSATILITY_RATING_DAMAGE_TAKEN"] = {
			6.182309441,	6.182309441,	6.182309441,	6.182309441,	6.182309441,	--    5
			6.182309441,	6.182309441,	6.182309441,	6.182309441,	6.182309441,	--   10
			6.182309441,	6.491424913,	6.800540385,	7.109655857,	7.418771329,	--   15
			7.727886801,	8.037002274,	8.346117746,	8.655233218,	8.96434869,	--   20
			9.273464162,	9.582579634,	9.891695106,	10.20081058,	10.50992605,	--   25
			10.82816661,	11.15907538,	11.50322127,	11.86120151,	12.23364324,	--   30
			12.62120506,	13.02457877,	13.44449119,	13.88170605,	14.337026,	--   35
			14.81129482,	15.30539962,	15.82027326,	16.35689693,	16.91630281,	--   40
			17.54676065,	18.20071519,	18.87904212,	19.58264978,	20.31248037,	--   45
			21.06951119,	21.85475598,	22.66926626,	23.51413271,	24.39048671,	--   50
			26.9283407,	29.73026089,	32.82372361,	36.23906415,	40.00977422,	--   55
			44.17283036,	48.76905655,	53.84352459,	59.44599598,	80.00000021,	--   60
		  },
		  -- Speed
		  --[[{
			0.77278868,	0.77278868,	0.77278868,	0.77278868,	0.77278868,	--    5
			0.77278868,	0.77278868,	0.77278868,	0.77278868,	0.77278868,	--   10
			0.77278868,	0.811428114,	0.850067548,	0.888706982,	0.927346416,	--   15
			0.96598585,	1.004625284,	1.043264718,	1.081904152,	1.120543586,	--   20
			1.15918302,	1.197822454,	1.236461888,	1.275101322,	1.313740756,	--   25
			1.353520826,	1.394884423,	1.437902658,	1.482650189,	1.529205405,	--   30
			1.577650632,	1.628072347,	1.680561399,	1.735213256,	1.79212825,	--   35
			1.851411853,	1.913174952,	1.977534158,	2.044612116,	2.114537851,	--   40
			2.193345082,	2.275089399,	2.359880265,	2.447831223,	2.539060046,	--   45
			2.633688899,	2.731844498,	2.833658282,	2.939266589,	3.048810838,	--   50
			3.366042588,	3.716282611,	4.102965451,	4.529883019,	5.001221777,	--   55
			5.521603794,	6.096132069,	6.730440573,	7.430749497,	10.00000003,	--   60
		  },]]
		  -- Avoidance
		  ["AVOIDANCE_RATING"] = {
			1.081904152,	1.081904152,	1.081904152,	1.081904152,	1.081904152,	--    5
			1.081904152,	1.081904152,	1.081904152,	1.081904152,	1.081904152,	--   10
			1.081904152,	1.13599936,	1.190094567,	1.244189775,	1.298284983,	--   15
			1.35238019,	1.406475398,	1.460570605,	1.514665813,	1.568761021,	--   20
			1.622856228,	1.676951436,	1.731046644,	1.785141851,	1.839237059,	--   25
			1.894929157,	1.952838192,	2.013063722,	2.075710265,	2.140887567,	--   30
			2.208710885,	2.279301285,	2.352785959,	2.429298558,	2.508979551,	--   35
			2.591976594,	2.678444933,	2.768547821,	2.862456963,	2.960352991,	--   40
			3.070683114,	3.185125158,	3.303832371,	3.426963712,	3.554684064,	--   45
			3.687164459,	3.824582297,	3.967121595,	4.114973225,	4.268335174,	--   50
			4.712459623,	5.202795655,	5.744151632,	6.341836227,	7.001710488,	--   55
			7.730245312,	8.534584897,	9.422616802,	10.4030493,	14.00000004,	--   60
		  },
		};

		local function GetEffectFromRating(rating, stat, level)
			level = level or UnitLevel("player")
			local ratingPerBonus = combat_ratings[stat][level]
			local bonus = rating / ratingPerBonus;
			return bonus, ratingPerBonus
		end

    -- [classIdentifier][specializationIndex] = {specializationId, }
    GA.classIdentifier2SpecializationsMap = {}
    
    LuaUtils:loop(5000, function(specId)

		local switchId = specId
		if specId == 269 then switchId = 270 end
		if specId == 270 then switchId = 269 end

        local id, name, description, icon, role, class = GetSpecializationInfoByID(switchId)

        if (class ~= nil) then
            if GA.classIdentifier2SpecializationsMap[class] == nil then
                GA.classIdentifier2SpecializationsMap[class] = {}
            end

            local classTable = GA.classIdentifier2SpecializationsMap[class]

			if name and name ~= "" then
				classTable[#classTable + 1] = {["name"] = name, ["id"] = id}
			end
        end
    end)

    if DugisGuideUser.userCustomWeights_v4 == nil then
        DugisGuideUser.userCustomWeights_v4 = {}
    end

    local function GetAllPossibleWeightIdentifiers(classId, specIndex)
        local excludeList = {
            "ARMOR_SPECIALIZATION_STAT"
            ,"LE_ITEM_CLASS_WEAPON"
            ,"LE_ITEM_CLASS_ARMOR"
            --,"XP_BONUS"
        }

        local weightNames = {}
        if scoring[PackStrings(classId, specIndex)] ~= nil then
            local weightValuePairs = {strsplit(":", scoring[PackStrings(classId, specIndex)])}
            for index, weightValuePair in pairs (weightValuePairs) do
                local weightName_weightValue = {strsplit(",", weightValuePair)}
                local weightName = weightName_weightValue[1]
                if not LuaUtils:isInTable(weightName, excludeList) then
                    weightNames[#weightNames + 1] = weightName
                end
            end
        else
            print("|cFF00FF00CANNOT FIND IN THE scoring TABLE (GearAdvisor) THE FOLLOWING ENTRY:", classId..":"..specIndex.."|r")
        end
        return weightNames
    end

    function GA:SpecExists(specIndex)
        local classId = GA:GetCurrentSelectedClassIdentifier()
        return scoring[PackStrings(classId, specIndex)] ~= nil
    end

    local function InitializeUserCustomWeights()
        -- test:   /script print(DugisGuideViewer.Modules.GearAdvisor.userCustomWeights_v4["WARLOCK"][1]["INT"])
        for classId, info in pairs (DugisGuideViewer.defaultLevelingSpec) do
            local classIndex = info.index
            --max: 4. Used 10 for compatibility with new releases/version
            LuaUtils:loop(10, function (specIndex)
                local classAndSpecId = PackStrings(classId, specIndex)
                if scoring[classAndSpecId] ~= nil then
                    local weightValuePairs = {strsplit(":", scoring[classAndSpecId])}
                    for _, weightValuePair in pairs (weightValuePairs) do
                        local weightName_weightValue = {strsplit(",", weightValuePair)}
                        local weightName = weightName_weightValue[1]
                        local weightValue = weightName_weightValue[2]

                        if not DugisGuideUser.userCustomWeights_v4[classId] then
                            DugisGuideUser.userCustomWeights_v4[classId] = {}
                        end

                        if not DugisGuideUser.userCustomWeights_v4[classId][specIndex] then
                            DugisGuideUser.userCustomWeights_v4[classId][specIndex] = {}
                        end

                        if DugisGuideUser.userCustomWeights_v4[classId][specIndex][weightName] == nil then
                            DugisGuideUser.userCustomWeights_v4[classId][specIndex][weightName] = weightValue
                        end
                    end
                else
                    --print("CANNOT FIND IN THE sscoring TABLE THE FOLLOWING ENTRY:", classId..":"..specIndex)
                end
            end)
        end
    end

    InitializeUserCustomWeights()
	;
    local classDropDownIndex2classIdentifierMap = {
         [1] = "DEATHKNIGHT"
        ,[2] = "MONK"
        ,[3] = "WARRIOR"
        ,[4] = "PALADIN"
        ,[5] = "DRUID"
        ,[6] = "ROGUE"
        ,[7] = "SHAMAN"
        ,[8] = "HUNTER"
        ,[9] = "MAGE"
        ,[10] = "PRIEST"
        ,[11] = "WARLOCK"
		,[12] = "DEMONHUNTER"
		,[13] = "EVOKER"
    }

    function GA:GetCurrentSelectedClassIdentifier()
        return classDropDownIndex2classIdentifierMap[DugisGuideViewer.Modules.GearAdvisor.selectedClassIndex]
    end

    --["classId:specIndex"]= "AGI,INT..."
    --Please add tothis list all other stats that wou want to display only when "Display All Stats" is checked.
    local advancedList = {
		["DEATHKNIGHT:1"] = "AGI,INT,SPELL_DMG,XP_BONUS,AP",
		["DEATHKNIGHT:2"] = "AGI,AVOIDANCE_RATING,INT,LEECH_RATING,SPELL_DMG,XP_BONUS,AP,DPS",
		["DEATHKNIGHT:3"] = "AGI,AVOIDANCE_RATING,INT,LEECH_RATING,SPELL_DMG,XP_BONUS,AP",
		["MONK:1"] = "INT,LEECH_RATING,SPELL_DMG,STR,XP_BONUS,AP",
		["MONK:2"] = "AGI,AP,AVOIDANCE_RATING,LEECH_RATING,STR,XP_BONUS,SPELL_DMG",
		["MONK:3"] = "AVOIDANCE_RATING,INT,LEECH_RATING,SPELL_DMG,STR,XP_BONUS,AP,DPS",
		["WARRIOR:1"] = "AGI,AVOIDANCE_RATING,INT,LEECH_RATING,SPELL_DMG,XP_BONUS,AP",
		["WARRIOR:2"] = "AGI,AVOIDANCE_RATING,INT,LEECH_RATING,SPELL_DMG,XP_BONUS,AP,DPS",
		["WARRIOR:3"] = "AGI,INT,SPELL_DMG,XP_BONUS,AP",
		["PALADIN:1"] = "AGI,AP,AVOIDANCE_RATING,LEECH_RATING,STR,XP_BONUS,SPELL_DMG",
		["PALADIN:2"] = "AGI,INT,SPELL_DMG,XP_BONUS,AP",
		["PALADIN:3"] = "AGI,AVOIDANCE_RATING,INT,LEECH_RATING,SPELL_DMG,XP_BONUS,AP",
		["DRUID:1"] = "AGI,AP,AVOIDANCE_RATING,LEECH_RATING,STR,XP_BONUS,SPELL_DMG",
		["DRUID:2"] = "AVOIDANCE_RATING,INT,LEECH_RATING,SPELL_DMG,STR,XP_BONUS,AP",
		["DRUID:3"] = "INT,SPELL_DMG,STR,XP_BONUS,AP",
		["DRUID:4"] = "AGI,AP,AVOIDANCE_RATING,LEECH_RATING,STR,XP_BONUS,SPELL_DMG",
		["ROGUE:1"] = "AVOIDANCE_RATING,INT,LEECH_RATING,SPELL_DMG,STR,XP_BONUS,AP,DPS",
		["ROGUE:2"] = "AVOIDANCE_RATING,INT,LEECH_RATING,SPELL_DMG,STR,XP_BONUS,AP,DPS",
		["ROGUE:3"] = "AVOIDANCE_RATING,INT,LEECH_RATING,SPELL_DMG,STR,XP_BONUS,AP,DPS",
		["SHAMAN:1"] = "AGI,AP,AVOIDANCE_RATING,LEECH_RATING,STR,XP_BONUS,SPELL_DMG",
		["SHAMAN:2"] = "AVOIDANCE_RATING,INT,LEECH_RATING,SPELL_DMG,STR,XP_BONUS,AP",
		["SHAMAN:3"] = "AGI,AP,AVOIDANCE_RATING,STR,XP_BONUS,SPELL_DMG",
		["HUNTER:1"] = "AVOIDANCE_RATING,INT,LEECH_RATING,SPELL_DMG,STR,XP_BONUS,AP",
		["HUNTER:2"] = "AVOIDANCE_RATING,INT,LEECH_RATING,SPELL_DMG,STR,XP_BONUS,AP",
		["HUNTER:3"] = "AVOIDANCE_RATING,INT,LEECH_RATING,SPELL_DMG,STR,XP_BONUS,AP",
		["MAGE:1"] = "AGI,AP,AVOIDANCE_RATING,LEECH_RATING,STR,XP_BONUS,SPELL_DMG",
		["MAGE:2"] = "AGI,AP,AVOIDANCE_RATING,LEECH_RATING,STR,XP_BONUS,SPELL_DMG",
		["MAGE:3"] = "AGI,AP,AVOIDANCE_RATING,LEECH_RATING,STR,XP_BONUS,SPELL_DMG",
		["PRIEST:1"] = "AGI,AP,AVOIDANCE_RATING,LEECH_RATING,STR,XP_BONUS,SPELL_DMG",
		["PRIEST:2"] = "AGI,AP,AVOIDANCE_RATING,LEECH_RATING,STR,XP_BONUS,SPELL_DMG",
		["PRIEST:3"] = "AGI,AP,AVOIDANCE_RATING,LEECH_RATING,STR,XP_BONUS,SPELL_DMG",
		["WARLOCK:1"] = "AGI,AP,AVOIDANCE_RATING,LEECH_RATING,STR,XP_BONUS,SPELL_DMG",
		["WARLOCK:2"] = "AGI,AP,AVOIDANCE_RATING,LEECH_RATING,STR,XP_BONUS,SPELL_DMG",
		["WARLOCK:3"] = "AGI,AP,AVOIDANCE_RATING,LEECH_RATING,STR,XP_BONUS,SPELL_DMG",
		["DEMONHUNTER:1"] = "AVOIDANCE_RATING,INT,LEECH_RATING,SPELL_DMG,STR,XP_BONUS,AP,DPS",
		["DEMONHUNTER:2"] = "AVOIDANCE_RATING,INT,LEECH_RATING,SPELL_DMG,STR,XP_BONUS,AP,DPS",   
		["EVOKER:1"] = "AVOIDANCE_RATING,STR,AGI,LEECH_RATING,SPELL_DMG,STR,XP_BONUS,AP,DPS",
		["EVOKER:2"] = "AVOIDANCE_RATING,STR,AGI,LEECH_RATING,SPELL_DMG,STR,XP_BONUS,AP,DPS",   
		["EVOKER:3"] = "AVOIDANCE_RATING,STR,AGI,LEECH_RATING,SPELL_DMG,STR,XP_BONUS,AP,DPS",   
    }

    -- test:   /script DugisGuideViewer.Modules.GearAdvisor:UpdateWeightsTextboxes("WARLOCK", 1)
    function GA:UpdateWeightsTextboxesForClassAndSpec(classId, specIndex)
        if DugisGuideUser.userCustomWeights_v4 == nil then
            return
        end

		GA.duringSettingWeights = true

        specIndex = tonumber(specIndex)
        local possibleWeightIdentifiers = GetAllPossibleWeightIdentifiers(classId, specIndex)

        LuaUtils:loop(28, function(item)
            _G["GA_TextWeight"..item]:Hide()
            _G["GA_EditBoxWeight"..item]:Hide()
        end)

        local weightsTop = 10

        for index, weightIdentifier in pairs(possibleWeightIdentifiers) do
             local weightValue = DugisGuideUser.userCustomWeights_v4[classId][specIndex][weightIdentifier]

             local shouldDisplayWeigt = true
             local advancedListWeights = advancedList["" .. classId .. ":" .. specIndex]

             if advancedListWeights ~= nil then
                local advancedWeights = {strsplit(",", advancedListWeights)}

                if LuaUtils:isInTable(weightIdentifier, advancedWeights) then
                    if not DugisGuideViewer:UserSetting(DGV_DISPLAYALLSTATS) then
                        shouldDisplayWeigt = false
                    end
                end
             end

             if shouldDisplayWeigt then
                _G["GA_EditBoxWeight"..index]:SetText(weightValue)
                _G["GA_TextWeight"..index]:SetText(weightIdentifier2weightLabelMap[weightIdentifier] or weightIdentifier)
                _G["GA_EditBoxWeight"..index]['weightIdentifier'] = weightIdentifier

                _G["GA_EditBoxWeight"..index]:Show()
                _G["GA_TextWeight"..index]:Show()

                _G["GA_TextWeight"..index]:ClearAllPoints( )
                _G["GA_TextWeight"..index]:SetPoint("TOPLEFT", DugisGuideViewer.Modules.GearAdvisor.scrollFrame.frame.content, "TOPLEFT", 6, weightsTop)
                weightsTop = weightsTop - 22
            end
        end

        DugisGuideViewer.Modules.GearAdvisor.scrollFrame.scrollBar:GetParent():SetVerticalScroll(0)
        DugisGuideViewer.Modules.GearAdvisor.scrollFrame.scrollBar:SetValue(0)
        local newScrollHeight = -weightsTop - 200
        if newScrollHeight < 0 then
            newScrollHeight = 0
        end
	
		DugisGuideViewer.Modules.GearAdvisor.scrollFrame.scrollBar:SetMinMaxValues(0, newScrollHeight)
		GA.duringSettingWeights = false
	end

    function GA:ResetWeights()
        DugisGuideUser.userCustomWeights_v4 = {}
        InitializeUserCustomWeights()
        GA:UpdateWeightsTextboxes()
    end

    function GA:ApplyWeights(saveWeightsOnly)
        local specIndex = tonumber(DugisGuideViewer.Modules.GearAdvisor.selectedSpecIndex)
        local classId = GA:GetCurrentSelectedClassIdentifier()
        local possibleWeightIdentifiers = GetAllPossibleWeightIdentifiers(classId, specIndex)

        LuaUtils:loop(28, function(index)
             if _G["GA_EditBoxWeight"..index]:IsShown() then
                 local weightIdentifier = _G["GA_EditBoxWeight"..index]['weightIdentifier']
                 local weightValue = tonumber(_G["GA_EditBoxWeight"..index]:GetText())

                 DugisGuideUser.userCustomWeights_v4[classId][specIndex][weightIdentifier] = weightValue

             end
        end)

        if DugisCharacterCache and DugisCharacterCache.CalculateScore_cache_v12 then
            DugisCharacterCache.CalculateScore_cache_v12 = {}
		end
		
		if saveWeightsOnly then
			return
		end

        GA:ResetCalculateScoreCache()
		DGV:ShowReloadUi()
		DGV.GetCurrentBestInSlot_cache_v2 = {}
    end

    -- test:   /script DugisGuideViewer.Modules.GearAdvisor:UpdateWeightsTextboxes("WARLOCK", 1)
    function GA:UpdateWeightsTextboxes()
        if DugisGuideViewer.Modules.GearAdvisor.selectedClassIndex and DugisGuideViewer.Modules.GearAdvisor.selectedSpecIndex then
           GA:UpdateWeightsTextboxesForClassAndSpec(GA:GetCurrentSelectedClassIdentifier(), DugisGuideViewer.Modules.GearAdvisor.selectedSpecIndex)
        end
    end


	local defaultLevelingSpec = DugisGuideViewer.defaultLevelingSpec

	local SPELL_SCHOOL_HOLY = 2
	local SPELL_SCHOOL_FIRE = 3
	local SPELL_SCHOOL_NATURE = 4
	local SPELL_SCHOOL_FROST = 5
	local SPELL_SCHOOL_SHADOW = 6
	local SPELL_SCHOOL_ARCANE = 7
	local defaultSpellSchool = {
		["MONK:2"] = SPELL_SCHOOL_NATURE,
		["PALADIN:1"] = SPELL_SCHOOL_HOLY,
		["DRUID:1"] = SPELL_SCHOOL_NATURE,
		["DRUID:4"] = SPELL_SCHOOL_NATURE,
		["SHAMAN:1"] = SPELL_SCHOOL_NATURE,
		["SHAMAN:3"] = SPELL_SCHOOL_NATURE,
		["MAGE:1"] = SPELL_SCHOOL_ARCANE,
		["MAGE:2"] = SPELL_SCHOOL_FIRE,
		["MAGE:3"] = SPELL_SCHOOL_FROST,
		["PRIEST:1"] = SPELL_SCHOOL_HOLY,
		["PRIEST:2"] = SPELL_SCHOOL_HOLY,
		["PRIEST:3"] = SPELL_SCHOOL_SHADOW,
		["WARLOCK:1"] = SPELL_SCHOOL_SHADOW,
		["WARLOCK:2"] = SPELL_SCHOOL_SHADOW,
		["WARLOCK:3"] = SPELL_SCHOOL_SHADOW
	}

	local orig_GetSpecialization
	local function GetSpecialization(...)
		if not orig_GetSpecialization then orig_GetSpecialization = _G.GetSpecialization end
		local spec = orig_GetSpecialization(...)
		if (spec == 5 or not spec) and select("#", ...)==0 then return defaultLevelingSpec[select(2,UnitClass("player"))].index end
		return spec
	end

    GetSpecialization_dugis = GetSpecialization

    --/dump DugisGuideViewer.Modules.GearAdvisor:GetGearAdvisorScoringValues("LE_ITEM_CLASS_WEAPON")
	function GA:GetGearAdvisorScoringValues(tagName)
		local spec = GetSpecialization()
        local _, class = UnitClass("player")

        local scoringDefinition = scoring[class..":"..spec]
        local tagvalueslist = LuaUtils:split(scoringDefinition or "", ":")

        local result = {}

        for _, tagvalues in pairs(tagvalueslist) do
            local tag_values = LuaUtils:split(tagvalues, ",")

            local tag = tag_values[1]

            if tagName == tag then
                for index, value in pairs(tag_values) do
                    if index > 1 then
                        if LuaUtils:matchString(value, "|") ~= "" then
                            local value_level = LuaUtils:split(value, "|")
                            value = value_level[1]
                            local level = value_level[2]
                            local playerLevel = UnitLevel("player")

                            if tonumber(level) > playerLevel then
                                value = nil
                            end
                        end

                        if value ~= nil then
                            result[#result + 1] = value
                        end
                    end
                end
            end
        end

        return  result
	end


	-- local function VisitCSV(func, itemLink, spec, level, slot, itemSums, uncapped, ...)
		-- local returns = GetCreateTable()
		-- for i=1,select("#", ...) do
			-- returns:InsertList(func(itemLink, spec, level, slot, itemSums, uncapped, strsplit(",", (select(i,...)))))
			-- local last = returns[returns.n]
			-- if type(last)=="number" and last<0 then --allow short circuit on negative values
				-- returns:Pool()
				-- return last
			-- end
		-- end
		-- return returns:Pool()
	-- end

	local function GetAllCSV(...)
		local tbl = {}
		for i=1,select("#", ...) do
			tinsert(tbl, {strsplit(",", (select(i,...)))})
		end
		return tbl
	end

	local scoringSpecs = {}
	local function GetScoringInfo(spec, pvp)
		if not spec then
			spec = GetSpecialization()
		end
		local key = pvp and spec.."PVP" or spec
		if not scoringSpecs[key]  then
			local _, class = UnitClass("player")
			if pvp then
                local scor = scoring[PackStrings(class, spec, "PVP")]
				scoringSpecs[spec.."PVP"] = GetAllCSV(strsplit(":", scor))
			else
                local scor = scoring[PackStrings(class, spec)] or ""
				scoringSpecs[spec] = GetAllCSV(strsplit(":", scor))
                for _, row in pairs (scoringSpecs[spec]) do
                    local weightName = row[1]
                    if DugisGuideUser.userCustomWeights_v4[class] ~= nil
                    and DugisGuideUser.userCustomWeights_v4[class][spec] ~= nil
                    and DugisGuideUser.userCustomWeights_v4[class][spec][weightName] ~= nil then
                       local weightValue = DugisGuideUser.userCustomWeights_v4[class][spec][weightName]
                       row[2] = weightValue
                    end
                end
			end
		end
		return unpack(scoringSpecs[key])
	end
	GA.scoringSpecs = scoringSpecs

	local function GetTieBreakerScoringInfo()

		return unpack(scoring.tiebreaker)
	end

	local function PlayerCanTitansGrip(weaponSubClass, spec, level)
		local hasTitansGrip = select(2,UnitClass("player"))=="WARRIOR" and spec==2 and level>=10
		if not hasTitansGrip then return end
		return weaponSubClass==LE_ITEM_WEAPON_AXE2H or weaponSubClass==LE_ITEM_WEAPON_MACE2H or weaponSubClass==LE_ITEM_WEAPON_SWORD2H or weaponSubClass==LE_ITEM_WEAPON_POLEARM
	end

	local function PlayerCanDualWield(spec, level)
		local class = select(2,UnitClass("player"))
		if class=="DEATHKNIGHT" or class=="ROGUE" or class=="DEMONHUNTER" then
			return true
		elseif class=="SHAMAN" and level>=10 and spec==2 then
			return true
		elseif class=="MONK" and level>=10 and (spec==1 or spec==3) then
			return true
		elseif class=="WARRIOR" and level>=10 and spec==2 then
			return true
		end
	end


	local function PlayerShouldDualWieldWithMainHand(spec, level, equipSlot, itemSubclass)
		return
			PlayerCanDualWield(spec, level) and
			(equipSlot=="INVTYPE_WEAPON" or equipSlot=="INVTYPE_WEAPONMAINHAND" or PlayerCanTitansGrip(itemSubclass, spec, level))
	end

	local function GetValidKeyTransform(key, itemLink, spec, level, uniqueInventorySlot, itemClass, itemSubclass, equipSlot)



		if uniqueInventorySlot==INVSLOT_OFFHAND then
			if key=="DPS" or key=="DPS|MAIN" then return end
			if key=="DPS|OFF" and itemClass==LE_ITEM_CLASS_WEAPON then
				if not PlayerCanDualWield(spec, level) then return end
				if
					equipSlot=="INVTYPE_SHIELD" or equipSlot=="INVTYPE_WEAPONOFFHAND" or equipSlot=="INVTYPE_HOLDABLE" or equipSlot=="INVTYPE_WEAPON" or
					PlayerCanTitansGrip(itemSubclass, spec, level)
				then
					return "DPS"
				end
			end
		elseif uniqueInventorySlot==INVSLOT_MAINHAND then
			if key=="DPS|OFF" then return end
			if (key=="DPS" or key=="DPS|MAIN") and PlayerShouldDualWieldWithMainHand(spec, level, equipSlot, itemSubclass) then
				return key=="DPS|MAIN" and "DPS" or nil
			end
		end
		return key
	end

	local function nextValidSlot(includeEmpty, slot)
		if not slot then slot = INVSLOT_FIRST_EQUIPPED
		else slot = slot+1 end

		for i=slot,INVSLOT_LAST_EQUIPPED do
			local itemLink = GetInventoryItemLink("player", i)
			if includeEmpty or itemLink then return i,itemLink end
		end
		return
	end

	local function NextUniqueInventorySlot(invariant, control)
		if not control then return "INVTYPE_HEAD", INVSLOT_HEAD
		elseif control=="INVTYPE_HEAD" then return "INVTYPE_NECK", INVSLOT_NECK
		elseif control=="INVTYPE_NECK" then return "INVTYPE_SHOULDER", INVSLOT_SHOULDER
		elseif control=="INVTYPE_SHOULDER" then return "INVTYPE_CHEST", INVSLOT_CHEST
		elseif control=="INVTYPE_CHEST" then return "INVTYPE_WAIST", INVSLOT_WAIST
		elseif control=="INVTYPE_WAIST" then return "INVTYPE_LEGS", INVSLOT_LEGS
		elseif control=="INVTYPE_LEGS" then return "INVTYPE_FEET", INVSLOT_FEET
		elseif control=="INVTYPE_FEET" then return "INVTYPE_WRIST", INVSLOT_WRIST
		elseif control=="INVTYPE_WRIST" then return "INVTYPE_HAND", INVSLOT_HAND
		elseif control=="INVTYPE_HAND" then return "INVTYPE_FINGER", INVSLOT_FINGER1, INVSLOT_FINGER2
		elseif control=="INVTYPE_FINGER" and DGV:UserSetting(DGV_SUGGESTTRINKET) then
			return "INVTYPE_TRINKET", INVSLOT_TRINKET1, INVSLOT_TRINKET2 --ignore trinkets
		elseif control=="INVTYPE_FINGER" and not DGV:UserSetting(DGV_SUGGESTTRINKET) then
			return "INVTYPE_CLOAK", INVSLOT_BACK --ignore trinkets
		elseif control=="INVTYPE_TRINKET" and DGV:UserSetting(DGV_SUGGESTTRINKET) then
			return "INVTYPE_CLOAK", INVSLOT_BACK --ignore trinkets
		--elseif control=="INVTYPE_FINGER" then return "INVTYPE_CLOAK", INVSLOT_BACK
		elseif control=="INVTYPE_CLOAK" then return INVSLOT_MAINHAND, INVSLOT_MAINHAND, INVSLOT_OFFHAND
		end
	end

	local function PoolItemSums(reaction)
		reaction:UnpackCache():Pool()
	end

	local function GetItemSums(itemLink, threading)
		-- local cacheKey = "GetItemSums"..itemLink
		-- local cacheReaction = TryGetCacheReaction(cacheKey)
		-- if cacheReaction then
			-- return cacheReaction:UnpackCache()
		-- end

		local itemSums = GetCreateTable()
		StatLogic:GetSum(itemLink, itemSums, threading)
		DGV.InitTable(itemSums)
		--DGV:DebugFormat("GetItemSums", "itemLink", itemLink, "itemSums", itemSums)
		-- return RegisterReaction("BAG_UPDATE"):SetCache(cacheKey, itemSums):WithAction(PoolItemSums):UnpackCache()
		return itemSums
	end

	--Armor Specialization Stats
	--STA, AGI, INT, STR
	local function GetItemStatSum(key, itemLink, invSlot, itemSums, threading)
		local poolItemSums
		if not itemSums then
			poolItemSums = true
			itemSums = GetItemSums(itemLink, threading)
		end
		local itemSum
			itemSum = itemSums[key] or 0
		if poolItemSums then itemSums:Pool() end
        
        if not StatLogic:CanUseGear(itemLink, threading) then
            return 0
        end

		return itemSum--, bonus, ratingPerBonus, ratingId
	end

	local function UniqueInventoryToInvSlot(uniqueInventorySlot)
		if type(uniqueInventorySlot)=="number" then
			return uniqueInventorySlot
		elseif uniqueInventorySlot=="INVTYPE_FINGER" then
			return INVSLOT_FINGER1, INVSLOT_FINGER2
		elseif uniqueInventorySlot=="INVTYPE_TRINKET" and DGV:UserSetting(DGV_SUGGESTTRINKET) then
			return INVSLOT_TRINKET1, INVSLOT_TRINKET2
		elseif uniqueInventorySlot=="INVTYPE_CLOAK" then
			return INVSLOT_BACK
		else
			if uniqueInventorySlot ~= nil then
				return _G["INVSLOT"..strsub(uniqueInventorySlot, 8)]
			else
				return nil
			end
		end
	end

	local function UnbindSpecDataTable(dataTable)
		--DGV:DebugFormat("UnbindSpecDataTable")
		if dataTable.cacheInValid then
			--DGV:DebugFormat("UnbindSpecDataTable", "dataTable", tostring(dataTable))
			dataTable:Pool()
			return
		end
	end

	local function GetBestBaselineRating(key, spec, pvp, level, uniqueInventorySlot, itemLink, threading)
		--DGV:DebugFormat("GetBestBaselineRating", "key", key, "spec", spec, "level", level, "uniqueInventorySlot", uniqueInventorySlot)	
		local dataTable = GA:GetSpecDataTable(spec, pvp, nil, nil, threading):BindToAutoroutineLifetime(UnbindSpecDataTable)
		--if true then return 0 end
		local invSlot
		local inv1, inv2 = UniqueInventoryToInvSlot(uniqueInventorySlot)
		
		if not inv1 then
			return 0
		end
		
		local current1, current2 = GetInventoryItemLink("player", inv1), GetInventoryItemLink("player", inv2)
		local bestEquippedSlot =
			(dataTable[inv1] == current1 and inv1) or
			(dataTable[inv1] == current2 and inv1) or
			(inv2 and dataTable[inv2] == current1 and inv2) or
			(inv2 and dataTable[inv2] == current2 and inv2)
		--if true then return 0 end
		if not inv2 then
			invSlot = inv1
		elseif dataTable[inv1]==itemLink then
			invSlot = inv1
		elseif dataTable[inv2]==itemLink then
			invSlot = inv2
		elseif bestEquippedSlot then
			invSlot = bestEquippedSlot
		else
			local score1 = CalculateScore(dataTable[inv1], spec, pvp, level, uniqueInventorySlot, nil, nil, true)
			local score2 = CalculateScore(dataTable[inv2], spec, pvp, level, uniqueInventorySlot, nil, nil, true)
			-- local score1 = DGV:CalculateScore(nil, current1, spec, level, uniqueInventorySlot, nil, nil, nil, true)
			-- local score2 = DGV:CalculateScore(nil, current2, spec, level, uniqueInventorySlot, nil, nil, nil, true)
			invSlot = score1>=score2 and inv2 or inv1
		end
		--if true then return 0 end
		local ratingCount = 0
		for unique,inv1,inv2 in NextUniqueInventorySlot do
			if invSlot~=inv1 then
				ratingCount = ratingCount + GetItemStatSum(key, dataTable[inv1], inv1, nil, threading)
			end
			if invSlot~=inv2 and inv2 and dataTable[inv2] then
				ratingCount = ratingCount + GetItemStatSum(key, dataTable[inv2], inv2, nil, threading)
			end
		end
		return ratingCount
	end

	local function GetCurrentRating(key)
		local cacheKey = "GetCurrentRating"..key
		local cachedValue = DGV.GetCurrentRating_cache[cacheKey]
		if cachedValue then
			return cachedValue
		end
		local ratingCount = 0
		for unique,inv1,inv2 in NextUniqueInventorySlot do
			local inv1Item = GetInventoryItemLink("player", inv1)
			if inv1Item then
				ratingCount = ratingCount + GetItemStatSum(key, inv1Item, inv1)
			end
			local inv2Item = GetInventoryItemLink("player", inv2)
			if inv2 and inv2Item then
				ratingCount = ratingCount + GetItemStatSum(key, inv2Item, inv2)
			end
		end
        
        DGV.GetCurrentRating_cache[cacheKey] = ratingCount
		return ratingCount
	end

	local function GetSpecializationSpellSchool(spec)
		if not spec then
			spec = GetSpecialization()
		end
		local _, class = UnitClass("player")
		return defaultSpellSchool[PackStrings(class, spec)]
	end

	--EXPERTISE_RATING
	--MELEE_HIT_RATING
	--SPELL_HIT_RATING
	--RANGED_HIT_RATING
	--MELEE_HASTE_RATING
	--RANGED_HASTE_RATING
	--SPELL_HASTE_RATING
	--MELEE_CRIT_RATING
	--SPELL_CRIT_RATING
	--RANGED_CRIT_RATING
	local function GetCurrentRatingBonus(key, spec)
		local _, class = UnitClass("player")
		-- if key=="EXPERTISE_RATING" then
			-- local expertise, offhandExpertise, rangedExpertise = GetExpertise()
			-- return class=="HUNTER" and rangedExpertise or expertise
		-- elseif key=="MELEE_HIT_RATING" then
			-- return GetCombatRatingBonus(CR_HIT_MELEE) + GetHitModifier()
		-- elseif key=="SPELL_HIT_RATING" then
			-- return GetCombatRatingBonus(CR_HIT_SPELL) + GetSpellHitModifier()
		-- elseif key=="RANGED_HIT_RATING" then
			-- return GetCombatRatingBonus(CR_HIT_RANGED) - GetHitModifier()
		-- else
		if key=="MELEE_HASTE_RATING" then
			return GetMeleeHaste()
		elseif key=="RANGED_HASTE_RATING" then
			return GetRangedHaste()
		elseif key=="SPELL_HASTE_RATING" then
			return UnitSpellHaste("player")
		elseif key=="MELEE_CRIT_RATING" then
			return GetCritChance()
		elseif key=="SPELL_CRIT_RATING" then
			return GetSpellCritChance(GetSpecializationSpellSchool(spec))
		elseif key=="RANGED_CRIT_RATING" then
			return GetRangedCritChance()
		end
	end

	local function GetBestBaselineBonus(key, spec, pvp, level, uniqueInventorySlot, itemLink, ratingPerBonus, threading)
		-- local cacheKey = strformat("%s%s%d%d%s", "GetBestBaselineBonus", key, spec, level, tostring(uniqueInventorySlot))
		-- local cacheReaction = TryGetCacheReaction(cacheKey)
		-- if cacheReaction then
			-- return cacheReaction:UnpackCache()
		-- end
		local currentBonus = GetCurrentRatingBonus(key, spec)
		if not currentBonus then return end
		local baseLineRating = GetBestBaselineRating(key, spec, pvp, level, uniqueInventorySlot, itemLink, threading)
		local currentRating = GetCurrentRating(key)
		local ratingDifference = baseLineRating - currentRating
		local baselineBonus = currentBonus + ratingDifference/ratingPerBonus
		--DGV:DebugFormat("GetBestBaselineBonus", "key", key, "baselineBonus", baselineBonus, "baseLineRating", baseLineRating, "currentRating", currentRating, "ratingDifference", ratingDifference, "currentBonus", currentBonus, "ratingDifference/ratingPerBonus", ratingDifference/ratingPerBonus)
		-- RegisterStopwatchReaction(0):SetCache(cacheKey, baselineBonus)
		return baselineBonus
	end

	local function GetCapValue(key, cap)
		local capNumber = tonumber(cap)
		if capNumber then return capNumber*100 end
		local levelDifference = DGV:UserSetting(DGV_GASTATCAPLEVELDIFFERENCE)
		if cap=="NO_MISS" then
			-- if key=="MELEE_HIT_RATING" or key=="RANGED_HIT_RATING" then
				-- capNumber = BASE_MISS_CHANCE_PHYSICAL[levelDifference]
			-- elseif key=="SPELL_HIT_RATING" then
				-- capNumber = BASE_MISS_CHANCE_SPELL[levelDifference]
			-- end
		elseif key=="EXPERTISE_RATING" then
			-- if cap=="NO_DODGE" then
				-- capNumber = BASE_ENEMY_DODGE_CHANCE[levelDifference]
			-- elseif cap=="NO_PARRY" then
				-- capNumber = BASE_ENEMY_DODGE_CHANCE[levelDifference]+BASE_ENEMY_PARRY_CHANCE[levelDifference]
			-- end
		end
		--DGV:DebugFormat("GetCapValue", "key", key, "capNumber", capNumber)
		return capNumber
	end

	local function CalculateRatingScore(key, baseLineBonus, rating, bonus, ratingPerBonus, weight, cap, ...)
		--DGV:DebugFormat("CalculateRatingScore", "baseLineBonus", baseLineBonus, "bonus", bonus, "rating", rating, "weight", weight, "cap", cap)
		weight = tonumber(weight)
		cap = GetCapValue(key, cap)
		local scoreBelowCap, scoreAboveCap = 0,0
		--DGV:DebugFormat("CalculateRatingScore 1", "baseLineBonus", baseLineBonus, "rating", rating, "bonus", bonus)
		if not cap or (baseLineBonus+bonus)<cap then
			scoreBelowCap = rating*weight
		elseif baseLineBonus<cap then
			local bonusRatio = (cap-baseLineBonus)/bonus
			scoreBelowCap = (rating*bonusRatio)*weight
			bonus = (1-bonusRatio)*bonus
			rating = (1-bonusRatio)*rating
		end
		if cap and baseLineBonus+bonus>cap then
			scoreAboveCap = CalculateRatingScore(key, baseLineBonus, rating, bonus, ratingPerBonus, ...)
		end

		--DGV:DebugFormat("CalculateRatingScore 2", "key", key, "baseLineBonus", baseLineBonus, "rating", rating, "bonus", bonus, "weight", weight, "cap", cap, "scoreBelowCap", scoreBelowCap, "scoreAboveCap", scoreAboveCap)
		return scoreBelowCap+scoreAboveCap
	end

	local function CheckSubclass(level, itemRarity, itemSubclass, ...)
		for i=1,select("#", ...) do
			local arg = select(i, ...)
			local number, requiredLevel = strsplit("|", arg)
			number = tonumber(number)
			requiredLevel = tonumber(requiredLevel) or 0
			local selectedArg = (requiredLevel<=level or itemRarity==7) and number or -1 --itemRarity==7 excuses heirlooms, so long as character can eventually use the armor type
			if selectedArg==itemSubclass then return true end
		end
	end


	local function IsArmorSpecSlot(slot)
		return
			slot==INVSLOT_CHEST or
			slot==INVSLOT_FEET or
			slot==INVSLOT_HAND or
			slot==INVSLOT_HEAD or
			slot==INVSLOT_LEGS or
			slot==INVSLOT_SHOULDER or
			slot==INVSLOT_WAIST or
			slot==INVSLOT_WRIST
	end

    
    local function AreItemsTheSame(linkA, linkB, ignoreSpecialization)
        if not linkA or not linkB or type(linkA) == "number" or type(linkB) == "number" then
            return linkA == linkB
        end        
    
        local _, itemId, enchantId, _, _, _, _, _, _, _, specializationID,_,_,_,bonusa1,bonusa2 = strsplit(":", linkA)
        local _, itemIdB, enchantIdB, _, _, _, _, _, _, _, specializationIDB,_,_,_,bonusb1,bonusb2 = strsplit(":", linkB)
		
		return itemId == itemIdB and enchantId == enchantIdB and (ignoreSpecialization or specializationID == specializationIDB) and bonusa1 == bonusb1 and bonusa2 == bonusb2
    end
    
	local function ItemIsEquipped(itemLink, itemIteratorTable)
		if itemIteratorTable then
			return itemIteratorTable.player and not itemIteratorTable.bags
		else
			for slot,link in nextValidSlot do
                if AreItemsTheSame(link, itemLink, true) then return true end
			end
		end
	end
    
    local function IsLegendaryItemEquipped()
		if DGV.IsLegendaryItemEquipped_cached ~= nil then
            return DGV.IsLegendaryItemEquipped_cached
        end
        
        for control, iteratedItemLink in GA.ItemIterator, ITEM_ITERATOR_EQUIPED_ONLY do
            if ItemIsEquipped(iteratedItemLink) then
                local _, _, itemRarity = GetItemInfo(iteratedItemLink) 
                if itemRarity == 5 then
                    DGV.IsLegendaryItemEquipped_cached = true
					control:Pool()
                    return true
                end
            end
			YieldAutoroutine()
        end
        
        DGV.IsLegendaryItemEquipped_cached = false
    end    

	local function ItemIsBanned(itemLink, itemIteratorTable, threading)
        local equipped = ItemIsEquipped(itemLink, itemIteratorTable)
        local _, _, quality = LuaUtils.GetItemInfo_dugi(itemLink, threading)
        
        if IsLegendaryItemEquipped() and quality == 5 and not equipped then
            return true
        end
    
		return DGV.chardb.GA_Blacklist and DGV.chardb.GA_Blacklist[DGV:GetItemIdFromLink(itemLink)] and not equipped
	end

	local function ItemIsBannedWeapon(itemLink)
		if not itemLink then return end
		return DGV.chardb.GA_Blacklist and DGV.chardb.GA_Blacklist[DGV:GetItemIdFromLink(itemLink)]
	end

	function CalculateScoreForInfo(itemLink, spec, pvp, level, uniqueInventorySlot, itemSums, enforceArmorSpecSubclass, uncapped, forGearFinder, key, value, ...)
		if key == "ARMOR_SPECIALIZATION_STAT" then return 0 end
        
        local _, itemId = nil, 0
        
        if type(itemLink) == "number" then
            itemId = tostring(itemLink)
        else
            local itemString = string.match(itemLink, "item[%-?%d:]+")
            _, itemId = strsplit(":", itemString)
        end

		local itemRarity, equipSlot, itemClass, itemSubclass
        
        if forGearFinder then
            _, _, itemRarity, _, _, _, _, _, equipSlot, _, _, itemClass, itemSubclass = LuaUtils.GetItemInfo_dugi(itemLink, true)
        else
            _, _, itemRarity, _, _, _, _, _, equipSlot, _, _, itemClass, itemSubclass = GetItemInfo(itemLink)
        end
    
		if itemClass~=LE_ITEM_CLASS_WEAPON and itemClass~=LE_ITEM_CLASS_ARMOR then return -2 end

		if DGV:UserSetting(DGV_FISHINGPOLE) and ItemIsEquipped(itemLink) then
			if itemClass==LE_ITEM_CLASS_WEAPON and itemSubclass==LE_ITEM_WEAPON_FISHINGPOLE then
				return 2000000
			elseif tonumber(itemId) == 6256 or
				tonumber(itemId) == 19969 or
				tonumber(itemId) == 50287 or
				tonumber(itemId) == 93732 or
				tonumber(itemId) == 118380 or
				tonumber(itemId) == 117405 or
				tonumber(itemId) == 88710 or
				tonumber(itemId) == 33820 or
				tonumber(itemId) == 19972 or
				tonumber(itemId) == 33820 or
				tonumber(itemId) == 118393
				then
				return 2000000
			end
		end

		if DGV:UserSetting(DGV_COOKINGITEM) and ItemIsEquipped(itemLink) then
			if tonumber(itemId) == 46349 or
				tonumber(itemId) == 86468 or
				tonumber(itemId) == 86559 or
				tonumber(itemId) == 86558
				then
				return 2000000
			end
		end

		if DGV:UserSetting(DGV_DAILYITEM) and ItemIsEquipped(itemLink) then
			if tonumber(itemId) == 46106 or --argent lance
				tonumber(itemId) == 46069 or --alliance lance
				tonumber(itemId) == 46070 --horde lance
				then
				return 2000000
			end
		end

		if itemClass==LE_ITEM_CLASS_ARMOR and itemSubclass==LE_ITEM_ARMOR_COSMETIC then return -2 end
		if (key == "LE_ITEM_CLASS_WEAPON" and itemClass==LE_ITEM_CLASS_WEAPON) or (key == "LE_ITEM_CLASS_ARMOR" and itemClass==LE_ITEM_CLASS_ARMOR) then
			if
				enforceArmorSpecSubclass and
				itemClass==LE_ITEM_CLASS_ARMOR and
				itemSubclass~=LE_ITEM_ARMOR_SHIELD and
				itemSubclass~=enforceArmorSpecSubclass and
				IsArmorSpecSlot(UniqueInventoryToInvSlot(uniqueInventorySlot))
			then
				return -4
			end
			return CheckSubclass(level, itemRarity, itemSubclass, value, ...) and 0 or -3
		elseif key ~= "LE_ITEM_CLASS_WEAPON" and key ~= "LE_ITEM_CLASS_ARMOR" then
			local keyTransform = GetValidKeyTransform(key, itemLink, spec, level, uniqueInventorySlot, itemClass, itemSubclass, equipSlot)
			if not keyTransform then return 0 end
			local itemSum = GetItemStatSum(keyTransform, itemLink, uniqueInventorySlot, itemSums, forGearFinder)
			--if true then return itemSum*value end
			if uncapped or itemSum==0 or not select(1, ...) or not GetCurrentRatingBonus(keyTransform, spec) then
				return itemSum*(value or 0), itemSum
			end
			local bonus, ratingPerBonus = GetEffectFromRating(itemSum, keyTransform)
			local baseLineBonus = GetBestBaselineBonus(keyTransform, spec, pvp, level, uniqueInventorySlot, itemLink, ratingPerBonus, forGearFinder)
			--if true then return itemSum*value end

			local score = CalculateRatingScore(keyTransform, baseLineBonus, itemSum, bonus, ratingPerBonus, value, ...)
			return score, itemSum
		end
	end


	local function GetUniqueID(control)
		return string.format("%s|%s|%d", tostring(control.func), tostring(control.bag), control.slot)
	end

	local function ItemWasFirst(control, link)
		if control.first==link then
			control.first = nil
			return true
		end
	end
    
    GA.playerClass = select(2,UnitClass("player"))
    GA.playerSpec = GetSpecialization_dugis()

	--GetContainerItemLink(container, slot)
	--BACKPACK_CONTAINER: Backpack (0)
	--1 through GetNumBankSlots(): Bag slots (as presented in the default UI, numbered right to left)
	--GetInventoryItemLink("unit", slot) INVSLOT_FIRST_EQUIPPED, INVSLOT_LAST_EQUIPPED
	--GetNumQuestRewards
	--GetNumQuestChoices
	--GetQuestLogItemLink("itemType", index)
	--GetQuestItemLink("itemType", index)
	--name, link, quality, iLevel, reqLevel, class, subclass, maxStack, equipSlot, texture, vendorPrice = GetItemInfo(itemID) or GetItemInfo("itemName") or GetItemInfo("itemLink")
	local ITEM_ITERATOR_SKIP_EQUIPPED = 1
	local ITEM_ITERATOR_SKIP_REWARDS = 2
	local ITEM_ITERATOR_SKIP_LOOT_ROLL = 4
	local ITEM_ITERATOR_SKIP_ENCOUNTER_JOURNAL = 8
	local ITEM_ITERATOR_SKIP_VENDOR = 16
	local ITEM_ITERATOR_SKIP_LOOT = 32
	local ITEM_ITERATOR_SKIP_CONTAINERS = 64
	local ITEM_ITERATOR_SKIP_ALL_EXTERNAL = bit.bor(ITEM_ITERATOR_SKIP_REWARDS, ITEM_ITERATOR_SKIP_LOOT_ROLL, ITEM_ITERATOR_SKIP_ENCOUNTER_JOURNAL, ITEM_ITERATOR_SKIP_VENDOR, ITEM_ITERATOR_SKIP_LOOT)
	local ITEM_ITERATOR_EQUIPED_ONLY = bit.bor(ITEM_ITERATOR_SKIP_ALL_EXTERNAL, ITEM_ITERATOR_SKIP_CONTAINERS)
	local function SkipRequested(control, predicate)
		return bit.band(control.skip or 0, predicate)==predicate
	end
	local function SkipEquipped(control)
		return SkipRequested(control, ITEM_ITERATOR_SKIP_EQUIPPED)
	end
	local function SkipRewards(control)
		return SkipRequested(control, ITEM_ITERATOR_SKIP_REWARDS)
	end
	local function SkipLootRoll(control)
		return SkipRequested(control, ITEM_ITERATOR_SKIP_LOOT_ROLL)
	end
	local function SkipEncounterJournal(control)
		return SkipRequested(control, ITEM_ITERATOR_SKIP_ENCOUNTER_JOURNAL)
	end
	local function SkipVendor(control)
		return SkipRequested(control, ITEM_ITERATOR_SKIP_VENDOR)
	end
	local function SkipLoot(control)
		return SkipRequested(control, ITEM_ITERATOR_SKIP_LOOT)
	end
	local function SkipContainers(control)
		return SkipRequested(control, ITEM_ITERATOR_SKIP_CONTAINERS)
	end
	local function ItemIterator(invariant, control)
		if not control then
			if type(invariant)=="table" then
				control = invariant
				control:BindToAutoroutineLifetime(tPool)
				if control.first then
					return control, control.first
				end
			else
				control = GetCreateTable():BindToAutoroutineLifetime(tPool)
				control.skip = invariant
			end
		end
		if not control.func then
			control.func = GetInventoryItemLink
		end
		while true do
			local func = control.func
			local slot = control.slot
			if func==GetInventoryItemLink then
				if not slot then control.slot = INVSLOT_FIRST_EQUIPPED
				else control.slot = slot + 1 end
				slot = control.slot
				if slot<=INVSLOT_LAST_EQUIPPED and not SkipEquipped(control) then
					local itemLink = GetInventoryItemLink("player", slot)
					if itemLink and select(9, GetItemInfo(itemLink))~="" and not ItemWasFirst(control, itemLink) then
						control.player = true
						control.bank = false
						control.bags = false
						return control, itemLink
					end
				else
					control.player = nil
					control.bank = nil
					control.bags = nil
					control.func = GetContainerItemLink
					control.slot = nil
				end
			elseif func==GetContainerItemLink then
				if not slot then slot = 0 end
				slot = slot + 1
				control.slot = slot
				if not control.bag then control.bag = BACKPACK_CONTAINER end
				local skipContainers = SkipContainers(control)
				if slot<=C_Container.GetContainerNumSlots(control.bag) and not skipContainers then
					local itemLink = GetContainerItemLink(control.bag, slot)
					control.player = control.bag<=NUM_TOTAL_EQUIPPED_BAG_SLOTS
					control.bank = not control.player
					control.bags = true
					if itemLink then
						local itemName, _, _, _, _, _, _, _, itemEquipSlot = GetItemInfo(itemLink)
						if
							itemName
							and itemEquipSlot
							and itemEquipSlot~=""
							and not ItemWasFirst(control, itemLink)
						then
                            return control, itemLink
						end
					end
				else
					control.bag = control.bag + 1
					if control.bag>NUM_TOTAL_EQUIPPED_BAG_SLOTS+C_Bank.FetchNumPurchasedBankTabs(Enum.BankType.Character) or skipContainers then
						control.bag = nil
						control.player = nil
						control.bank = nil
						control.bags = nil
						control.func = GetNumQuestRewards
						control.itemType = "reward"
					end
					control.slot = nil
				end
			elseif control.itemType then
				if
					not SkipRewards(control) and
					(QuestFrame:IsShown() or
					QuestLogPopupDetailFrame:IsShown() or
					(WorldMapFrame:IsShown() and QuestMapFrame.DetailsFrame:IsShown())) then
					if not slot then slot = 0 end
					slot = slot + 1
					control.slot = slot
					if slot<=func(GetQuestID()) then
						local itemLink, itemName
						if QuestInfoFrame.questLog then
							itemLink = GetQuestLogItemLink(control.itemType, slot)
							if control.itemType=="choice" then
								itemName = GetQuestLogChoiceInfo(slot)
							else
								itemName = GetQuestLogRewardInfo(slot)
							end
						else
							itemLink = GetQuestItemLink(control.itemType, slot)
							itemName = GetQuestItemInfo(control.itemType, slot)
						end
						local itemFrame = _G["QuestInfoItem"..control.slot]
						if
							itemLink
							and itemFrame
							and itemFrame:IsShown()
							and itemFrame.type==control.itemType
							and itemFrame.name:GetText()==itemName
							and not ItemWasFirst(control, itemLink)
						then
                            return control, itemLink
						end
					elseif func == GetNumQuestRewards then
						control.func = GetNumQuestChoices
						control.itemType = "choice"
						control.slot = nil
					elseif func == GetNumQuestChoices then
						control.func = GetNumQuestLogRewards
						control.itemType = "reward"
						control.slot = nil
					elseif func == GetNumQuestLogRewards then
						control.func = GetNumQuestLogChoices
						control.itemType = "choice"
						control.slot = nil
					else
						control.func = GetLootRollItemLink
						control.slot = nil
						control.itemType = nil
					end
				else
					control.func = GetLootRollItemLink
					control.slot = nil
					control.itemType = nil
				end
			elseif func==GetLootRollItemLink then
				if not slot then slot = 0 end
				slot = (not SkipLootRoll(control)) and slot + 1
				control.slot = slot
				 if slot and slot<=NUM_GROUP_LOOT_FRAMES and GroupLootFrame1 and GroupLootFrame1:IsShown() then
					local lootFrame =  _G["GroupLootFrame"..slot]
					if lootFrame and lootFrame:IsShown() then
						local itemLink = func(lootFrame.rollID)
						if itemLink and not ItemWasFirst(control, itemLink) then
                            return control, func(lootFrame.rollID)
						end
					end
				end
				control.func = C_EncounterJournal.GetLootInfoByIndex
				control.slot = nil
			elseif func==C_EncounterJournal.GetLootInfoByIndex then
				if not SkipEncounterJournal(control) and
					EncounterJournal and
					EncounterJournal:IsShown() and
					EncounterJournal.encounter.info.LootContainer:IsShown()
				then
					if not slot then slot = 0 end
					slot = slot + 1
					control.slot = slot
					local numLoot = EJ_GetNumLoot()
					if slot<=numLoot then
						local info = func(slot)
						if link and not ItemWasFirst(control, info.link) then
							return control, info.link
						end
					end
				end
				control.func = GetMerchantItemLink
				control.slot = nil
			elseif func==GetMerchantItemLink then
				if
					not SkipVendor(control) and
					MerchantFrame:IsShown()
				then
					if not slot then slot = 0 end
					slot = slot + 1
					control.slot = slot
					local link = func(slot)
					if link and not ItemWasFirst(control, link) then
						return control, link
					end
				end
				control.func = GetBuybackItemLink
				control.slot = nil
			elseif func==GetBuybackItemLink then
				if
					not SkipVendor(control) and
					MerchantFrame:IsShown()
				then
					if not slot then slot = 0 end
					slot = slot + 1
					control.slot = slot
					local link = func(slot)
					if link and not ItemWasFirst(control, link) then
						return control, link
					end
				end
				control.func = GetLootSlotLink
				control.slot = nil
			elseif func==GetLootSlotLink then
				if not slot then slot = 0 end

				slot = slot + 1
				control.slot = slot
				if
					not SkipLoot(control) and
					LootFrame:IsShown()
				then
					local link = func(slot)
					if link and not ItemWasFirst(control, link) then
						return control, link
					end
				end
				if slot > GetNumLootItems() then
					control:Pool()
					return
				end
			else
				control:Pool()
				return
			end
		end
	end

	GA.ItemIterator = ItemIterator

	function AmountOfShownChoises()
		local result = 0

		if QuestFrame:IsShown() and QuestInfoRewardsFrame:IsShown() then
			local questItemName = "QuestInfoRewardsFrameQuestInfoItem";
			for i=1, 500 do
				local button = _G[questItemName..i];
				if button and button:IsShown() then
					if button.type == "choice" then
						result = result + 1
					end
				end
			end
		end

		if QuestMapFrame:IsShown()  and MapQuestInfoRewardsFrame:IsShown() then
			local questItemName = "MapQuestInfoRewardsFrameQuestInfoItem";

			for i=1, 500 do
				local button = _G[questItemName..i];
				if button and button:IsShown() then
					if button.type == "choice" then
						result = result + 1
					end
				end
			end
		end

		return result
	end

	function GA.ItemChoiceIterator(invariant, control)
		if not control then control = 0 end
		control = control + 1
        
        --Immersion addon
		if ImmersionFrame and ImmersionFrame:IsShown() then
				local buttonname = "ImmersionQuestInfoItem"..control
		
			if _G[buttonname] then
				local button = _G[buttonname]
				local itemid = button:GetID()
		
				if button and itemid > 0 then
					local link = GetQuestItemLink(button.type, itemid)
				end
				
				if link then
					return control, link, button
				else
					return
				end
			else
				return
			end
		end
				
		--Quest acept
		if QuestFrame:IsShown() and QuestInfoRewardsFrame:IsShown() then
			if control <= AmountOfShownChoises() then
				local questItemName = "QuestInfoRewardsFrameQuestInfoItem";
				local button = _G[questItemName..control];
				local itemLink = GetQuestItemLink("choice", button:GetID())
				if itemLink then
					return control, itemLink, button
				end
			else return end 
		end

		--Map qust log
		if WorldMapFrame:IsShown() and QuestMapFrame:IsShown()  and MapQuestInfoRewardsFrame:IsShown() then
			if control <= AmountOfShownChoises() then
				local questItemName = "MapQuestInfoRewardsFrameQuestInfoItem";
				local button = _G[questItemName..control];
				local itemLink = GetQuestLogItemLink("choice", button:GetID())
				if itemLink then
					return control, itemLink, button
				end
			else return end 
		end
	end

	local function LootRollIterator(invariant, control)
		if not control then control = 0 end
		control = control + 1
		if control<=NUM_GROUP_LOOT_FRAMES then
			local lootFrame =  _G["GroupLootFrame"..control]
			if lootFrame and lootFrame:IsShown() then
				local itemLink = GetLootRollItemLink(lootFrame.rollID)
				if itemLink then
					return control, itemLink, lootFrame
				end
			end
		end
		return
	end

	local ejLootContainer
	function GA.IterateEncounterJournal()
		local EncounterJournal = EncounterJournal
		if not EncounterJournal then return end
		ejLootContainer = ejLootContainer or EncounterJournal.encounter.info.LootContainer
		if
			EncounterJournal and
			EncounterJournal:IsShown() and
			ejLootContainer:IsShown()
		then
			return DGV.IterateScrollBox(ejLootContainer.ScrollBox)
		end
		return DGV.NoOp
	end

	function GA.VendorIterator(invariant, control)
		if not control then control = 0 end
		control = control + 1
		local buyBack = BuybackBG:IsShown()
		local link = buyBack and GetBuybackItemLink(control) or GetMerchantItemLink(control)
		if link then
			local numItems = buyBack and BUYBACK_ITEMS_PER_PAGE or MERCHANT_ITEMS_PER_PAGE
			local buttonIndex = control % numItems
			if buttonIndex==0 then buttonIndex = numItems end
			local itemButton =  _G["MerchantItem"..buttonIndex.."ItemButton"]
			itemButton = itemButton and itemButton:IsShown() and itemButton
			if not itemButton then return end
			return control, link, itemButton
		end
		return
	end

	local function IterateLoot()
		return DGV.IterateScrollBox(LootFrame.ScrollBox)
	end

	local function EvaluateWinCriteron(criterion, itemLink)
		if criterion:Predicate(itemLink, unpack(criterion)) then
			local win, altWinner, score, altScore, uniqueSlot = criterion:GetScore(itemLink, unpack(criterion))
			if win then
				return score, altWinner, altScore, uniqueSlot
			end
		end
	end

	math.sum = function(...)
		local sum = 0
		for i=1,select("#", ...) do
			sum = sum + (select(i,...))
		end
		return sum
	end

	local function MainHandBlocksSecondarySlot(itemSubclass, equipSlot, link, spec, level)
		if PlayerCanTitansGrip(itemSubclass, spec, level) then

			return false
		elseif itemSubclass==LE_ITEM_WEAPON_CROSSBOW or itemSubclass==LE_ITEM_WEAPON_GUNS or itemSubclass==LE_ITEM_WEAPON_BOWS or itemSubclass==LE_ITEM_WEAPON_POLEARM or itemSubclass==LE_ITEM_WEAPON_STAFF then --crossbow does, but wands (another INVTYPE_RANGEDRIGHT) don't
			return true
		elseif equipSlot=="INVTYPE_RANGED" or equipSlot=="INVTYPE_2HWEAPON" then
			return true
		end
		return false

	end

	function GetDefaultUniqueInventorySlot(equipSlot)
		if equipSlot=="" then return end
		if equipSlot=="INVTYPE_ROBE" then
			return "INVTYPE_CHEST"
		elseif
			equipSlot=="INVTYPE_WEAPON" or equipSlot=="INVTYPE_RANGED" or equipSlot=="INVTYPE_2HWEAPON" or
			equipSlot=="INVTYPE_WEAPONMAINHAND" or equipSlot=="INVTYPE_RANGEDRIGHT"
		then
			return INVSLOT_MAINHAND
		elseif equipSlot=="INVTYPE_SHIELD" or equipSlot=="INVTYPE_WEAPONOFFHAND" or equipSlot=="INVTYPE_HOLDABLE" then
			return INVSLOT_OFFHAND
		end
		return equipSlot
	end

	local function CanAdornUniqueInventorySlot(uniqueInventorySlot, itemClass, itemSubclass, equipSlot, spec, level)
		local class = select(2,UnitClass("player"))
		if not equipSlot or equipSlot=="" then return false end
		if equipSlot=="INVTYPE_WEAPONOFFHAND" and not PlayerCanDualWield(spec, level) then return false end
		if uniqueInventorySlot==GetDefaultUniqueInventorySlot(equipSlot) then
			return true
		elseif uniqueInventorySlot==INVSLOT_OFFHAND and itemClass==LE_ITEM_CLASS_WEAPON then
			if (equipSlot=="INVTYPE_WEAPONOFFHAND" or equipSlot=="INVTYPE_WEAPON") and PlayerCanDualWield(spec, level) then
				return true
			elseif equipSlot=="INVTYPE_SHIELD" or equipSlot=="INVTYPE_HOLDABLE" and class~="HUNTER" then
				return true
			elseif PlayerCanTitansGrip(itemSubclass, spec, level) then
				return true
			end
		end
		return false
	end

	local function DualWieldPreferenceIsMainhandDPS()
		local class = select(2,UnitClass("player"))
		return class=="MONK"
	end

	local function DualWieldPreferenceIsOffhandDPH()
		local class = select(2,UnitClass("player"))
		return class=="SHAMAN"
	end

	local function DualWieldPreferenceIsMainhandDagger()
		local class = select(2,UnitClass("player"))
		local spec = GetSpecialization()
		return class=="ROGUE" and spec==3 --Subtlety Rogue
	end

	local function ShouldSwapMainOffWeapons(mainLink, offLink, spec, level)
		if not mainLink or not offLink then return end
		if ItemIsBannedWeapon(mainLink) or ItemIsBannedWeapon(offLink) then return end --don't swap hands if item is banned
		local mainSums, offSums = GetItemSums(mainLink), GetItemSums(offLink)
		local mainItemClass, mainItemSubclass = select(12, GetItemInfo(mainLink))
		local offItemClass, offItemSubclass = select(12, GetItemInfo(offLink))

		local continueSwap = false
		if DualWieldPreferenceIsMainhandDPS() and DGV:UserSetting(DGV_WEAPONPREF)=="Auto" then
			continueSwap = (mainSums["DPS"] or 0) < (offSums["DPS"] or 0)
		--elseif DualWieldPreferenceIsOffhandDPH() or DGV:UserSetting(DGV_WEAPONPREF)=="Fast / Slow" then --No longer needed for Legion
			--continueSwap = (mainSums["MAX_DAMAGE"] or 0) > (offSums["MAX_DAMAGE"] or 0)
		elseif DGV:UserSetting(DGV_WEAPONPREF)=="Slow / Fast" and PlayerCanDualWield(spec, level) then
			continueSwap = (mainSums["MAX_DAMAGE"] or 0) < (offSums["MAX_DAMAGE"] or 0)
		elseif DualWieldPreferenceIsMainhandDagger() and (mainItemSubclass == 15 or offItemSubclass == 15) then
			continueSwap = mainItemSubclass ~= 15 --and (mainSums["MAX_DAMAGE"] or 0) < ((offSums["MAX_DAMAGE"]*1.5) or 0) --Dagger unless other item does 50% more MAX_DAMAGE
		else
			continueSwap = (mainSums["MAX_DAMAGE"] or 0) < (offSums["MAX_DAMAGE"] or 0)
		end
		mainSums:Pool()

		offSums:Pool()
		if not continueSwap then return end
		local itemEquipSlot, _, _, itemClass, itemSubclass = select(9, GetItemInfo(mainLink))
		if not CanAdornUniqueInventorySlot(INVSLOT_OFFHAND, itemClass, itemSubclass, itemEquipSlot, spec, level) then return end
		local itemEquipSlot, _, _, itemClass, itemSubclass = select(9, GetItemInfo(offLink))
		if not CanAdornUniqueInventorySlot(INVSLOT_MAINHAND, itemClass, itemSubclass, itemEquipSlot, spec, level) then return end
		return true
	end

	local function IsEnforcedTableComplete(enforcedTable)
		for unique,inv1,inv2 in NextUniqueInventorySlot do
			if IsArmorSpecSlot(inv1) and not enforcedTable[inv1] then return end
		end
		return true
	end

	local function StandardTableGrantsArmorSpecialization(standardTable, enforcedTable)
		for unique,inv1,inv2 in NextUniqueInventorySlot do
			if IsArmorSpecSlot(inv1) and (not standardTable[inv1]) or enforcedTable[inv1]~=standardTable[inv1] then return end
		end
		return true
	end

	local function StatKeyToUnitStatIndex(statKey)
		if statKey=="STR" then return 1
		elseif statKey=="AGI" then return 2
		elseif statKey=="STA" then return 3
		elseif statKey=="INT" then return 4
		--		elseif statKey=="SPI" then return 5
		end
	end

	local function EstimateStatBaseValue(statKey, spec)
		local stat, effectiveStat, posBuff, negBuff = UnitStat("player", StatKeyToUnitStatIndex(statKey))
		local base =  stat - posBuff - negBuff
		local coefficient = 1
		local bearform

		for i=1,10 do
			local aura = C_UnitAuras.GetBuffDataByIndex("player",i)
			if (aura and aura.name == C_Spell.GetSpellInfo(5487).name) then bearform = true end
		end
		
		if StatLogic.GetArmorSpecActive()==spec then
			coefficient = coefficient + .05
		end
		if statKey=="STA" then
			if IsSpellKnown(29144) then --Unwavering Sentinel
				coefficient = coefficient + .15
			elseif bearform then --Bear Form
				coefficient = coefficient + .2
			else
				if IsSpellKnown(50029) then --Veteran of the Third War
					coefficient = coefficient + .09
				end
				if StatLogic.SlotHasEnchant(3847, INVSLOT_MAINHAND) then --Rune of the Stoneskin Gargoyle
					coefficient = coefficient + .02
				elseif StatLogic.SlotHasEnchant(3883, INVSLOT_MAINHAND) then --Rune of the Nerubian Carapace Mainhand
					coefficient = coefficient + .01
				end
				if StatLogic.SlotHasEnchant(3883, INVSLOT_OFFHAND) then --Rune of the Nerubian Carapace Mainhand Offhand
					coefficient = coefficient + .01
				end
			end
		elseif statKey=="STR" and IsSpellKnown(91107) then --Unholy Might
			coefficient = coefficient + .1
		end
		base = base/coefficient
		--DGV:DebugFormat("EstimateStatBaseValue", "statKey", statKey, "spec", spec, "base", base)
		return base
	end

	local function GetArmorSpecStat(data)
		if data[1]=="ARMOR_SPECIALIZATION_STAT" then
			return data[2]
		end
	end

	local function GetArmorSpecStatWeight(...)
		local armorSpecStatKey
		for i=1,select("#", ...) do
			local data = (select(i, ...))
			armorSpecStatKey = armorSpecStatKey or GetArmorSpecStat(data)
			if armorSpecStatKey and data[1]==armorSpecStatKey then
				return armorSpecStatKey, data[2]
			end
		end
	end

	local function ArmorSpecializationBonusWins(spec, standardTable, enforcedTable)
		local armorSpecStatKey, armorSpecStatWeight = GetArmorSpecStatWeight(GetScoringInfo(spec))
		local bonusStatValue = (enforcedTable.ArmorSpecStatTotal + EstimateStatBaseValue(armorSpecStatKey, spec))*.05
		--DGV:DebugFormat("ArmorSpecializationBonusWins", "bonusStatValue", bonusStatValue, "enforcedTable.ArmorSpecStatTotal", enforcedTable.ArmorSpecStatTotal, "EstimateStatBaseValue(armorSpecStatKey, spec)", EstimateStatBaseValue(armorSpecStatKey, spec))
		local bonusScore = bonusStatValue * armorSpecStatWeight
		local enforcedTableScore = enforcedTable.ScoreTotal + bonusScore
		-- if not standardTable.ScoreTotal	then
		-- DGV:DebugFormat("ArmorSpecializationBonusWins", "standardTable", tostring(standardTable), "standardTable:IsBoundToAutoroutineLifetime()", standardTable:IsBoundToAutoroutineLifetime())
		-- end

        if enforcedTableScore == nil then
            enforcedTableScore = 0
        end

        local scoreTotal = standardTable.ScoreTotal

        if scoreTotal == nil then
            scoreTotal = 0
        end

		return enforcedTableScore>scoreTotal
	end

	local function GetArmorSpecSubclassFrom(data)
		if data[1]=="LE_ITEM_CLASS_ARMOR" then
			local greatest
			for i=2,#data do
				local value = data[i]
				value = tonumber(value:match("%d"))
				if
					value
					and value~=LE_ITEM_ARMOR_SHIELD
					and value~=LE_ITEM_ARMOR_COSMETIC
					and (not greatest or value>greatest)
				then
					greatest = value
				end
			end
			return greatest
		end
	end

	local function GetArmorSpecSubclass(...)
		for i=1,select("#", ...) do
			local data = (select(i, ...))
			local subclass = GetArmorSpecSubclassFrom(data)
			if subclass then return subclass end
		end
	end
    
    local function Set_GetCurrentBestInSlot_cache_v2(slot, key, value)
        if not slot then
            return
        end
        
        local slotData = DGV.GetCurrentBestInSlot_cache_v2[slot]
        if not slotData then
            slotData = {}
            DGV.GetCurrentBestInSlot_cache_v2[slot] = slotData
        end
        slotData[key] = value
    end

	local function GetCurrentBestInSlot(uniqueInventorySlot, spec, pvp, level, skip, enforceArmorSpecSubclass
		,uncapped, ignoreLevelRequirement, itemMustWin, threading)
	
		if not uniqueInventorySlot then
			return
		end

		--Used to detect unique-equpped gears with the same id (such gears cannot be used at the same time)
		local itemId2Score = {}

		local key = strformat("%s%s%s%s%s%s%s%%s%s%s%s", "BestInSlot", uniqueInventorySlot, spec or 0, pvp or 0, level or 0, tostring(skip or 0), tostring(enforceArmorSpecSubclass or 0)
		, tostring(uncapped or 0), tostring(ignoreLevelRequirement or 0), tostring(itemMustWin or 0))
	
		local cachedSlotsData = DGV.GetCurrentBestInSlot_cache_v2[uniqueInventorySlot]
		if cachedSlotsData and uniqueInventorySlot then
			local cachedSlotData = cachedSlotsData[key]
			if cachedSlotData then
				return unpack(cachedSlotData)
			end
		end
	
		if not cachedSlotsData then
			 DGV.GetCurrentBestInSlot_cache_v2[uniqueInventorySlot] = {}
		end
		
	
        DGV.autoroutineTimeLimitOverride = 10
    
		level = level or UnitLevel("player")
		spec = spec or GetSpecialization()
		local armorSpecSubclass =  itemMustWin and GetArmorSpecSubclass(GetScoringInfo(spec, pvp))
		local winner, winScore, winArmorSpecStatValue, winnerGrantsArmorSpecSubclass,
			altWinner, altScore, altArmorSpecStatValue,
			mainHandWinner, mainHandScore, mainHandOffHandScore, mainHandArmorSpecStatValue,
			offHandWinner, offHandScore, offHandArmorSpecStatValue,
			twoHandWinner, twoHandScore, twoHandArmorSpecStatValue,
			mustWinGrantsArmorSpecSubclass
		local itemInvariant = GetCreateTable()
		itemInvariant.first = itemMustWin
		itemInvariant.skip = skip
		for control,iteratedItemLink in ItemIterator,itemInvariant do
			local reqLevel, itemClass, itemSubclass, itemEquipSlot
            
			LuaUtils:RestIfNeeded(threading)
            
			if not ItemIsBanned(iteratedItemLink, control, threading) then
				local score, armorSpecStatValue
				reqLevel, _, _, _, itemEquipSlot, _, _, itemClass, itemSubclass = select(5, GetItemInfo(iteratedItemLink))
				if not ignoreLevelRequirement then
					if not level or not reqLevel or level<reqLevel then score = -1 end
				end
				if iteratedItemLink==itemMustWin and itemClass==LE_ITEM_CLASS_ARMOR and itemSubclass==armorSpecSubclass then
					mustWinGrantsArmorSpecSubclass = true
				end
				local canAdornOffHand
				local canAdornSlot = CanAdornUniqueInventorySlot(uniqueInventorySlot, itemClass, itemSubclass, itemEquipSlot, spec, level)
				if uniqueInventorySlot==INVSLOT_MAINHAND then
					canAdornOffHand = CanAdornUniqueInventorySlot(INVSLOT_OFFHAND, itemClass, itemSubclass, itemEquipSlot, spec, level)
				end
				if not canAdornSlot then
					if not canAdornOffHand then
						score = -4
					end
				end
				if not score then
					score, armorSpecStatValue = CalculateScore(iteratedItemLink, spec, pvp, level, uniqueInventorySlot, nil, enforceArmorSpecSubclass, uncapped)
				end

				if score>=0 then--pair holdable, ring and trinket slots
					if uniqueInventorySlot==INVSLOT_MAINHAND then
						if canAdornSlot and MainHandBlocksSecondarySlot(itemSubclass, itemEquipSlot, iteratedItemLink, spec, level) then
							if not twoHandScore or twoHandScore<score then
								twoHandScore=score
								twoHandWinner=iteratedItemLink
								twoHandArmorSpecStatValue=armorSpecStatValue
							end
						else
							if canAdornSlot and (not mainHandScore or mainHandScore<score) then
								if mainHandOffHandScore and (not offHandScore or offHandScore<mainHandOffHandScore) then
									offHandScore = mainHandOffHandScore
									offHandWinner = mainHandWinner
									offHandArmorSpecStatValue = mainHandArmorSpecStatValue
								end
								mainHandScore=score
								mainHandWinner=iteratedItemLink
								mainHandArmorSpecStatValue=armorSpecStatValue
								if canAdornOffHand then
									mainHandOffHandScore = CalculateScore(iteratedItemLink, spec, pvp, level, INVSLOT_OFFHAND, nil, enforceArmorSpecSubclass, uncapped)
								end
							elseif canAdornOffHand then
								score = CalculateScore(iteratedItemLink, spec, pvp, level, INVSLOT_OFFHAND, nil, enforceArmorSpecSubclass, uncapped)
								if not offHandScore or offHandScore<score then
									offHandScore = score
									offHandWinner = iteratedItemLink
									offHandArmorSpecStatValue = armorSpecStatValue
								end
							end
						end
					else
						local iteratedItemWonTieBreaker
						if score==winScore then
							local winnerTieBreaker = CalculateScore(winner, spec, pvp, level, uniqueInventorySlot, true, enforceArmorSpecSubclass, uncapped)
							local iteratedItemTieBreaker = CalculateScore(iteratedItemLink, spec, pvp, level, uniqueInventorySlot, true, enforceArmorSpecSubclass, uncapped)
							if iteratedItemTieBreaker>winnerTieBreaker then

								iteratedItemWonTieBreaker = true
							end
						end
						if not winScore then
							winScore = score
							winner = iteratedItemLink
							winArmorSpecStatValue = armorSpecStatValue
							winnerGrantsArmorSpecSubclass = itemClass==LE_ITEM_CLASS_ARMOR and itemSubclass==armorSpecSubclass
						elseif winScore<score or iteratedItemWonTieBreaker then

                            local itemLinkParts = {strsplit(":", winner)}
                            local winneritemId = tonumber(itemLinkParts[2])                        
                            --if not GA.IsUniqueEquippedGear(winneritemId) then
                                altScore = winScore
                                altWinner = winner
                                altArmorSpecStatValue = winArmorSpecStatValue
                            --end
                            
							winScore = score
							winner = iteratedItemLink
							winArmorSpecStatValue = armorSpecStatValue
							winnerGrantsArmorSpecSubclass = itemClass==LE_ITEM_CLASS_ARMOR and itemSubclass==armorSpecSubclass
						elseif not altScore or altScore<score then
                            local itemLinkParts = {strsplit(":", iteratedItemLink)}
                            local winneritemId = tonumber(itemLinkParts[2])                        
                        
                           -- if not GA.IsUniqueEquippedGear(winneritemId) then
                                altScore = score
                                altWinner = iteratedItemLink
                                altArmorSpecStatValue = armorSpecStatValue
                           -- end
						end
					end
				end
			end
			if
				itemMustWin
				and (winner~=itemMustWin and ((not mustWinGrantsArmorSpecSubclass) or winnerGrantsArmorSpecSubclass))
				and altWinner~=itemMustWin
				and mainHandWinner~=itemMustWin
				and offHandWinner~=itemMustWin
				and twoHandWinner~=itemMustWin
			then
				control:Pool()
                DGV.autoroutineTimeLimitOverride = nil
                Set_GetCurrentBestInSlot_cache_v2(uniqueInventorySlot, key, {})
				return
			end
			YieldAutoroutine()
		end
        
        DGV.autoroutineTimeLimitOverride = nil
        
		if uniqueInventorySlot~="INVTYPE_FINGER" and uniqueInventorySlot~="INVTYPE_TRINKET" then
			altScore = nil
			altWinner = nil
			altArmorSpecStatValue = nil
		end
		if uniqueInventorySlot==INVSLOT_MAINHAND then
			offHandScore = offHandScore or 0
			mainHandScore = mainHandScore or -1
			twoHandScore = twoHandScore or -1
			if mainHandScore+offHandScore>=twoHandScore then
				if
					itemMustWin
					and mainHandWinner~=itemMustWin
					and offHandWinner~=itemMustWin
				then
					Set_GetCurrentBestInSlot_cache_v2(uniqueInventorySlot, key, {})
					return
				end
				if DGV:UserSetting(DGV_WEAPONPREF)~="Never Swap" then
					if ShouldSwapMainOffWeapons(mainHandWinner, offHandWinner, spec, level) then
						winner, winScore, altWinner, altScore = offHandWinner, mainHandScore+offHandScore, mainHandWinner, mainHandScore
						winArmorSpecStatValue, altArmorSpecStatValue = offHandArmorSpecStatValue, mainHandArmorSpecStatValue
					elseif not ItemIsBannedWeapon(mainHandWinner) and not ItemIsBannedWeapon(offHandWinner) then
						winner, winScore, altWinner, altScore = mainHandWinner, mainHandScore+offHandScore, offHandWinner, offHandScore
						winArmorSpecStatValue, altArmorSpecStatValue = mainHandArmorSpecStatValue, offHandArmorSpecStatValue
					end
				end
			else
				if
					itemMustWin
					and twoHandWinner~=itemMustWin
				then
					Set_GetCurrentBestInSlot_cache_v2(uniqueInventorySlot, key, {})
					return
				end
				winner, winScore, winArmorSpecStatValue = twoHandWinner, twoHandScore, twoHandArmorSpecStatValue
			end
		end
		local invSlot = UniqueInventoryToInvSlot(uniqueInventorySlot)
		if
			enforceArmorSpecSubclass==nil and
			not winnerGrantsArmorSpecSubclass and
			IsArmorSpecSlot(invSlot)
		then
			local standardTable = GA:GetSpecDataTable(spec, pvp, nil, false, uncapped, nil, not threading):BindToAutoroutineLifetime(UnbindSpecDataTable)
			--DGV:DebugFormat("GetCurrentBestInSlot", "standardTable", tostring(standardTable), "standardTable:IsBoundToAutoroutineLifetime()", standardTable:IsBoundToAutoroutineLifetime())
			local enforcedTable = GA:GetSpecDataTable(spec, pvp, nil, true, uncapped, nil, not threading):BindToAutoroutineLifetime(UnbindSpecDataTable)
			if
				IsEnforcedTableComplete(enforcedTable) and
				not StandardTableGrantsArmorSpecialization(standardTable, enforcedTable)
				and UnitStat("player", 1) --check if working... sometime this can fail during level up event.
				and ArmorSpecializationBonusWins(spec, standardTable, enforcedTable)
			then
				winner = enforcedTable[invSlot]
				local score, armorSpecStatValue = CalculateScore(winner, spec, pvp, level, uniqueInventorySlot, nil, enforceArmorSpecSubclass, uncapped)
				
                Set_GetCurrentBestInSlot_cache_v2(uniqueInventorySlot, key, {winner, score, nil, nil, armorSpecStatValue})
				
				return winner, score, nil, nil, armorSpecStatValue
			end
		end
		if winScore and winScore>=0 then
            Set_GetCurrentBestInSlot_cache_v2(uniqueInventorySlot, key, {winner, winScore, altWinner, altScore, (winArmorSpecStatValue or 0) + (altArmorSpecStatValue or 0)})
			return winner, winScore, altWinner, altScore, (winArmorSpecStatValue or 0) + (altArmorSpecStatValue or 0)
		end
	end

	local function CalculateByScoringInfo(itemLink, spec, pvp, level, uniqueInventorySlot, itemSums, enforceArmorSpecSubclass, uncapped, forGearFinder, ...)
		local total, armorSpecStatKey, armorSpecStatValue = 0
		for i=1,select("#", ...) do
			local data = (select(i, ...))
			if enforceArmorSpecSubclass then
				enforceArmorSpecSubclass = GetArmorSpecSubclassFrom(data) or enforceArmorSpecSubclass
			end
			armorSpecStatKey = armorSpecStatKey or GetArmorSpecStat(data)
			local score, statSum = CalculateScoreForInfo(itemLink, spec, pvp, level, uniqueInventorySlot, itemSums, enforceArmorSpecSubclass, uncapped, forGearFinder, unpack(data))
            
            local itemLevel, _, _, _, _, equipSlot
            
            if forGearFinder then
                itemLevel, _, _, _, _, equipSlot = select(4, LuaUtils.GetItemInfo_dugi(itemLink, true))
            else
                itemLevel, _, _, _, _, equipSlot = select(4, GetItemInfo(itemLink))
            end
            
			if equipSlot == "INVTYPE_TRINKET" and DGV:UserSetting(DGV_SUGGESTTRINKET) then
				if score and score>0 then
                    if not forGearFinder then
                        --score = score + (itemLevel * 10) --itemLevel value is not accurate from GetItemInfo need to be scanned from Tooltip somehow
                    end
				end
			end

			if armorSpecStatKey and data[1]==armorSpecStatKey then
				armorSpecStatValue = statSum
			end
			if score then
				if score<0 then return score end
				total = total + score
			end
			YieldAutoroutine()
		end
		return total, armorSpecStatValue
	end

	CalculateScore =  function(itemLink, spec, pvp, level, uniqueInventorySlot, tiebreaker, enforceArmorSpecSubclass, uncapped)
		--uncapped = true

        --equipped Needs to also be included as for some items the score is also based on the equipped state.
        local equipped = ItemIsEquipped(itemLink)
		local cacheKey = not tiebreaker and strformat("%d%s%s%d%s%d%s%s%s", "CalculateScore", equipped and 1 or 0, itemLink, spec, pvp and "true" or "false", level, tostring(uniqueInventorySlot), enforceArmorSpecSubclass and tostring(enforceArmorSpecSubclass) or "nil", uncapped and "0" or "1")
		if cacheKey then
			local cachedValue = DGV.CalculateScore_cache[cacheKey]
			if cachedValue then
				return unpack(cachedValue)
			end
		end

		local itemSums = GetItemSums(itemLink):BindToAutoroutineLifetime(tPool)
		--StatLogic:GetSum(itemLink, itemSums)
		--DGV.InitTable(itemSums)
		local levelLimit = itemSums["PLAYER_LEVEL_LIMIT"]
		if levelLimit and level>=levelLimit then
			itemSums["XP_BONUS"] = 0 -- Heirloom XP Bonus stops working after Level limit
			--itemSums:Pool()
			--return -6
		end
		local score, armorSpecStatValue
		if not tiebreaker then
			score, armorSpecStatValue = CalculateByScoringInfo(itemLink, spec, pvp, level, uniqueInventorySlot, itemSums, enforceArmorSpecSubclass, uncapped, false, GetScoringInfo(spec, pvp))
		else
			score = CalculateByScoringInfo(itemLink, spec, pvp, level, uniqueInventorySlot, itemSums, enforceArmorSpecSubclass, uncapped, false, GetTieBreakerScoringInfo())

		end
		itemSums:Pool()

		if cacheKey then
            DGV.CalculateScore_cache[cacheKey] = {score, armorSpecStatValue}
		end
		return score, armorSpecStatValue
	end

    if not DugisCharacterCache.CalculateScore_cache_v12 then
        DugisCharacterCache.CalculateScore_cache_v12 = {}
    end

	CalculateScoreForGearFinder = function(itemLink, spec, pvp, level, uniqueInventorySlot, tiebreaker, enforceArmorSpecSubclass, uncapped)
		--uncapped = true
        local equipped = ItemIsEquipped(itemLink)
		local cacheKey = not tiebreaker and strformat("%d%s%s%d%s%d%s%s%s", "CalculateScore", equipped and 1 or 0, itemLink, spec, pvp and "true" or "false", level, tostring(uniqueInventorySlot), enforceArmorSpecSubclass and tostring(enforceArmorSpecSubclass) or "nil", uncapped and "0" or "1")
		if cacheKey then
            if DugisCharacterCache.CalculateScore_cache_v12[cacheKey] then
                return unpack(DugisCharacterCache.CalculateScore_cache_v12[cacheKey])
            end

		--	local cacheReaction = TryGetCacheReaction(cacheKey)
		--	if cacheReaction then
		--DGV:De--bugFormat("CalculateScore cached", "itemLink", itemLink, "uncapped", uncapped,  "score", (cacheReaction:UnpackCache()))
        --
        --        --print("X", cacheReaction:UnpackCache())
		--		return cacheReaction:UnpackCache()
		--	end
		end
		--Added parameter to make LibStatLogic back waiting until Tooltip gets loaded for scratching data. Without that fix LibStatLogic often fails.
		local itemSums = GetItemSums(itemLink, true) --:BindToAutoroutineLifetime(tPool)
		--StatLogic:GetSum(itemLink, itemSums)
		--DGV.InitTable(itemSums)
		local levelLimit = itemSums["PLAYER_LEVEL_LIMIT"]
		if levelLimit and level>=levelLimit then
			itemSums["XP_BONUS"] = 0 -- Heirloom XP Bonus stops working after Level limit
			--itemSums:Pool()
			--return -6
		end
		local score, armorSpecStatValue
		if not tiebreaker then
			score, armorSpecStatValue = CalculateByScoringInfo(itemLink, spec, pvp, level, uniqueInventorySlot, itemSums, enforceArmorSpecSubclass, uncapped, true, GetScoringInfo(spec, pvp))
		else
			score = CalculateByScoringInfo(itemLink, spec, pvp, level, uniqueInventorySlot, itemSums, enforceArmorSpecSubclass, uncapped, true, GetTieBreakerScoringInfo())

		end
		--itemSums:Pool()

		if cacheKey then
           DugisCharacterCache.CalculateScore_cache_v12[cacheKey] = {score, armorSpecStatValue}
		end
		--DGV:DebugFormat("CalculateScore", "itemLink", itemLink,"uncapped", uncapped, "score", score)
		return score, armorSpecStatValue
	end

	function GA.ResetCalculateScoreCache()
        GA.AutoEquipSmartSet(false)
	end

	local function StandardCompare(criterion, link, level)
		local uniqueSlot = GetDefaultUniqueInventorySlot(select(9, GetItemInfo(link)))
		local winningItem, winningScore, altWinner, altScore = GetCurrentBestInSlot(uniqueSlot, criterion.specNum, criterion.pvp, level, nil, nil, nil, true, link)
		return winningItem==link or altWinner==link, altWinner==link and winningItem or altWinner, winningItem==link and winningScore or altScore, winningItem==link and altScore or winningScore, uniqueSlot
	end

	local function CoinCompare(criterion, link)
		local highest, value = true, select(11, GetItemInfo(link))
		for _, choice in GA.ItemChoiceIterator do
			local choiceValue = select(11, GetItemInfo(choice))
			highest = highest and choiceValue<=value
		end
		if value == 0 then 
			return false, value, value 
		else 
			return highest, value, value
		end
	end

    local function GetStorylineButtonByRewardName(rewardName)
        for i = 1, 50 do
            if _G["Storyline_ItemButton"..i] and _G["Storyline_ItemButton"..i].Name:GetText() == rewardName then
                return _G["Storyline_ItemButton"..i]
            end
        end
    end

	local function StandardRewardAdorner(criterion, link, rewardFrame)
        if Storyline_NPCFrame and Storyline_NPCFrame:IsShown() then
            local storyLineRewardFrame = GetStorylineButtonByRewardName(rewardFrame.Name:GetText())
            if storyLineRewardFrame then
                rewardFrame = storyLineRewardFrame
            end
        end

		local chosenColor
		if not DugisGreenArrowRewardAdornment:IsShown() then
			chosenColor = DugisGreenArrowRewardAdornment
		elseif not DugisYellowArrowRewardAdornment:IsShown() then
			if DugisGreenArrowRewardAdornment:IsShown() then
				local _,relativeTo = DugisGreenArrowRewardAdornment:GetPoint()
				if relativeTo==rewardFrame then return end
			end
			chosenColor = DugisYellowArrowRewardAdornment
		end
		if not chosenColor then return end
		chosenColor:SetParent(rewardFrame)
		chosenColor:ClearAllPoints()
		chosenColor:SetSize(28, 28)
        
        --Immersion addon
        if ImmersionFrame and ImmersionFrame:IsShown() then
            chosenColor:SetPoint("TOP", rewardFrame, "BOTTOMLEFT", 40, 25)
        else
            chosenColor:SetPoint("TOP", rewardFrame, "BOTTOMLEFT", 30, 25)
        end

        chosenColor:SetFrameStrata("DIALOG")
        chosenColor:SetFrameLevel(501)
		chosenColor:Show()
	end

	local function StandardLootRollAdorner(criterion, link, groupLootFrame)
		if not groupLootFrame.dugisGreenArrow then
			groupLootFrame.dugisGreenArrow = CreateFrame("Frame", nil, groupLootFrame.IconFrame, "DugisGreenArrowAdornmentTemplate")
		end
		groupLootFrame.dugisGreenArrow:ClearAllPoints()
		groupLootFrame.dugisGreenArrow:SetSize(28, 28)
		groupLootFrame.dugisGreenArrow:SetPoint("TOP", groupLootFrame.IconFrame, "BOTTOMLEFT", 30, 25)
		groupLootFrame.dugisGreenArrow.link = link
		groupLootFrame.dugisGreenArrow:Show()
	end

	local function StandardItemButtonAdorner(criterion, link, buttonFrame, color)
		local frame
		if color=="green" then
			if not buttonFrame.dugisGreenArrow then
				buttonFrame.dugisGreenArrow = CreateFrame("Frame", nil, buttonFrame, "DugisGreenArrowAdornmentTemplate")
			end
			frame = buttonFrame.dugisGreenArrow
		else
			if buttonFrame.dugisGreenArrow and buttonFrame.dugisGreenArrow:IsShown() then return end
			if not buttonFrame.dugisYellowArrow then
				buttonFrame.dugisYellowArrow = CreateFrame("Frame", nil, buttonFrame, "DugisYellowArrowAdornmentTemplate")
			end
			frame = buttonFrame.dugisYellowArrow
		end

		frame:ClearAllPoints()
		frame:SetSize(28, 28)
		frame:SetPoint("TOP", buttonFrame.icon, "BOTTOMLEFT", 30, 25)
		frame:Show()
	end

	local function ResetPooledArrow(pool, frame)
		frame:Hide()
		frame:ClearAllPoints()
	end

	local function CreatePooledArrow(pool)
		return CreateFrame("Frame", nil, UIParent, pool:GetTemplate())
	end

	local greenArrowPool = CreateUnsecuredRegionPoolInstance("DugisGreenArrowAdornmentTemplate", CreatePooledArrow, ResetPooledArrow)
	local yellowArrowPool = CreateUnsecuredRegionPoolInstance("DugisYellowArrowAdornmentTemplate", CreatePooledArrow, ResetPooledArrow)

	local function ReleaseIf(pool, predicate)
		for frame in pool:EnumerateActive() do
			if predicate(frame) then
				pool:Release(frame)
			end
		end
	end
	greenArrowPool.ReleaseIf = ReleaseIf
	yellowArrowPool.ReleaseIf = ReleaseIf

	local function StandardPooledAdorner(criterion, link, itemButton, color)
		local frame = color=="green" and greenArrowPool:Acquire() or yellowArrowPool:Acquire()
		frame:SetParent(itemButton)
		frame:SetPoint("TOP", itemButton.icon, "BOTTOMLEFT", 30, 25)
		frame:Show()
	end

	function StandardTiebreaker(criterion, linkA, linkB, level)
		level = level or UnitLevel("player")
		local uniqueInventorySlotA = GetDefaultUniqueInventorySlot(select(9, GetItemInfo(linkA)))
		local uniqueInventorySlotB = GetDefaultUniqueInventorySlot(select(9, GetItemInfo(linkB)))

		local tieBreakerA = CalculateScore(linkA, criterion.specNum, criterion.pvp, level, uniqueInventorySlotA, true)
		local tieBreakerB = CalculateScore(linkB, criterion.specNum, criterion.pvp, level, uniqueInventorySlotB, true)
		return tieBreakerA>tieBreakerB and linkA or linkB
	end

	local function FallbackTooltipAdorner(tooltip, option)
		tooltip:AddLine(option)
	end

	local function IsQuestRewardLootRollOrEJItem(link)
		local itemInvariant = GetCreateTable()
		itemInvariant.func = GetLootRollItemLink
		for control,iteratedItemLink in ItemIterator,itemInvariant do
			if link==iteratedItemLink then
				local pass = control.itemType or control.func==GetLootRollItemLink or control.func==C_EncounterJournal.GetLootInfoByIndex
				control:Pool()
				return pass
			end
			YieldAutoroutine()
		end
	end

	local function StandardTooltipAdorner(criterion, tooltip, link, altWinner, score, altScore)
		score = score or 0
		if type(score) == "string" then
			score = tonumber(score) or 0
		end

		local color = RAID_CLASS_COLORS[select(2,UnitClass("player"))].colorStr
		if altWinner then
			tooltip:AddLine(L["Best in slot with %s - |c%s%s|r"]:format(altWinner, color, criterion.specName))
		else

			tooltip:AddLine(L["Best in slot - |c%s%s|r"]:format(color, criterion.specName))
		end
		if IsQuestRewardLootRollOrEJItem(link) then
			local uniqueInventorySlot = GetDefaultUniqueInventorySlot(select(9, GetItemInfo(link)))
			if uniqueInventorySlot ~= "INVTYPE_TRINKET" or DGV:UserSetting(DGV_SUGGESTTRINKET) then
				local currentWinningItem, currentWinningScore, currentAltWinner, currentAltScore = GetCurrentBestInSlot(uniqueInventorySlot, criterion.specNum, criterion.pvp, nil, ITEM_ITERATOR_SKIP_ALL_EXTERNAL)
				if not currentWinningItem or currentWinningScore==0 or score==0 then return end
				local upgrade, upgradeOver, upgradeOverAlt
				if altWinner==currentWinningItem then
					if not currentAltScore then return end
					upgradeOver, upgrade, upgradeOverAlt  = currentAltWinner, (1-currentAltScore/score)*100, currentWinningItem
				else
					upgradeOver, upgrade, upgradeOverAlt = currentWinningItem, (1-currentWinningScore/score)*100, currentAltWinner
				end
				--DGV:DebugFormat("StandardTooltipAdorner", "link", link, "altWinner", altWinner, "currentWinningItem",currentWinningItem, "currentAltWinner", currentAltWinner, "upgrade", upgrade, "currentWinningScore", currentWinningScore, "score", score, "altScore", altScore, "currentAltScore", currentAltScore)
				if upgradeOverAlt and upgradeOver and upgrade > 0 then
					tooltip:AddLine(L["|TInterface\\AddOns\\DugisGuideViewerZ\\Artwork\\UpgradeArrow:0|t|cff1eff00+%d%%|r upgrade over %s with %s"]:format(upgrade, upgradeOver, upgradeOverAlt)) --need fixing
				elseif upgradeOver and upgrade > 0 then

					tooltip:AddLine(L["|TInterface\\AddOns\\DugisGuideViewerZ\\Artwork\\UpgradeArrow:0|t|cff1eff00+%d%%|r upgrade over %s"]:format(upgrade, upgradeOver)) --need fixing
				end

				Repaint(tooltip)
			end
		end
	end

	local function CoinRewardAdorner(criterion, link, rewardFrame)
        if Storyline_NPCFrame and Storyline_NPCFrame:IsShown() then
            local storyLineRewardFrame = GetStorylineButtonByRewardName(rewardFrame.Name:GetText())
            if storyLineRewardFrame then
                rewardFrame = storyLineRewardFrame
            end
        end
         
        --Immersion addon already adds the coin 
        if ImmersionFrame and ImmersionFrame:IsShown() then
            return
        end
        
		DugisCoinRewardAdornment:ClearAllPoints()
		DugisCoinRewardAdornment:SetParent(rewardFrame)
		--DugisCoinRewardAdornment:SetFrameLevel(129)
		DugisCoinRewardAdornment:SetSize(35, 35)
		DugisCoinRewardAdornment:SetPoint("TOPRIGHT", rewardFrame, 5, 9)

        DugisCoinRewardAdornment:SetFrameStrata("DIALOG")
        DugisCoinRewardAdornment:SetFrameLevel(501)
		DugisCoinRewardAdornment:Show()
	end

	local function SpecFromLocalizedName(name)
		if name == "" then return 1 end 
		for specNum=1,5 do
			local specName = select(2,GetSpecializationInfo(specNum))
			if specName==name then
				return specNum
			end
		end
	end

	function IsEquipment(equipLoc)
		if invType=="" then return end
		  local uniqueInventorySlot = GetDefaultUniqueInventorySlot(equipLoc)
		return
			uniqueInventorySlot=="INVTYPE_HEAD" or
			uniqueInventorySlot=="INVTYPE_NECK" or
			uniqueInventorySlot=="INVTYPE_SHOULDER" or
			uniqueInventorySlot=="INVTYPE_CHEST" or
			uniqueInventorySlot=="INVTYPE_WAIST" or
			uniqueInventorySlot=="INVTYPE_LEGS" or
			uniqueInventorySlot=="INVTYPE_FEET" or
			uniqueInventorySlot=="INVTYPE_WRIST" or
			uniqueInventorySlot=="INVTYPE_HAND" or
			uniqueInventorySlot=="INVTYPE_FINGER" or
			(uniqueInventorySlot=="INVTYPE_TRINKET" and DGV:UserSetting(DGV_SUGGESTTRINKET)) or
			uniqueInventorySlot=="INVTYPE_CLOAK" or
			uniqueInventorySlot==INVSLOT_MAINHAND or
			uniqueInventorySlot==INVSLOT_OFFHAND 
	end
   

	local function StandardPredicate(criterion, link)
		if link then
			return IsEquipment((select(9, GetItemInfo(link))))
		end
	end

	local function CoinPredicate(criterion, item)
		for _, choice in GA.ItemChoiceIterator do
			if choice==item then return true end
		end
		return false
	end


	local function SpecFromOption(option)
		if option==WIN_CRITERIA_NONE then return end
		local activeSpecName = select(2, GetSpecializationInfo(GetSpecialization()))
		local inactiveSpec = GetSpecialization(false, false, GetActiveSpecGroup()==1 and 2 or 1)
		local inactiveSpecName
		if inactiveSpec then
			inactiveSpecName = select(2, GetSpecializationInfo(inactiveSpec))
		end
		local pvp
		local capture = option:match(L["(.*) %(PvP%)"])

		if capture then
			pvp = true
		end
		if option==WIN_CRITERIA_CURRENT or capture==WIN_CRITERIA_CURRENT then
				return SpecFromLocalizedName(activeSpecName), pvp and L["%s (PvP)"]:format(activeSpecName) or activeSpecName, pvp
		elseif option==WIN_CRITERIA_INACTIVE_SPEC or capture==WIN_CRITERIA_INACTIVE_SPEC then
			if inactiveSpecName then
				return SpecFromLocalizedName(inactiveSpecName), pvp and L["%s (PvP)"]:format(inactiveSpecName) or inactiveSpecName, pvp
			end
		else
			local capture = option:match(L["(.*) %(PvP%)"])
			if capture then
				return SpecFromLocalizedName(capture), option, true
			end
			return SpecFromLocalizedName(option), option
		end
	end

	local standardCriterionMeta = {
		__index = {
			Predicate = StandardPredicate,
			GetScore = StandardCompare,
			AdornReward = StandardRewardAdorner,
			AdornLootRoll = StandardLootRollAdorner,
			AdornEncounterJournal = StandardItemButtonAdorner,
			AdornVendorItem = StandardPooledAdorner,
			AdornBagItem = StandardPooledAdorner,
			AdornLoot = StandardItemButtonAdorner,
			SettleTie = StandardTiebreaker,
			AdornTooltip = StandardTooltipAdorner
		}
	}
	DGV.InitTable(standardCriterionMeta.__index) --subclass pool table

	local function CreateStandardCriterion()
		local table = GetCreateTable()
		return setmetatable(table, standardCriterionMeta)
	end

	local WIN_CRITERIA_COIN = "Highest Vendor Price"
	local function WinCriteriaIterator(invariant, control)
		if not control then control = 0 end

		while true do
			control = control + 1
			local option = invariant[control]
			if not option then return end
			local spec, specName, pvp
			if option==WIN_CRITERIA_COIN then
				local criterion = GetCreateTable()
				criterion.Predicate = CoinPredicate
				criterion.GetScore = CoinCompare
				criterion.AdornReward = CoinRewardAdorner
				return control, WIN_CRITERIA_COIN, criterion
			else
				spec, specName, pvp = SpecFromOption(option)
			end
			local alreadyIterated
			if spec then
				for i=1,control-1 do
					local sfo, option, pfo = SpecFromOption(invariant[i])
					if spec==sfo and pvp==pfo then
						alreadyIterated = true
						break
					end
				end
			end
			if spec and not alreadyIterated then
				local criterion = CreateStandardCriterion()
				criterion.specNum = spec
				criterion.pvp = pvp
				criterion.specName = specName
				return control, specName, criterion
			end
		end
	end

	function GA:IterateWinCriteria()
		return WinCriteriaIterator, DugisGuideViewer.chardb[DGV_GAWINCRITERIACUSTOM].options
	end

	local function ItemIsInBag(link)
		for control,iteratedItemLink in ItemIterator,ITEM_ITERATOR_SKIP_ALL_EXTERNAL do
			if iteratedItemLink==link and control.bags then
				control:Pool()
				return true
			end
			YieldAutoroutine()
		end
	end

	local function IsNewItemTooltip(scores, link, newLink)
		if newLink~=link then
			return true
		end
	end

	local function PoolScores(scores)
		if scores then
			scores:TryReleaseLifetime(PoolScores)
			for _,score in scores:IPairs() do
				if type(score)=="table" then
					score:Pool()
				end
			end
			scores:Pool()
		end
	end

	local function ProcessTooltipRoutine(tooltip, link)
		local cacheReaction = TryGetCacheReaction("ProcessTooltipRoutine")
		local scores
		if not cacheReaction then
			local reqLevel = select(5, GetItemInfo(link))
			local playerLevel = UnitLevel("player")
			if reqLevel and playerLevel and reqLevel>playerLevel and ItemIsInBag(link) then return end
			for index, option,criterion in GA:IterateWinCriteria() do
				criterion:BindToAutoroutineLifetime(tPool)
				local score, altWinner, altScore = EvaluateWinCriteron(criterion, link)
				if score then
					if not scores then
						scores = GetCreateTable():BindToAutoroutineLifetime(PoolScores)
					end
					if altWinner then
						scores[index] = GetCreateTable(score, altWinner, altScore)
					else
						scores[index] = score
					end
				end
				criterion:Pool()
			end
			if scores then
				scores:TryReleaseLifetime(PoolScores)
			end
			RegisterReaction()
				:InvokePassively()
				:WithAction(PoolScores, scores)
				:WithPredicate(IsNewItemTooltip, link)
				:SetCache("ProcessTooltipRoutine", scores)
		else
			scores = cacheReaction:UnpackCache()
		end
		if not scores then return end
		YieldAutoroutine()
		for index, option,criterion in GA:IterateWinCriteria() do
			criterion:BindToAutoroutineLifetime(tPool)
			local score, altWinner, altScore = scores[index]
			if score then
				if type(score)=="table" then
					score, altWinner, altScore = score:Unpack()
				end
				if criterion.AdornTooltip then
					criterion:AdornTooltip(tooltip, link, altWinner, score, altScore)
				else
					FallbackTooltipAdorner(tooltip, option)
				end
			end
			criterion:Pool()
		end

		Repaint(tooltip)
	end

	function ProcessTooltip_Dugis(tooltip, name, link, ...)
		if tooltip:GetName():match("ShoppingTooltip") then return end

		local cacheReaction = TryGetCacheReaction("ProcessTooltipRoutine")
		if cacheReaction then
			cacheReaction:TryInvoke(link)
			cacheReaction = TryGetCacheReaction("ProcessTooltipRoutine")
		end
		if not cacheReaction then
			if not GetRunningAutoroutine("ProcessTooltipRoutine") then
				BeginAutoroutine("ProcessTooltipRoutine", ProcessTooltipRoutine, tooltip, link)
			end
		else
			ProcessTooltipRoutine(tooltip, link)
		end
	end

	local orig_GetNumEquipmentSets = (C_EquipmentSet and C_EquipmentSet.GetNumEquipmentSets) or GetNumEquipmentSets --For 7.2.0
	local orig_GetEquipmentSetInfo = (C_EquipmentSet and C_EquipmentSet.GetEquipmentSetInfo) or GetEquipmentSetInfo
	local orig_GetItemIDs = C_EquipmentSet.GetItemIDs
	local orig_GetEquipmentSetIDs = (C_EquipmentSet and C_EquipmentSet.GetEquipmentSetIDs) or C_EquipmentSet
	local equipmentSetDataTable = {}
	local equipmentSetIdTable = {}
	setmetatable(equipmentSetIdTable, {
		__index = function(self, i)
			local link = rawget(equipmentSetDataTable, i)
			if i==INVSLOT_RANGED then return 0 end
			if i==INVSLOT_BODY or i==INVSLOT_TABARD then return EQUIPMENT_SET_IGNORED_SLOT end
			return link and DGV:GetItemIdFromLink(link) or EQUIPMENT_SET_IGNORED_SLOT, link
		end,
	})

	local levelUpReaction, activeSpecReaction, smartSetReaction, rewardShowReaction, questHideReaction, adornerParentShowReaction, equipExecutedReaction,
		lootRollShowReaction, encounterJournalUpdateReaction, vendorUpdateReaction, lootUpdateReaction, advisorBagUpdateReaction

	function GA:Load()
		TipHooker:Hook(ProcessTooltip_Dugis, "item")

		if not GA.initializedTooltipHook then
			TipHooker:RegisterCustomTooltip("item", "WorldMapTooltip")
			GA.initializedTooltipHook = true
		end

        --Outfitter bugfix
        if Outfitter then
            hooksecurefunc(Outfitter, "Item_CheckboxClicked", function()
                GA.lastOutfitterClickedTime = GetTime()
            end)
        end

		local function SmartSetShown()
			if DugisGuideViewer.armoryloaded or DugisGuideViewer.outfitterloaded or DugisGuideViewer.arkinventoryloaded then
				return false
			else
				return DGV:UserSetting(DGV_GASMARTSETTARGET)~=WIN_CRITERIA_NONE
			end
		end
        
        --GetNumEquipmentSets
        local function fn()
            if shouldUseOriginalEquipmentFunctions() then
                return orig_GetNumEquipmentSets()
            end

            local add = SmartSetShown() and 1 or 0
            return orig_GetNumEquipmentSets()+add
        end        

        if GetNumEquipmentSets then
            GetNumEquipmentSets = fn
        end
        
        if C_EquipmentSet and C_EquipmentSet.GetNumEquipmentSets then
            --For 7.2.0
             C_EquipmentSet.GetNumEquipmentSets = fn
        end

        if C_EquipmentSet and C_EquipmentSet.GetEquipmentSetIDs then
            --For 7.2.0
            C_EquipmentSet.GetEquipmentSetIDs = function()
                if shouldUseOriginalEquipmentFunctions() then
                    return orig_GetEquipmentSetIDs()
                end
            
                local res = orig_GetEquipmentSetIDs()
				
				if SmartSetShown() then
					res[#res + 1] = dugiSmartSetID
				end
                return res
            end    
        end        
        
		local function IsInOtherSlot(itemLink, slot)
			local otherSlot
			if slot==INVSLOT_TRINKET1 then
				otherSlot = INVSLOT_TRINKET2
			elseif slot==INVSLOT_TRINKET2 then
				otherSlot = INVSLOT_TRINKET1
			elseif slot==INVSLOT_FINGER1 then
				otherSlot = INVSLOT_FINGER2
			elseif slot==INVSLOT_FINGER2 then
				otherSlot = INVSLOT_FINGER1
			else return
			end
			local link = GetInventoryItemLink("player", otherSlot)
			return link and link==itemLink
		end

		local function SmartSetIsEquipped(cachedOnly)
			local result = GA:GetSpecDataTable(nil, nil, equipmentSetDataTable, nil, nil, cachedOnly)
			if not result and cachedOnly and not GetRunningAutoroutine("CacheEquipmentSetDataTableRoutine") then
				BeginAutoroutine("CacheEquipmentSetDataTableRoutine", GA.GetSpecDataTable, GA, nil, nil, equipmentSetDataTable):OnCompletion(GA.RefreshCheckIconOnSmartSet)
				return
			end
			for slot,link in nextValidSlot, true do
				local setLink = equipmentSetDataTable[slot]
				if
					setLink and
					(not link or setLink ~= link) and
					not IsInOtherSlot(setLink, slot)
				then
					return false
				end
			end
			return true
		end

		local function GetSpecIcon()
            local spec = DGV:UserSetting(DGV_GASMARTSETTARGET)
            local specIndex = SpecFromOption(spec)
            
            if specIndex == nil then
                return "Interface\\ICONS\\INV_Misc_QuestionMark"
            else
                return (select(4, GetSpecializationInfo(specIndex)))
            end
		end

		-- name, icon, setID, isEquipped, numItems, numEquipped, numInventory, numMissing, numIgnored = GetEquipmentSetInfo(index)
		-- Arguments:
		-- index - Index of an equipment set (between 1 and GetNumEquipmentSets()) (number)
		-- Returns:
		-- name - Name of the equipment set (string)
		-- icon - Path to an icon texture for the equipment set (string)
		-- setID - Internal ID number for the set (not used elsewhere in API) (number)
		-- isEquipped - If the set is equipped returns true, if not, false (boolean)
		-- numItems - Number of items in the set (number)
		-- numEquipped - Number of items in the set currently equipped (number)
		-- numInventory - Number of items from the set in current bags (number)
		-- numMissing - Number of items missing from the set (current bags) (number)
		-- numIgnored - Number of ignored slots (number)
       
        local function fn(id)
            if shouldUseOriginalEquipmentFunctions() then
                return orig_GetEquipmentSetInfo(id)
            end

			if id == dugiSmartSetID then
				local cacheReaction = TryGetCacheReaction("GetEquipmentSetInfo") --keep a cache until next update.  Some addons call this a lot.
				if cacheReaction then
					return cacheReaction:UnpackCache()
				end

				local isEquipped = SmartSetIsEquipped(true)
                 
                local index_SetId = dugiSmartSetID
                
                index_SetId = dugiSmartSetID
                
                local result = {RegisterStopwatchReaction(0):SetCache("GetEquipmentSetInfo",
					L["Dugi Smart Set"],
					GetSpecIcon(),
					index_SetId,
					isEquipped,
					16,
					isEquipped and 16 or 0,
					isEquipped and 0 or 16,
					0,
					2):UnpackCache()}
                
                
				return unpack(result)
			else
                return orig_GetEquipmentSetInfo(id)
			end
            
		end

        C_EquipmentSet.GetEquipmentSetInfo = fn

		local function GetItemForTable(uniqueInventorySlot, spec, pvp, enforceArmorSpecSubclass, uncapped, threading)
			local sfo, option, pfo = SpecFromOption(DGV:UserSetting(DGV_GASMARTSETTARGET))
			spec = spec or sfo
			pvp = pvp or pfo
			local winner, winScore, altWinner, altScore, armorSpecStatValue = GetCurrentBestInSlot(uniqueInventorySlot, spec, pvp, nil, ITEM_ITERATOR_SKIP_ALL_EXTERNAL, enforceArmorSpecSubclass, uncapped, nil, nil, threading)
			return winner, altWinner, armorSpecStatValue, (winScore or 0)+(altScore or 0)
		end


        --Prepares smart-set items list
		function GA:GetSpecDataTable(spec, pvp, preferredTable, enforceArmorSpecSubclass, uncapped, cachedOnly, notThreading)
			--DGV:DebugFormat("GetSpecDataTable 1", "spec", spec)
			local sfo, option, pfo = SpecFromOption(DGV:UserSetting(DGV_GASMARTSETTARGET))
			spec = spec or sfo
			pvp = pvp or pfo
			local cacheKey = strformat("%s%s%s%s%d%s%s%s%s", "GetSpecDataTable", pfo or "nil", option or "nil", sfo or "nil", spec, pvp and "true" or "false", tostring(preferredTable), tostring(enforceArmorSpecSubclass), uncapped and "true" or "false")
			local cachedValue = DGV.GetSpecDataTable_cache[cacheKey]
			if cachedValue then
                if preferredTable then
                    LuaUtils:AddTableToTable(cachedValue, preferredTable)
                end
				return cachedValue
			end
			if cachedOnly then return end
			local dataTable = preferredTable and wipe(preferredTable) or GetCreateTable()
			--DGV:DebugFormat("GetSpecDataTable", "dataTable", tostring(dataTable))
			dataTable.ArmorSpecStatTotal , dataTable.ScoreTotal = 0,0
			for unique,inv1,inv2 in NextUniqueInventorySlot do
				local winner, altWinner, armorSpecStatValue, score = GetItemForTable(unique, spec, pvp, enforceArmorSpecSubclass, uncapped, not notThreading)
				dataTable.ArmorSpecStatTotal = dataTable.ArmorSpecStatTotal + (armorSpecStatValue or 0)
				dataTable.ScoreTotal = dataTable.ScoreTotal + score
				dataTable[inv1] = winner
				if inv2 then
					--Preventing two unique-equipped 
					if GA.CanBeEquippedAtTheSameTime(winner, altWinner) then
						dataTable[inv2] = altWinner
					end
				end
			end
            
            DGV.GetSpecDataTable_cache[cacheKey] = dataTable
            
			return dataTable
		end

        --Underscore in the function name is used to avoid detecting "Bagnon" in the stacktrace the test function name
        function shouldUseOriginalEquipmentFunctions()
            local stack = debugstack()
            local isCalledByBagnonAddon = (string.find(stack, "Bagnon") ~= nil)
            local isCalledCargBags_NivayaAddon = (string.find(stack, "Nivaya") ~= nil)
			local isCalledByAdibags = (string.find(stack, "AdiBags") ~= nil)
			local isCalledByOutfitter = (string.find(stack, "Outfitter") ~= nil)
            return  isCalledByBagnonAddon or isCalledCargBags_NivayaAddon or isCalledByAdibags or isCalledByOutfitter
        end

		-- Returns a table listing the items in an equipment set
		-- See also Equipment Manager functions.
		-- Signature:
		-- Arguments:
		-- name - Name of an equipment set (case sensitive) (string)
		-- Returns:
		-- itemIDs - A table listing the itemIDs of the set's contents, keyed by inventoryID (table)
		--EQUIPMENT_SET_IGNORED_SLOT
        
		local function fn(setID)
            if shouldUseOriginalEquipmentFunctions() then
                return orig_GetItemIDs(setID)
            end

			if setID == L["Dugi Smart Set"] or setID == dugiSmartSetID then
				GA:GetSpecDataTable(nil, nil, equipmentSetDataTable, nil, nil, nil, true)
				return equipmentSetIdTable
			else
				return orig_GetItemIDs(setID)
			end
		end
        
        C_EquipmentSet.GetItemIDs =  fn

        
		if C_EquipmentSet and C_EquipmentSet.GetIgnoredSlots then
			local org_GetIgnoredSlots = C_EquipmentSet.GetIgnoredSlots
			C_EquipmentSet.GetIgnoredSlots = function(setID)
				if setID == dugiSmartSetID then
					return {}
				else
					return org_GetIgnoredSlots(setID)
				end
			end
		else
			function PaperDollFrame_IgnoreSlotsForSet (setName)
				if setName == L["Dugi Smart Set"] then return end
				local set = GetEquipmentSetIgnoreSlots(setName);
				for slot, ignored in pairs(set) do
					if ( ignored ) then
						PaperDollFrame_IgnoreSlot(slot)
					end
				end
			end
		end

        
		PaperDollFrame.EquipmentManagerPane:HookScript("OnUpdate", function ()
			if GA.loaded then
				for _,button in DGV.IterateScrollBox(PaperDollFrame.EquipmentManagerPane.ScrollBox) do
					if button.name==L["Dugi Smart Set"] then
						button.DeleteButton:Hide()
						button.EditButton:Hide()
					end
				end
				if PaperDollFrame.EquipmentManagerPane.selectedSetName==L["Dugi Smart Set"] then
					PaperDollFrame.EquipmentManagerPaneSaveSet:Disable()
				end
			end
		end)

		local function RunAdditionalAction(action)
			EquipmentManager_RunAction(action)
			action:Pool()
			--ClearCursor() --was preventing BoE equip
		end

		local function EquipmentChangedAction(reaction, event, slotOrElapsed, hasItem, ...)
			if equipExecutedReaction.requestedSlots then
				equipExecutedReaction.requestedSlots:Pool()
				equipExecutedReaction.requestedSlots = nil
			end
			equipExecutedReaction:Dispose()
			equipExecutedReaction = nil
		end

		local EQUIP_ITEM, UNEQUIP_ITEM, SWAP_ITEM = 1,2,3
		local function ExecuteEquip(slot, action)
			ClearCursor()
			if (slot==INVSLOT_MAINHAND or slot==INVSLOT_OFFHAND) and action.player and not action.bags then --we may not be able to swap weapons in place
				if slot==action.slot then return end --already holding
				local requestedHand = slot
				action.type = UNEQUIP_ITEM
				action.invSlot = slot;
				EquipmentManager_RunAction(action)
				ClearCursor()
				local additionalAction = GetCreateTable()
				additionalAction.player = true
				additionalAction.slot = action.slot
				additionalAction.invSlot = slot
				additionalAction.type = EQUIP_ITEM
				QueueInvocation(RunAdditionalAction, additionalAction)
				return
			else
				action.type = (GetInventoryItemID("player", slot) and SWAP_ITEM) or EQUIP_ITEM;
				action.invSlot = slot;
				EquipmentManager_RunAction(action)
				--ClearCursor() --was preventing BoE equip
			end
		end

		local function IsSecondSlot(slot)
			return slot==INVSLOT_OFFHAND or slot==INVSLOT_FINGER2 or slot==INVSLOT_TRINKET2
		end

		local function GetCopyEquipAction(action, copy)
			local copy = copy or GetCreateTable()
			copy.slot = action.slot
			copy.bag = action.bag
			copy.player = action.player
			copy.bank = action.bank
			copy.bags = action.bags
			return copy
		end

		local function FindEquip(slot, itemLink, skip)
			local lastMatch
			for control,iteratedItemLink in ItemIterator,skip do
				if itemLink==iteratedItemLink then
					if IsSecondSlot(slot) then
						lastMatch = GetCopyEquipAction(control, lastMatch)
					else
                        --Finger first slot
                        if slot == 11 then
                            --Un-equipping the old one in the second slot when the gear can be equipped only once
                            local currentItemLink = GetInventoryItemLink("player", 12)     
                            if currentItemLink then
                                local itemLinkParts = {strsplit(":", currentItemLink)}
                                local currentitemId = tonumber(itemLinkParts[2])  
                                
                                if currentitemId and GA.IsUniqueEquippedGear(currentitemId) then
                                    local action = EquipmentManager_UnequipItemInSlot(12);
                                    if action then
                                        EquipmentManager_RunAction(action)
                                    end
                                end
                            end
                        end                
                    
						ExecuteEquip(slot, control)
						control:Pool()
						return true
					end
				end
				YieldAutoroutine()
			end
			if lastMatch then
				ExecuteEquip(slot, lastMatch)
				lastMatch:Pool()
				return true
			end
		end

		local function Equip(slot, itemLink, threading)
			if itemLink~=nil and not ItemIsBanned(itemLink, nil, threading) then
				if not FindEquip(slot, itemLink, bit.bor(ITEM_ITERATOR_SKIP_EQUIPPED, ITEM_ITERATOR_SKIP_ALL_EXTERNAL)) then
					FindEquip(slot, itemLink, ITEM_ITERATOR_SKIP_ALL_EXTERNAL)
				end
			end
		end

		local function ContinueEquipRoutine(showPrompt, continueFromSlot)
			if SmartSetIsEquipped() then 
                GA.isDuringEquippingChain = false
                GA.makingForAllInProgress = false
                return
            end
            
			GA:GetSpecDataTable(nil, nil, equipmentSetDataTable)
			local diff = GetCreateTable()
			for slot,itemLink in next,equipmentSetDataTable,equipmentSetDataTable[continueFromSlot] and continueFromSlot do
				if type(slot)=="number" then
					local currentItemLink = GetInventoryItemLink("player", slot)
					if itemLink and not AreItemsTheSame(itemLink, currentItemLink, true) and not IsInOtherSlot(itemLink, slot) then
						diff:Insert(slot)
					end
				end
			end
            
            if diff:Length() == 0 then
                GA.isDuringEquippingChain = false
            end
            
            if not showPrompt then
                LuaUtils:Delay(1, function()
                    GA.isDuringEquippingChain = false
                    GA.makingForAllInProgress = false
                end)
            end
            
			local continue, showPrompt, remaining = nil, showPrompt, diff:Length()
			for _,slot in diff:IPairs() do
				local itemLink = equipmentSetDataTable[slot]
				continue, showPrompt, remaining = GA:Equip(slot,itemLink,showPrompt,remaining)
                GA.isDuringEquippingChain = false
				if not continue then break end
			end
			diff:Pool()
		end

		local function ContinueEquip(showPrompt, continueFromSlot)
			if GetRunningAutoroutine("ContinueEquipRoutine") then 
                return 
            end
            
            LuaUtils:Delay(0.3, function()
                BeginAutoroutine("ContinueEquipRoutine", ContinueEquipRoutine, showPrompt, continueFromSlot)
            end)
		end

		local function ClearCompareLines()
			if not DugisEquipPromptFrame.compare then return end
			for _,fontString in ipairs(DugisEquipPromptFrame.compare) do
				fontString:Hide()
			end
		end

		local function AddSetCompareLine(text, r, g, b, a)
			if not DugisEquipPromptFrame.compare then DugisEquipPromptFrame.compare = {} end
			local toSet
			for _,fontString in ipairs(DugisEquipPromptFrame.compare) do
				if not fontString:IsShown() then
					toSet = fontString
					break
				end
			end
			if not toSet then
				toSet = DugisEquipPromptFrame:CreateFontString(nil, "ARTWORK", "GameFontNormal")
				toSet:SetJustifyH("LEFT")
				toSet:SetJustifyV("TOP")
				tinsert(DugisEquipPromptFrame.compare, toSet)
				if #DugisEquipPromptFrame.compare==1 then
					toSet:SetPoint("TOPLEFT", DugisEquipPromptFrame.recommended, "TOPRIGHT", 15, 0)
				else
					toSet:SetPoint("TOPLEFT", DugisEquipPromptFrame.compare[#DugisEquipPromptFrame.compare-1], "BOTTOMLEFT")
				end
			end
			toSet:SetHeight(13)
			toSet:SetWidth(1000)
			toSet:SetText(text)
			local width = toSet:GetStringWidth()
			if width>170 then
				toSet:SetHeight(13+math.floor(width/160)*13)
			end
			if r then
				toSet:SetTextColor(r,g,b)
			end
			toSet:SetWidth(170)
			toSet:Show()
		end

		function GA:GetSlotBackgroundInfo(slot)
			local slotFrame = ListContains(slot,
				function(frame)
					return (GetInventorySlotInfo(strsub(frame:GetName(),10)));
				end,
				PaperDollItemsFrame:GetChildren())
			return _G[strupper(strsub(slotFrame:GetName(), 10))], slotFrame.backgroundTextureName
		end

		local tempIgnoreCache = {}
		function GA:Equip(slot, itemLink, showPrompt, remaining)
			if tContains(tempIgnoreCache, itemLink..slot) then return end --avoids nagging over and over for the same piece in the same slot without long term ban listing (reset by manual gear set equip or reload ui)
			local currentItemLink = GetInventoryItemLink("player", slot)
			remaining = remaining and remaining-1
			if currentItemLink==itemLink then
				return true, showPrompt, remaining
			end
			if --[[not currentItemLink or]] not showPrompt or not DGV:UserSetting(DGV_SHOWAUTOEQUIPPROMPT) then --commented to show prompt if slot is empty
				Equip(slot, itemLink)
				return true, showPrompt, remaining
			else
                DugisEquipPromptFrame.slot = slot
				DugisEquipPromptFrame.recommended.item = itemLink
				DugisEquipPromptFrame.recommended.title = L["Equip recommended item:"]
				DugisEquipPromptFrame.action = "EQUIP"
				DugisEquipItemHighlight:SetPoint("TOPLEFT", DugisEquipPromptFrame.recommended.itemButton, "TOPLEFT", -8, 7)
				DugisEquipItemHighlight:Show()

				DugisEquipPromptFrame.forAll:Enable()
				DugisEquipPromptFrame.forAll.text:SetTextColor(1.0, 0.82, 0)
				if remaining and remaining>0 then
					DugisEquipPromptFrame.forAll:SetChecked(false)
					DugisEquipPromptFrame.forAll.text:SetText(L["Do above for remaining %d items"]:format(remaining))
					DugisEquipPromptFrame.forAll:Show()
				else
					DugisEquipPromptFrame.forAll:Hide()
				end
				DugisEquipPromptFrame.blacklist:SetChecked(false)
				DugisEquipPromptFrame.blacklist.text:SetText(L["Add %s to ban list"]:format(itemLink))
				DugisEquipPromptFrame.blacklist.text:SetWidth(352)
				DugisEquipPromptFrame.blacklist.text:SetJustifyH("LEFT")

				ClearCompareLines()
				local statTable = GetCreateTable()
				if currentItemLink then
					DugisEquipPromptFrame.existing.item = currentItemLink
					DugisEquipPromptFrame.existing.title = L["Or keep equipped item:"]
					--C_Item.GetItemStatDelta(itemLink, currentItemLink, statTable) --original method not working 
					statTable = C_Item.GetItemStatDelta(itemLink, currentItemLink)
					AddSetCompareLine(ITEM_DELTA_DESCRIPTION)
				else
					DugisEquipPromptFrame.existing.item = nil
					DugisEquipPromptFrame.existing.title = L["Or leave slot empty:"]
					--C_Item.GetItemStats(itemLink, statTable) --original method not working 
					statTable = C_Item.GetItemStats(itemLink)
					AddSetCompareLine(L["Item has the following stats:"])
				end
                
                local name, _, _, _, _, _, _, _, _, texture = GetItemInfo(itemLink)
                  
				if DGV:UserSetting(DGV_ENABLED_GEAR_NOTIFICATIONS) then
					local notificationTitle = "Gear Upgrade Suggested"
					local notification =  DugisGuideViewer:GetNotificationByTitle(notificationTitle)
					
					DugisEquipPromptFrame.dontRemoveNotificationOnCancel = false
					
					if notification == nil then
						notification = DugisGuideViewer:AddNotification({title = notificationTitle
						, notificationType = "gear-suggestion" })
						DugisGuideViewer:ShowNotifications()   
						DugisGuideViewer.RefreshMainMenu()
					end
					
					DugisEquipPromptFrame.notificationId = notification.id
					
					if DGV:UserSetting(DGV_ALWAYS_SHOW_STANDARD_PROMPT_GEAR) then
						--Old standard prompt
						DugisEquipPromptFrame:Show()
						DugisEquipPromptFrame.dontRemoveNotificationOnCancel = true
					end
				else
					--Old standard prompt
					DugisEquipPromptFrame:Show()
                end
				DugisEquipPromptFrame.compare[1]:Hide()
				if not statTable then statTable = {} end --sometimes statTable error bad argument #1 to 'pairs' (table expected, got nil)
				for stat, value in pairs(statTable) do
					if stat~="n" and _G[stat] and type(value)=="number" then
						DugisEquipPromptFrame.compare[1]:Show()
						local color = "ff00ff00"
						if value < 0 then
							color = "ffff2020"
						end
						if mod(value, 1)==0 then
							AddSetCompareLine(L["|c%s%d|r %s"]:format(color, value, _G[stat]), 1, 1, 1) --Localization: enUS uses number-space-statname
						else
							AddSetCompareLine(L["|c%s%.1f|r %s"]:format(color, value, _G[stat]), 1, 1, 1)
						end
					end
				end
				--statTable:Pool() --original method not working
				statTable = nil
			end
		end

		local function EquipmentChangedContinueEquipPredicate(reaction, requestedSlot, event, slot, hasItem)
			return tostring(requestedSlot)==tostring(slot)
		end

		function GA.EquipmentChangedContinueEquipAction(requestedSlot)
			ContinueEquip(true, requestedSlot)
		end

		function GA:OnPromptHidden(prompt)
			prompt.action = not prompt.blacklist:GetChecked() and prompt.action
			local quit = (prompt.forAll:GetChecked() and prompt.action == "SKIP") or prompt.action == "CANCEL"
			local doTheSameForAll = prompt.forAll:GetChecked()
			if prompt.blacklist:GetChecked() then
				if not DGV.chardb.GA_Blacklist then DGV.chardb.GA_Blacklist = {} end
				DGV.chardb.GA_Blacklist[DGV:GetItemIdFromLink(prompt.recommended.item)] = true
                
                local uniqueSlot = GetDefaultUniqueInventorySlot(select(9, GetItemInfo((prompt.recommended.item))))
                if uniqueSlot then
                    DGV.GetCurrentBestInSlot_cache_v2[uniqueSlot] = {}
                end
			end
			if prompt.action == "EQUIP" then
                if doTheSameForAll then
                    GA.makingForAllInProgress = true
				end
            
				Equip(prompt.slot, prompt.recommended.item, false)
               
                GA.RefreshCheckIconOnSmartSet()
			
				if doTheSameForAll then
					ContinueEquip(false, prompt.slot)
				end
				return
			end
			if prompt.action == "SKIP" then
				tinsert(tempIgnoreCache, prompt.recommended.item..prompt.slot)
			end
			if not quit then
				ContinueEquip(not doTheSameForAll, prompt.slot)
			else
                GA.isDuringEquippingChain = false
            end
		end

		function GA:OnGearOptionClicked(button)
			if button==DugisEquipPromptFrame.recommended then
				DugisEquipPromptFrame.action="EQUIP"
			else
				DugisEquipPromptFrame.action="SKIP"
			end
		end



		function GA.AutoEquipSmartSet(clearTempIgnore, button)
			if not InCombatLockdown() then
				if DGV:UserSetting(DGV_GASMARTSETTARGET)==WIN_CRITERIA_NONE or (not GA.AutoEquipEnabled() and not button) then return end
				if clearTempIgnore then
					wipe(tempIgnoreCache)
				end
				ContinueEquip(true, nil)
			else
				DoOutOfCombat(GA.AutoEquipSmartSet, clearTempIgnore)
			end
		end

		local function NoEquipInProgress()
			return not DugisEquipPromptFrame:IsShown() and (not equipExecutedReaction or equipExecutedReaction.invoked)
		end

		--levelUpReaction = RegisterReaction("PLAYER_LEVEL_UP", GA.AutoEquipEnabled, GA.AutoEquipSmartSet, true)
		activeSpecReaction = RegisterReaction("ACTIVE_TALENT_GROUP_CHANGED", GA.AutoEquipEnabled, GA.AutoEquipSmartSet, true)

        EquipmentManager_EquipSet_org = EquipmentManager_EquipSet
        EquipmentManager_EquipSet = function(id)
            if id == dugiSmartSetID then
                if not GA.isDuringEquippingChain then
                    GA.isDuringEquippingChain = true
                    GA.AutoEquipSmartSet()
                end
            else
                EquipmentManager_EquipSet_org(id)
            end
        end
        
		if GA.AutoEquipEnabled() then 
			 if firstTimeload then 
				LuaUtils:Delay(30, function()
					GA.AutoEquipSmartSet()
					firstTimeload = nil
				end)
			else 
				GA.AutoEquipSmartSet()
			end
		end

		local function HideRewardGuidance()
			DugisCoinRewardAdornment:Hide()
			DugisGreenArrowRewardAdornment:Hide()
			DugisYellowArrowRewardAdornment:Hide()
		end

		local function EvaluateRewards()
			if QuestFrameRewardPanel:IsShown() then
				local questId = GetQuestID()
				local questLogIndex
				if questId then
					questLogIndex = C_QuestLog.GetLogIndexForQuestID(questId);
					C_QuestLog.GetSelectedQuest(questId)  --Blizzard bug? need this since GetNumQuestLogChoices or GetNumQuestChoices always returns 0 otherwise.
				end
			end

			HideRewardGuidance()
			local selectionMade
			for _, option,criterion in GA:IterateWinCriteria() do
				criterion:BindToAutoroutineLifetime(tPool)
				local winScore, winLink, winFrame, unresolvableTie
				for _, link, frame in GA.ItemChoiceIterator do
					local score = EvaluateWinCriteron(criterion, link)
					if score and (not winScore or winScore<score) then
						winScore = score
						winLink = link
						winFrame = frame
					elseif criterion.SettleTie and score and winScore==score then
						winLink = criterion:SettleTie(winLink, link)
						winFrame = winLink==link and frame or winFrame
						winScore = winLink==link and score or winScore
					elseif not criterion.SettleTie and score and winScore==score then
						unresolvableTie = true
					end
				end
				if winLink then
					if not selectionMade then

                        if Storyline_NPCFrame and Storyline_NPCFrame:IsShown() then
                            local buttonToBeHighlighted = GetStorylineButtonByRewardName(winFrame.Name:GetText())

                            if buttonToBeHighlighted then
                                if glowRewardFrame == nil then
                                    CreateFrame("Frame", "glowRewardFrame", buttonToBeHighlighted)
                                    texture = glowRewardFrame:CreateTexture()
                                    texture:SetAllPoints()
                                    texture:SetBlendMode("ADD")
                                    texture:SetTexture("Interface\\QuestFrame\\UI-QuestItemHighlight")
                                end

                                glowRewardFrame:SetWidth(256)
                                glowRewardFrame:SetHeight(64)
                                glowRewardFrame:SetFrameStrata("DIALOG")
                                buttonToBeHighlighted:SetFrameLevel(500)

                                glowRewardFrame:SetPoint("TOPLEFT", buttonToBeHighlighted, "TOPLEFT", -8, 7);

                                glowRewardFrame:Show()
                            else
                                if glowRewardFrame then
                                    glowRewardFrame:Hide()
                                end
                            end
                        end

						QueueInvocation(winFrame:GetScript("OnClick"), winFrame)
						selectionMade = true
					end
					criterion:AdornReward(winLink, winFrame)
				end
				criterion:Pool()
			end
		end

        GA.EvaluateRewards = EvaluateRewards

		local function ShouldShowQuestItems()
			local val = AmountOfShownChoises()
			return val > 0
		end

		local lastRewardFrameShow
		local function DeferredRewardFrameShow(frame, elapsed) --first QuestInfoRewardsFrame:Show gets spammed
			if lastRewardFrameShow~=elapsed then
				lastRewardFrameShow = elapsed
				InterruptAutoroutine("EvaluateRewards")
				BeginAutoroutine("EvaluateRewards", EvaluateRewards)
			end
		end
		rewardShowReaction = RegisterFunctionPathReaction(QuestInfoRewardsFrame, "Show", ShouldShowQuestItems):
			Or(RegisterFunctionPathReaction(MapQuestInfoRewardsFrame, "Show", ShouldShowQuestItems)):
			WithAction(DeferredRewardFrameShow):Defer():InvokePassively()
		questHideReaction = RegisterFunctionPathReaction(QuestInfoRewardsFrame, "Hide"):WithAction(HideRewardGuidance)
		adornerParentShowReaction = RegisterFunctionPathReaction(QuestInfoRewardsFrame, "Show"):WithAction(HideRewardGuidance)

		local function HideLootRollGuidance()
			for i=1,NUM_GROUP_LOOT_FRAMES do
				local lootFrame = _G["GroupLootFrame"..i]
				if lootFrame and lootFrame.dugisGreenArrow then
					lootFrame.dugisGreenArrow:Hide()
				end
			end
		end

		local function EvaluateLootRollRoutine()
			HideLootRollGuidance()
			for _, option,criterion in GA:IterateWinCriteria() do
				criterion:BindToAutoroutineLifetime(tPool)
				for _, link, frame in LootRollIterator do
					local score = EvaluateWinCriteron(criterion, link)
					if score and criterion.AdornLootRoll then
						criterion:AdornLootRoll(link, frame)
					end
				end
				criterion:Pool()
			end
		end

		local function EvaluateLootRoll(reaction, event)
			InterruptAutoroutine("EvaluateLootRollRoutine")
			BeginAutoroutine("EvaluateLootRollRoutine", EvaluateLootRollRoutine)
		end

		lootRollShowReaction = RegisterFunctionReaction("GroupLootContainer_AddRoll"):WithAction(EvaluateLootRoll)

		local orig_GetLootRollTimeLeft, orig_GetLootRollItemLink, orig_GetLootRollItemInfo
		local function UndoMockGroupLoot()
			DGV:DebugFormat("UndoMockGroupLoot")
			GroupLootContainer_RemoveFrame(GroupLootContainer, GroupLootFrame1)
			GetLootRollTimeLeft = orig_GetLootRollTimeLeft
			GetLootRollItemLink = orig_GetLootRollItemLink
			GetLootRollItemInfo = orig_GetLootRollItemInfo
			orig_GetLootRollTimeLeft, orig_GetLootRollItemLink, orig_GetLootRollItemInfo = nil, nil, nil
		end

		local function OverrideGetLootRollTimeLeft(durationMillis)
			orig_GetLootRollTimeLeft = GetLootRollTimeLeft
			local nowMillis = GetTime()*100
			GetLootRollTimeLeft = function()
				return durationMillis - (GetTime()*100-nowMillis)
			end
		end

		local function OverrideGetLootRollItemLink(item)
			orig_GetLootRollItemLink = GetLootRollItemLink
			GetLootRollItemLink = function() return item end
		end

		local function OverrideGetLootRollItemInfo(item)
			orig_GetLootRollItemInfo = GetLootRollItemInfo
			GetLootRollItemInfo = function()
				local itemName, itemLink, itemRarity, itemLevel, itemMinLevel, itemType, itemSubType,

					itemStackCount, itemEquipLoc, itemTexture, itemSellPrice =
					GetItemInfo(item)
				return itemTexture, itemName, 1, itemRarity, 1, 1, 1, 1
			end
		end

		--/run MockGroupLoot("", 10)
		function MockGroupLoot(item, duration)
			DGV:DebugFormat("MockGroupLoot", "item",item,"duration", duration)
			if orig_GetLootRollTimeLeft then return end
			local durationMillis = duration*100
			OverrideGetLootRollTimeLeft(durationMillis)
			OverrideGetLootRollItemLink(item)
			OverrideGetLootRollItemInfo(item)
			GroupLootContainer_AddRoll(1, durationMillis)
			RegisterStopwatchReaction(duration):Once():WithAction(UndoMockGroupLoot)
		end

		local function HideEncounterJournalGuidance()
			for _,button in GA.IterateEncounterJournal() do
				if button.dugisGreenArrow then
					button.dugisGreenArrow:Hide()
				end
				if button.dugisYellowArrow then
					button.dugisYellowArrow:Hide()
				end
			end
		end

		local function EvaluateEncounterJournalRoutine()
			HideEncounterJournalGuidance()
			-- if DGV.currentAutoroutine then
			-- DGV:DebugFormat("EvaluateEncounterJournalRoutine 1", "time", DGV.GetTicks()-DGV.currentAutoroutine.startTime)
			-- end
			if
				EncounterJournal and
				EncounterJournal:IsShown() and
				EncounterJournal.encounter.info.LootContainer:IsShown()
			then
				local greenedSlots = GetCreateTable():BindToAutoroutineLifetime(tPool)
				for slot,inv1,inv2 in NextUniqueInventorySlot do
					for criterionIndex, option,criterion in GA:IterateWinCriteria() do
						criterion:BindToAutoroutineLifetime(tPool)
						if criterion.AdornEncounterJournal then
							-- if DGV.currentAutoroutine then
							-- DGV:DebugFormat("EvaluateEncounterJournalRoutine 2", "time", DGV.GetTicks()-DGV.currentAutoroutine.startTime, "slot", slot, "criterionIndex", criterionIndex)
							-- end
							local winningItem, _, altWinner = GetCurrentBestInSlot(slot, criterion.specNum, criterion.pvp, nil, nil, nil, nil, true)
							-- if DGV.currentAutoroutine then
							-- DGV:DebugFormat("EvaluateEncounterJournalRoutine 3", "time", DGV.GetTicks()-DGV.currentAutoroutine.startTime, "slot", slot, "criterionIndex", criterionIndex)
							-- end

							for _, frame in GA.IterateEncounterJournal() do
								local link = frame.link
								if criterion:Predicate(link, unpack(criterion)) then
									if slot==INVSLOT_MAINHAND and link==altWinner then
										slot = INVSLOT_OFFHAND
										winningItem = link
									end

									local itemId = C_Item.GetItemInfoInstant(link)
									if winningItem and itemId==C_Item.GetItemInfoInstant(winningItem) then
										if frame then
											criterion:AdornEncounterJournal(link, frame, (greenedSlots[slot] and "yellow") or "green")
										end
										greenedSlots[slot] = true
									elseif altWinner and itemId==C_Item.GetItemInfoInstant(altWinner) then
										if frame then
											criterion:AdornEncounterJournal(link, frame, "yellow")
										end
									end
								end
								YieldAutoroutine()
							end
						end
						criterion:Pool()
					end
				end
				greenedSlots:Pool()
			end
		end

		local function EvaluateEncounterJournal()
			InterruptAutoroutine("EvaluateEncounterJournal")
			BeginAutoroutine("EvaluateEncounterJournal", EvaluateEncounterJournalRoutine)
		end


        local EncounterJournal_advising_initialized = false
        C_Timer.NewTicker(2, function()
			lastIsStealthed = IsStealthed()
		
            if EncounterJournal and EncounterJournal_advising_initialized == false then
              encounterJournalUpdateReaction = RegisterFunctionReaction("EncounterJournal_LootUpdate"):WithAction(EvaluateEncounterJournal)

                EncounterJournal_advising_initialized = true
            end
        end)

		local function EvaluateVendorRoutine()
			if MerchantFrame:IsShown() then
				local greenedSlots = GetCreateTable():BindToAutoroutineLifetime(tPool)
				for slot,inv1,inv2 in NextUniqueInventorySlot do
					for criterionIndex, option, criterion in GA:IterateWinCriteria() do
						criterion:BindToAutoroutineLifetime(tPool)
						if criterion.AdornVendorItem then
							local winningItem, _, altWinner = GetCurrentBestInSlot(slot, criterion.specNum, criterion.pvp, nil, nil, nil, nil, true)
							for _, link, frame in GA.VendorIterator do
								if criterion:Predicate(link, unpack(criterion)) then
									if slot==INVSLOT_MAINHAND and link==altWinner then
										slot = INVSLOT_OFFHAND
										winningItem = link
									end

									local itemId = C_Item.GetItemInfoInstant(link)
									if winningItem and itemId==C_Item.GetItemInfoInstant(winningItem) then
										if frame then
											criterion:AdornVendorItem(link, frame, (greenedSlots[slot] and "yellow") or "green")
										end
										greenedSlots[slot] = true
									elseif altWinner and itemId==C_Item.GetItemInfoInstant(altWinner) then
										if frame then
											criterion:AdornVendorItem(link, frame, "yellow")
										end
									end
								end
								YieldAutoroutine()
							end
						end
						criterion:Pool()
					end
				end
				greenedSlots:Pool()
			end
		end

		local function VendorReleasePredicate(arrowFrame)
			local itemButton = arrowFrame:GetParent()
			local merchantItem = itemButton and itemButton:GetParent()
			local merchantItemName = merchantItem and merchantItem:GetName()
			return merchantItemName and merchantItemName:match("^MerchantItem%d+")
		end
			
		local function EvaluateVendor()
			greenArrowPool:ReleaseIf(VendorReleasePredicate)
			yellowArrowPool:ReleaseIf(VendorReleasePredicate)
			InterruptAutoroutine("EvaluateVendor")
			BeginAutoroutine("EvaluateVendor", EvaluateVendorRoutine)
		end

		vendorUpdateReaction =  RegisterFunctionReaction("MerchantFrame_Update")
			:Defer()
			:WithAction(EvaluateVendor)

		--Blizzard's ContainerFrameUtil_EnumerateBagFrames leaves off the last bag as of 9/17/2024
		--So is anything that doesn't observe that bag 1 is the first bag after the backpack, but the backpack frame is ContainerFrame1
		local function EnumerateBagFrames(invariant, control)
			if not control then control = 0 end
			if control == Constants.InventoryConstants.NumBagSlots + 1 then return end
			control = control + 1
			return control, ContainerFrameContainer.ContainerFrames[control]
		end

		local function EvaluateBagItemsRoutine()
			if InCombatLockdown() then return end
			local greenedSlots = GetCreateTable():BindToAutoroutineLifetime(tPool)
			for slot,inv1,inv2 in NextUniqueInventorySlot do
				for criterionIndex, option, criterion in GA:IterateWinCriteria() do
					criterion:BindToAutoroutineLifetime(tPool)
					if criterion.AdornBagItem then
						local winningItem, _, altWinner, _ = GetCurrentBestInSlot(slot, criterion.specNum, criterion.pvp, nil, nil, nil, nil, true)
						for bagIndex, bagFrame in EnumerateBagFrames do
							for itemIndex, itemButton in bagFrame:EnumerateValidItems() do
								if itemButton then --sometimes gets itemButton nil value error
									local itemLocation = ItemLocation:CreateFromBagAndSlot(itemButton:GetBagID(), itemButton:GetID());
									if itemLocation:IsValid() then
										local link = C_Item.GetItemLink(itemLocation)
										local itemId = C_Item.GetItemID(itemLocation)
										if link then
											if criterion:Predicate(link, unpack(criterion)) then
												if slot==INVSLOT_MAINHAND and link==altWinner then
													slot = INVSLOT_OFFHAND
													winningItem = link
												end
				
												if winningItem and itemId==C_Item.GetItemInfoInstant(winningItem) then
													criterion:AdornBagItem(link, itemButton, (greenedSlots[slot] and "yellow") or "green")
													greenedSlots[slot] = true
												elseif altWinner and itemId==C_Item.GetItemInfoInstant(altWinner) then
													criterion:AdornBagItem(link, itemButton, "yellow")
												end
											end
											YieldAutoroutine()
										end
									end
								end
							end
						end
					end
					criterion:Pool()
				end
			end
			greenedSlots:Pool()
		end

		local function BagReleasePredicate(arrowFrame)
			local itemButton = arrowFrame:GetParent()
			local container = itemButton and itemButton:GetParent()
			local containerName = container and container:GetName()
			return containerName and containerName:match("^ContainerFrame%d+")
		end

		local function EvaluateBagItems()
		 	if not InCombatLockdown() then
				greenArrowPool:ReleaseIf(BagReleasePredicate)
				yellowArrowPool:ReleaseIf(BagReleasePredicate)
				DGV.InterruptAutoroutine("EvaluateBagItems")
				DGV.BeginAutoroutine("EvaluateBagItems", EvaluateBagItemsRoutine)
			end
		end

		advisorBagUpdateReaction = RegisterReaction("BAG_UPDATE")
                :Or(RegisterReaction("PLAYER_EQUIPMENT_CHANGED"))
                :Or(RegisterReaction("BANKFRAME_OPENED"))
                :WithAction(EvaluateBagItems)
                :InvokePassively()
                :Defer()
                :OutOfCombat()
        
		local function HideLootGuidance()
			for _, frame in IterateLoot() do
				if frame.dugisGreenArrow then
					frame.dugisGreenArrow:Hide()
				end
				if frame.dugisYellowArrow then
					frame.dugisYellowArrow:Hide()
				end
			end
		end

		local function EvaluateLootRoutine()
			HideLootGuidance()
			if LootFrame:IsShown() then
				local greenedSlots = GetCreateTable():BindToAutoroutineLifetime(tPool)
				for _, option,criterion in GA:IterateWinCriteria() do
					criterion:BindToAutoroutineLifetime(tPool)
					local winScore, winLink, winFrame, winSlot
					for _, frame in IterateLoot() do
						if frame:IsShown() then 
							local link = GetLootSlotLink(frame:GetSlotIndex())
							local score,_,_,uniqueInventorySlot = EvaluateWinCriteron(criterion, link)
							if score and (not winScore or winScore<score) then
								winScore = score
								winLink = link
								winFrame = frame
								winSlot = uniqueInventorySlot
							elseif criterion.SettleTie and score and winScore==score then
								winLink = criterion:SettleTie(winLink, link)
								winFrame = winLink==link and frame or winFrame
								winScore = winLink==link and score or winScore
								winSlot = uniqueInventorySlot
							end
						end
					end
					if winLink and winFrame then
						criterion:AdornLoot(winLink, winFrame, (greenedSlots[winSlot] and "yellow") or "green")
					end
					if winSlot then
						greenedSlots[winSlot] = true
					end
					criterion:Pool()
				end
				greenedSlots:Pool()
			end
		end

		local function EvaluateLoot()
			InterruptAutoroutine("EvaluateLoot")
			BeginAutoroutine("EvaluateLoot", EvaluateLootRoutine)
		end

		lootUpdateReaction =  RegisterFunctionPathReaction(LootFrame, "Open"):WithAction(EvaluateLoot)

		if DGV.Debug then
			DGV.Modules.Test:RegisterTest(
				function(suite)
					--suite.mocks
					--suite.setUp
					--suite.tearDown
					local function justReturn(arg1, arg2, arg3, arg4, arg5, arg6, ...)
						return ...
					end
					suite.testVisitCSV = function(state)
						local joined = strjoin("test", VisitCSV(justReturn, nil, nil, nil, nil, nil, nil, "1,two,3", "four,5,six"))
						local expected = "1testtwotest3testfourtest5testsix"
						DGV:ShouldEqual(expected, joined)
					end
					suite.testMathSum = function(state)
						DGV:ShouldEqual(6, math.sum(1,2,3))
					end
					suite.testItemIteratorWithArgITEM_ITERATOR_SKIP_EQUIPPEDShouldNotGivePlayerInventory = function(state)
						local playerInventoryFound = false
						for control,iteratedItemLink in ItemIterator,ITEM_ITERATOR_SKIP_EQUIPPED do
							playerInventoryFound = playerInventoryFound or (control.player and not control.bags)
						end
						DGV:Shouldnt(playerInventoryFound)
					end
					return "GearAdvisorConditionlessTests"
				end)
		end
	end

	function GA:Unload()
		TipHooker:Unhook(ProcessTooltip_Dugis, "item")
		advisorBagUpdateReaction:Dispose()
		greenArrowPool:ReleaseAll()
		yellowArrowPool:ReleaseAll()
        
		--levelUpReaction:Dispose()
		activeSpecReaction:Dispose()
		rewardShowReaction:Dispose()
		questHideReaction:Dispose()
		adornerParentShowReaction:Dispose()
		lootRollShowReaction:Dispose()
		vendorUpdateReaction:Dispose()
		lootUpdateReaction:Dispose()
		if encounterJournalUpdateReaction then
			encounterJournalUpdateReaction:Dispose()
		end
		if equipExecutedReaction then
			if equipExecutedReaction.requestedSlots then
				equipExecutedReaction.requestedSlots:Pool()
				equipExecutedReaction.requestedSlots = nil
			end
			equipExecutedReaction:Dispose()
			equipExecutedReaction = nil
		end
        
        if C_EquipmentSet then 
            --For 7.2.0
            C_EquipmentSet.GetEquipmentSetInfo = orig_GetEquipmentSetInfo
            C_EquipmentSet.GetNumEquipmentSets = orig_GetNumEquipmentSets
            C_EquipmentSet.GetItemIDs = orig_GetItemIDs
            C_EquipmentSet.GetEquipmentSetIDs = orig_GetEquipmentSetIDs
        else
            GetEquipmentSetInfo = orig_GetEquipmentSetInfo
            GetNumEquipmentSets = orig_GetNumEquipmentSets
        end
	end
end
--[[
C_Item.GetItemStats("itemLink" [, returnTable])
id, texture, checkRelic = GetInventorySlotInfo("slotName")
Arguments:

slotName - Name of an inventory slot to query (string)
AmmoSlot - Ranged ammunition slot
BackSlot - Back (cloak) slot
Bag0Slot - Backpack slot
Bag1Slot - First bag slot
Bag2Slot - Second bag slot
Bag3Slot - Third bag slot
ChestSlot - Chest slot
FeetSlot - Feet (boots) slot
Finger0Slot - First finger (ring) slot
Finger1Slot - Second finger (ring) slot
HandsSlot - Hand (gloves) slot
HeadSlot - Head (helmet) slot
LegsSlot - Legs (pants) slot
MainHandSlot - Main hand weapon slot
NeckSlot - Necklace slot
RangedSlot - Ranged weapon or relic slot
SecondaryHandSlot - Off-hand (weapon, shield, or held item) slot
ShirtSlot - Shirt slot
ShoulderSlot - Shoulder slot
TabardSlot - Tabard slot
Trinket0Slot - First trinket slot
Trinket1Slot - Second trinket slot
WaistSlot - Waist (belt) slot
WristSlot - Wrist (bracers) slot]]

--StatLogic:GetEffectFromRating same as GetCombatRatingBonus
--GetExpertise includes racial bonuses, StatLogic:GetEffectFromRating does not.
--LibStatLogic does not mod spell hit/expertise based upon racial weapon specializations.
--GetHitModifier, GetSpellHitModifier, and GetExpertise perform this sort of function, but only apply to equipped items, current level, etc.

--[[function GetMeleeMissChance(levelOffset, special)
    if (levelOffset < 0 or levelOffset > 3) then
        return 0;
    end
    local chance = BASE_MISS_CHANCE_PHYSICAL[levelOffset];
    chance = chance - GetCombatRatingBonus(CR_HIT_MELEE) - GetHitModifier();
    if (IsDualWielding() and not special) then
        chance = chance + DUAL_WIELD_HIT_PENALTY;
    end
    if (chance < 0) then
        chance = 0;
    elseif (chance > 100) then
        chance = 100;
    end
    return chance;
end

function GetRangedMissChance(levelOffset, special)
    if (levelOffset < 0 or levelOffset > 3) then
        return 0;
    end
    local chance = BASE_MISS_CHANCE_PHYSICAL[levelOffset];
    chance = chance - GetCombatRatingBonus(CR_HIT_RANGED) - GetHitModifier();
    if (chance < 0) then
        chance = 0;
    elseif (chance > 100) then
        chance = 100;
    end
    return chance;
end

function GetSpellMissChance(levelOffset, special)
    if (levelOffset < 0 or levelOffset > 3) then
        return 0;
    end
    local chance = BASE_MISS_CHANCE_SPELL[levelOffset];
    chance = chance - GetCombatRatingBonus(CR_HIT_SPELL) - GetSpellHitModifier();
    if (chance < 0) then
        chance = 0;
    elseif (chance > 100) then
        chance = 100;
    end
    return chance;
end

function GetEnemyDodgeChance(levelOffset)
    if (levelOffset < 0 or levelOffset > 3) then
        return 0;
    end
    local chance = BASE_ENEMY_DODGE_CHANCE[levelOffset];
    local offhandChance = BASE_ENEMY_DODGE_CHANCE[levelOffset];
    local rangedChance = BASE_ENEMY_DODGE_CHANCE[levelOffset];
    local expertisePct, offhandExpertisePct, rangedExpertisePct = GetExpertise();
    chance = chance - expertisePct;
    offhandChance = offhandChance - offhandExpertisePct;
    rangedChance = rangedChance - rangedExpertisePct;
    if (chance < 0) then
        chance = 0;
    elseif (chance > 100) then
        chance = 100;
    end
    if (offhandChance < 0) then
        offhandChance = 0;
    elseif (offhandChance > 100) then
        offhandChance = 100;
    end
    if (rangedChance < 0) then
        rangedChance = 0;
    elseif (rangedChance > 100) then
        rangedChance = 100;
    end
    return chance, offhandChance, rangedChance;
end

function GetEnemyParryChance(levelOffset)
    if (levelOffset < 0 or levelOffset > 3) then
        return 0;
    end
    local chance = BASE_ENEMY_PARRY_CHANCE[levelOffset];
    local offhandChance = BASE_ENEMY_PARRY_CHANCE[levelOffset];
    local expertisePct, offhandExpertisePct = GetExpertise();
    local mainhandDodge = BASE_ENEMY_DODGE_CHANCE[levelOffset];
    local offhandDodge = BASE_ENEMY_DODGE_CHANCE[levelOffset];

    expertisePct = expertisePct - mainhandDodge;
    if ( expertisePct < 0 ) then
        expertisePct = 0;
    end
    chance = chance - expertisePct;
    if (chance < 0) then
        chance = 0;
    elseif (chance > 100) then
        chance = 100;
    end

    offhandExpertisePct = offhandExpertisePct - offhandDodge;
    if ( offhandExpertisePct < 0 ) then
        offhandExpertisePct = 0;
    end
    offhandChance = offhandChance - offhandExpertisePct;
    if (offhandChance < 0) then
        offhandChance = 0;
    elseif (offhandChance > 100) then
        offhandChance = 100;
    end

    return chance, offhandChance;
end]]


--[[
from http://www.wowace.com/paste/832.txt
ITEM_MOD_AGILITY = "%c%d Agility"
ITEM_MOD_AGILITY_SHORT = "Agility"
ITEM_MOD_ARMOR_PENETRATION_RATING = "Increases your armor penetration rating by %d."
ITEM_MOD_ARMOR_PENETRATION_RATING_SHORT = "Armor Penetration Rating"
ITEM_MOD_ATTACK_POWER = "Increases attack power by %d."
ITEM_MOD_ATTACK_POWER_SHORT = "Attack Power"
ITEM_MOD_BLOCK_RATING = "Increases your shield block rating by %d."
ITEM_MOD_BLOCK_RATING_SHORT = "Block Rating"
ITEM_MOD_BLOCK_VALUE = "Increases your shield value by %d."
ITEM_MOD_BLOCK_VALUE_SHORT = "Block Value"
ITEM_MOD_CRIT_MELEE_RATING = "Improves melee critical strike rating by %d."
ITEM_MOD_CRIT_MELEE_RATING_SHORT = "Critical Strike Rating (Melee)"
ITEM_MOD_CRIT_RANGED_RATING = "Improves ranged critical strike rating by %d."
ITEM_MOD_CRIT_RANGED_RATING_SHORT = "Critical Strike Rating (Ranged)"
ITEM_MOD_CRIT_RATING = "Improves critical strike rating by %d."
ITEM_MOD_CRIT_RATING_SHORT = "Critical Strike Rating"
ITEM_MOD_CRIT_SPELL_RATING = "Improves spell critical strike rating by %d."
ITEM_MOD_CRIT_SPELL_RATING_SHORT = "Critical Strike Rating (Spell)"
ITEM_MOD_CRIT_TAKEN_MELEE_RATING = "Improves melee critical avoidance rating by %d."
ITEM_MOD_CRIT_TAKEN_MELEE_RATING_SHORT = "Critical Strike Avoidance Rating (Melee)"
ITEM_MOD_CRIT_TAKEN_RANGED_RATING = "Improves ranged critical avoidance rating by %d."
ITEM_MOD_CRIT_TAKEN_RANGED_RATING_SHORT = "Critical Strike Avoidance Rating (Ranged)"
ITEM_MOD_CRIT_TAKEN_RATING = "Improves critical avoidance rating by %d."
ITEM_MOD_CRIT_TAKEN_RATING_SHORT = "Critical Strike Avoidance Rating"
ITEM_MOD_CRIT_TAKEN_SPELL_RATING = "Improves spell critical avoidance rating by %d."
ITEM_MOD_CRIT_TAKEN_SPELL_RATING_SHORT = "Critical Strike Avoidance Rating (Spell)"
ITEM_MOD_DAMAGE_PER_SECOND_SHORT = "Damage Per Second"
ITEM_MOD_DEFENSE_SKILL_RATING = "Increases defense rating by %d."
ITEM_MOD_DEFENSE_SKILL_RATING_SHORT = "Defense Rating"
ITEM_MOD_DODGE_RATING = "Increases your dodge rating by %d."
ITEM_MOD_DODGE_RATING_SHORT = "Dodge Rating"
ITEM_MOD_EXPERTISE_RATING = "Increases your expertise rating by %d."
ITEM_MOD_EXPERTISE_RATING_SHORT = "Expertise Rating"
ITEM_MOD_FERAL_ATTACK_POWER = "Increases attack power by %d in Cat, Bear, Dire Bear, and Moonkin forms only."
ITEM_MOD_FERAL_ATTACK_POWER_SHORT = "Attack Power In Forms"
ITEM_MOD_HASTE_MELEE_RATING = "Improves melee haste rating by %d."
ITEM_MOD_HASTE_MELEE_RATING_SHORT = "Haste Rating (Melee)"
ITEM_MOD_HASTE_RANGED_RATING = "Improves ranged haste rating by %d."
ITEM_MOD_HASTE_RANGED_RATING_SHORT = "Haste Rating (Ranged)"
ITEM_MOD_HASTE_RATING = "Improves haste rating by %d."
ITEM_MOD_HASTE_RATING_SHORT = "Haste Rating"
ITEM_MOD_HASTE_SPELL_RATING = "Improves spell haste rating by %d."
ITEM_MOD_HASTE_SPELL_RATING_SHORT = "Haste Rating (Spell)"
ITEM_MOD_HEALTH = "%c%d Health"
ITEM_MOD_HEALTH_REGEN_SHORT = "Health Per 5 Sec."
ITEM_MOD_HEALTH_REGENERATION = "Restores %d health per 5 sec."
ITEM_MOD_HEALTH_REGENERATION_SHORT = "Health Regeneration"
ITEM_MOD_HEALTH_SHORT = "Health"
ITEM_MOD_HIT_MELEE_RATING = "Improves melee hit rating by %d."
ITEM_MOD_HIT_MELEE_RATING_SHORT = "Hit Rating (Melee)"
ITEM_MOD_HIT_RANGED_RATING = "Improves ranged hit rating by %d."
ITEM_MOD_HIT_RANGED_RATING_SHORT = "Hit Rating (Ranged)"
ITEM_MOD_HIT_RATING = "Improves hit rating by %d."
ITEM_MOD_HIT_RATING_SHORT = "Hit Rating"
ITEM_MOD_HIT_SPELL_RATING = "Improves spell hit rating by %d."
ITEM_MOD_HIT_SPELL_RATING_SHORT = "Hit Rating (Spell)"
ITEM_MOD_HIT_TAKEN_MELEE_RATING = "Improves melee hit avoidance rating by %d."
ITEM_MOD_HIT_TAKEN_MELEE_RATING_SHORT = "Hit Avoidance Rating (Melee)"
ITEM_MOD_HIT_TAKEN_RANGED_RATING = "Improves ranged hit avoidance rating by %d."
ITEM_MOD_HIT_TAKEN_RANGED_RATING_SHORT = "Hit Avoidance Rating (Ranged)"
ITEM_MOD_HIT_TAKEN_RATING = "Improves hit avoidance rating by %d."
ITEM_MOD_HIT_TAKEN_RATING_SHORT = "Hit Avoidance Rating"
ITEM_MOD_HIT_TAKEN_SPELL_RATING = "Improves spell hit avoidance rating by %d."
ITEM_MOD_HIT_TAKEN_SPELL_RATING_SHORT = "Hit Avoidance Rating (Spell)"
ITEM_MOD_INTELLECT = "%c%d Intellect"
ITEM_MOD_INTELLECT_SHORT = "Intellect"
ITEM_MOD_MANA = "%c%d Mana"
ITEM_MOD_MANA_REGENERATION = "Restores %d mana per 5 sec."
ITEM_MOD_MANA_REGENERATION_SHORT = "Mana Regeneration"
ITEM_MOD_MANA_SHORT = "Mana"
ITEM_MOD_MELEE_ATTACK_POWER_SHORT = "Melee Attack Power"
ITEM_MOD_PARRY_RATING = "Increases your parry rating by %d."
ITEM_MOD_PARRY_RATING_SHORT = "Parry Rating"
ITEM_MOD_POWER_REGEN0_SHORT = "Mana Per 5 Sec."
ITEM_MOD_POWER_REGEN1_SHORT = "Rage Per 5 Sec."
ITEM_MOD_POWER_REGEN2_SHORT = "Focus Per 5 Sec."
ITEM_MOD_POWER_REGEN3_SHORT = "Energy Per 5 Sec."
ITEM_MOD_POWER_REGEN4_SHORT = "Happiness Per 5 Sec."
ITEM_MOD_POWER_REGEN5_SHORT = "Runes Per 5 Sec."
ITEM_MOD_POWER_REGEN6_SHORT = "Runic Power Per 5 Sec."
ITEM_MOD_RANGED_ATTACK_POWER = "Increases ranged attack power by %d."
ITEM_MOD_RANGED_ATTACK_POWER_SHORT = "Ranged Attack Power"
ITEM_MOD_RESILIENCE_RATING = "Improves your resilience rating by %d."
ITEM_MOD_RESILIENCE_RATING_SHORT = "Resilience Rating"
ITEM_MOD_SPELL_DAMAGE_DONE = "Increases damage done by magical spells and effects by up to %d."
ITEM_MOD_SPELL_DAMAGE_DONE_SHORT = "Bonus Damage"
ITEM_MOD_SPELL_HEALING_DONE = "Increases healing done by magical spells and effects by up to %d."
ITEM_MOD_SPELL_HEALING_DONE_SHORT = "Bonus Healing"
ITEM_MOD_SPELL_PENETRATION = "Increases spell penetration by %d."
ITEM_MOD_SPELL_PENETRATION_SHORT = "Spell Penetration"
ITEM_MOD_SPELL_POWER = "Increases spell power by %d."
ITEM_MOD_SPELL_POWER_SHORT = "Spell Power"
ITEM_MOD_SPIRIT = "%c%d Spirit"
ITEM_MOD_SPIRIT_SHORT = "Spirit"
ITEM_MOD_STAMINA = "%c%d Stamina"
ITEM_MOD_STAMINA_SHORT = "Stamina"
ITEM_MOD_STRENGTH = "%c%d Strength"
ITEM_MOD_STRENGTH_SHORT = "Strength"
EMPTY_SOCKET_META = "Meta Socket"
EMPTY_SOCKET = "Level %d Socket"
EMPTY_SOCKET_RED = "Red Socket"
EMPTY_SOCKET_BLUE = "Blue Socket"
EMPTY_SOCKET_YELLOW = "Yellow Socket"
EMPTY_SOCKET_NO_COLOR = "Prismatic Socket"

RESISTANCE_NONE = "None"
RESISTANCE_TYPE0 = "armor"
RESISTANCE_TYPE1 = "holy"
RESISTANCE_TYPE2 = "fire"
RESISTANCE_TYPE3 = "nature"
RESISTANCE_TYPE4 = "frost"
RESISTANCE_TYPE5 = "shadow"
RESISTANCE_TYPE6 = "arcane"

RESISTANCE0_NAME = "Armor"
RESISTANCE1_NAME = "Holy Resistance"
RESISTANCE2_NAME = "Fire Resistance"
RESISTANCE3_NAME = "Nature Resistance"
RESISTANCE4_NAME = "Frost Resistance"
RESISTANCE5_NAME = "Shadow Resistance"
RESISTANCE6_NAME = "Arcane Resistance"]]

--[[
from a LibStatLogic L.StatIDLookup dump
["StatIDLookup"] = {
		["Critical Strike (Spell)"] = {
			"SPELL_CRIT_RATING", -- [1]

		},
		["Scope (Damage)"] = {
			"RANGED_DMG", -- [1]
		},
		["% Shield Block Value"] = {
			"MOD_BLOCK_VALUE", -- [1]
		},
		["Increases the damage done by Fire spells and effects"] = {
			"FIRE_SPELL_DMG", -- [1]
		},
		["Improves ranged critical strike"] = {
			"RANGED_CRIT_RATING", -- [1]
		},
		["Increases your critical strike"] = {
			"MELEE_CRIT_RATING", -- [1]
			"RANGED_CRIT_RATING", -- [2]
			"SPELL_CRIT_RATING", -- [3]
		},
		["Shadow and Frost Spell Power"] = {
			"SHADOW_SPELL_DMG", -- [1]
			"FROST_SPELL_DMG", -- [2]
		},
		["Critical Strike (Ranged)"] = {
			"RANGED_CRIT_RATING", -- [1]
		},
		["increases your hit"] = {
			"MELEE_HIT_RATING", -- [1]
			"RANGED_HIT_RATING", -- [2]
			"SPELL_HIT_RATING", -- [3]
		},
		["Hit Avoidance (Melee)"] = {
			"MELEE_HIT_AVOID_RATING", -- [1]
		},
		["Damage"] = {
			"SPELL_DMG", -- [1]
		},
		["improves spell hit"] = {
			"SPELL_HIT_RATING", -- [1]
		},
		["increases damage done to undead by magical spells and effects.  it also allows the acquisition of scourgestones on behalf of the argent dawn"] = {
			"SPELL_DMG_UNDEAD", -- [1]
		},
		["Frost Resistance"] = {
			"FROST_RES", -- [1]
		},
		["Improves melee hit"] = {
			"MELEE_HIT_RATING", -- [1]
		},
		["mining"] = {
			"MINING", -- [1]
		},
		["Hit (Ranged)"] = {
			"RANGED_HIT_RATING", -- [1]
		},
		["increases damage done to undead by magical spells and effects"] = {
			"SPELL_DMG_UNDEAD", -- [1]
		},
		["Improves spell critical avoidance"] = {
			"SPELL_CRIT_AVOID_RATING", -- [1]
		},
		["critical strike (melee)"] = {
			"MELEE_CRIT_RATING", -- [1]
		},
		["cooking skill increased"] = {
			"COOKING", -- [1]
		},
		["shadow spell damage"] = {
			"SHADOW_SPELL_DMG", -- [1]
		},
		["improves melee critical strike"] = {
			"MELEE_CRIT_RATING", -- [1]
		},
		["MP"] = {
			"MANA", -- [1]
		},
		["improves ranged critical strike"] = {
			"RANGED_CRIT_RATING", -- [1]
		},
		["Increases your armor penetration"] = {
			"ARMOR_PENETRATION_RATING", -- [1]
		},
		["skinning; does not need to be equipped"] = {
			"SKINNING", -- [1]
		},
		["experience gained from killing monsters and completing quests increased%"] = false,
		["mana regeneration"] = {
			"COMBAT_MANA_REGEN", -- [1]
		},
		["Healing and Spell Damage"] = {
			"SPELL_DMG", -- [1]
			"HEAL", -- [2]
		},
		["increases attack powerwhen fighting undead.  it also allows the acquisition of scourgestones on behalf of the argent dawn"] = {
			"AP_UNDEAD", -- [1]
		},
		["Holy Damage"] = {
			"HOLY_SPELL_DMG", -- [1]
		},
		["Stamina"] = {
			"STA", -- [1]
		},
		["HP"] = {
			"HEALTH", -- [1]
		},
		["Attack Power versus Undead"] = {
			"AP_UNDEAD", -- [1]
		},
		["hit avoidance (spell)"] = {
			"SPELL_HIT_AVOID_RATING", -- [1]
		},
		["Shadow Resistance"] = {
			"SHADOW_RES", -- [1]
		},
		["Weapon Damage"] = {
			"MELEE_DMG", -- [1]
		},
		["Spirit"] = {
			"SPI", -- [1]
		},
		["increases damage done by arcane spells and effects"] = {
			"ARCANE_SPELL_DMG", -- [1]
		},
		["Increases the damage done by Frost spells and effects"] = {
			"FROST_SPELL_DMG", -- [1]
		},
		["Haste (Ranged)"] = {
			"RANGED_HASTE_RATING", -- [1]
		},
		["improves spell hit avoidance"] = {
			"SPELL_HIT_AVOID_RATING", -- [1]
		},
		["Health"] = {
			"HEALTH", -- [1]
		},
		["Shadow Spell Damage"] = {
			"SHADOW_SPELL_DMG", -- [1]
		},
		["stamina"] = {
			"STA", -- [1]
		},
		["increases the damage dealt by your crusader strike ability%"] = false,
		["fire spell damage"] = {
			"FIRE_SPELL_DMG", -- [1]
		},
		["hit (spell)"] = {
			"SPELL_HIT_RATING", -- [1]
		},
		["Spell Damage"] = {
			"SPELL_DMG", -- [1]
		},
		["Attack Power In Forms"] = {
			"FERAL_AP", -- [1]
		},
		["mp"] = {
			"MANA", -- [1]
		},
		["Block"] = {
			"BLOCK_RATING", -- [1]
		},
		["Spell Power"] = {
			"SPELL_DMG", -- [1]
			"HEAL", -- [2]
		},
		["Attack Power when fighting Undead"] = {
			"AP_UNDEAD", -- [1]
		},
		["Improves spell hit"] = {
			"SPELL_HIT_RATING", -- [1]
		},
		["Nature Spell Damage"] = {
			"NATURE_SPELL_DMG", -- [1]
		},
		["Mining; does not need to be equipped"] = {
			"MINING", -- [1]
		},
		["Mining"] = {
			"MINING", -- [1]
		},
		["shadow resist"] = {
			"SHADOW_RES", -- [1]
		},
		["Improves spell hit avoidance"] = {
			"SPELL_HIT_AVOID_RATING", -- [1]
		},
		["health"] = {
			"HEALTH", -- [1]
		},
		["Cooking skill increased"] = {
			"COOKING", -- [1]
		},
		[" to All Resistances"] = {
			"ARCANE_RES", -- [1]
			"FIRE_RES", -- [2]
			"FROST_RES", -- [3]
			"NATURE_RES", -- [4]
			"SHADOW_RES", -- [5]
		},
		["Increases damage done to Undead by magical spells and effects.  It also allows the acquisition of Scourgestones on behalf of the Argent Dawn"] = {
			"SPELL_DMG_UNDEAD", -- [1]
		},
		["Mana"] = {
			"MANA", -- [1]
		},
		["hit (ranged)"] = {
			"RANGED_HIT_RATING", -- [1]
		},
		["Hit (Melee)"] = {
			"MELEE_HIT_RATING", -- [1]
		},
		["Expertise"] = {
			"EXPERTISE_RATING", -- [1]
		},
		["Improves critical avoidance"] = {
			"MELEE_CRIT_AVOID_RATING", -- [1]
			"RANGED_CRIT_AVOID_RATING", -- [2]
			"SPELL_CRIT_AVOID_RATING", -- [3]
		},
		["dodge"] = {
			"DODGE_RATING", -- [1]
		},
		["block"] = {
			"BLOCK_RATING", -- [1]
		},
		["to"] = false,
		["Improves ranged haste"] = {
			"RANGED_HASTE_RATING", -- [1]
		},
		["fire resist"] = {
			"FIRE_RES", -- [1]
		},
		["increases attack powerin cat, bear, dire bear, and moonkin forms only"] = {
			"FERAL_AP", -- [1]
		},
		["increases your pvp power"] = {
			"PVP_POWER", -- [1]
		},
		["frost resistance"] = {
			"FROST_RES", -- [1]
		},
		["skinning skill increased"] = {
			"SKINNING", -- [1]
		},
		["improves critical avoidance"] = {
			"MELEE_CRIT_AVOID_RATING", -- [1]
			"RANGED_CRIT_AVOID_RATING", -- [2]
			"SPELL_CRIT_AVOID_RATING", -- [3]
		},
		["Dodge"] = {
			"DODGE_RATING", -- [1]
		},
		["Fire Resist"] = {
			"FIRE_RES", -- [1]
		},
		["spell damage"] = {
			"SPELL_DMG", -- [1]
		},
		["Arcane Resist"] = {
			"ARCANE_RES", -- [1]
		},
		["increases the damage done by arcane spells and effects"] = {
			"ARCANE_SPELL_DMG", -- [1]
		},
		["Intellect"] = {
			"INT", -- [1]
		},
		["increases your pvp resilience"] = {
			"RESILIENCE_RATING", -- [1]
		},
		["Haste (Spell)"] = {
			"SPELL_HASTE_RATING", -- [1]
		},
		["increases ranged attack power"] = {
			"RANGED_AP", -- [1]
		},
		["Nature Damage"] = {
			"NATURE_SPELL_DMG", -- [1]
		},
		["Skinning skill increased"] = {
			"SKINNING", -- [1]
		},
		["Increases attack powerwhen fighting Demons"] = {
			"AP_DEMON", -- [1]
		},
		["Increases attack powerwhen fighting Undead"] = {
			"AP_UNDEAD", -- [1]
		},
		["shadow and frost spell power"] = {
			"SHADOW_SPELL_DMG", -- [1]
			"FROST_SPELL_DMG", -- [2]
		},
		["Increases damage done by Fire spells and effects"] = {
			"FIRE_SPELL_DMG", -- [1]
		},
		["Shadow resistance"] = {
			"SHADOW_RES", -- [1]
		},
		["all resistances"] = {
			"ARCANE_RES", -- [1]
			"FIRE_RES", -- [2]
			"FROST_RES", -- [3]
			"NATURE_RES", -- [4]
			"SHADOW_RES", -- [5]
		},
		["Improves ranged critical avoidance"] = {
			"RANGED_CRIT_AVOID_RATING", -- [1]
		},
		["increases your expertise"] = {
			"EXPERTISE_RATING", -- [1]
		},
		["Critical Strike"] = {
			"MELEE_CRIT_RATING", -- [1]
			"RANGED_CRIT_RATING", -- [2]
			"SPELL_CRIT_RATING", -- [3]
		},
		["intellect"] = {
			"INT", -- [1]
		},
		["Herbalism"] = {
			"HERBALISM", -- [1]
		},
		["Increases ranged attack power"] = {
			"RANGED_AP", -- [1]
		},
		["nature damage"] = {

			"NATURE_SPELL_DMG", -- [1]
		},
		["scope (critical strike rating)"] = {
			"RANGED_CRIT_RATING", -- [1]
		},
		["attack power"] = {
			"AP", -- [1]
		},
		["Armor"] = {
			"BONUS_ARMOR", -- [1]
		},
		["increases damage done by holy spells and effects"] = {
			"HOLY_SPELL_DMG", -- [1]
		},
		["Increases your PvP power"] = {
			"PVP_POWER", -- [1]
		},
		["Skinning"] = {
			"SKINNING", -- [1]
		},
		["Fire Damage"] = {
			"FIRE_SPELL_DMG", -- [1]
		},
		["Improves ranged hit avoidance"] = {
			"RANGED_HIT_AVOID_RATING", -- [1]
		},
		["Attack Power"] = {
			"AP", -- [1]
		},
		["Arcane and Fire Spell Power"] = {
			"ARCANE_SPELL_DMG", -- [1]
			"FIRE_SPELL_DMG", -- [2]
		},
		["frost damage"] = {
			"FROST_SPELL_DMG", -- [1]
		},
		["Arcane Damage"] = {
			"ARCANE_SPELL_DMG", -- [1]
		},
		["Hit Avoidance (Spell)"] = {
			"SPELL_HIT_AVOID_RATING", -- [1]
		},
		["Critical Strike (Melee)"] = {
			"MELEE_CRIT_RATING", -- [1]
		},
		["Increases the damage dealt by your Crusader Strike ability%"] = false,
		["holy resistance"] = {
			"HOLY_RES", -- [1]
		},
		["Scope (Critical Strike Rating)"] = {
			"RANGED_CRIT_RATING", -- [1]
		},
		["Critical Strike Avoidance"] = {
			"MELEE_CRIT_AVOID_RATING", -- [1]
			"RANGED_CRIT_AVOID_RATING", -- [2]
			"SPELL_CRIT_AVOID_RATING", -- [3]
		},
		["haste"] = {
			"MELEE_HASTE_RATING", -- [1]
			"RANGED_HASTE_RATING", -- [2]
			"SPELL_HASTE_RATING", -- [3]
		},
		["attack power in forms"] = {
			"FERAL_AP", -- [1]
		},
		["holy damage"] = {
			"HOLY_SPELL_DMG", -- [1]
		},
		["Frost Resist"] = {
			"FROST_RES", -- [1]
		},
		["Increases your mastery"] = {
			"MASTERY_RATING", -- [1]
		},
		["Improves hit avoidance"] = {
			"MELEE_HIT_AVOID_RATING", -- [1]
			"RANGED_HIT_AVOID_RATING", -- [2]
			"SPELL_HIT_AVOID_RATING", -- [3]
		},
		["increases your haste"] = {
			"MELEE_HASTE_RATING", -- [1]
			"RANGED_HASTE_RATING", -- [2]
			"SPELL_HASTE_RATING", -- [3]
		},
		[" to all resistances"] = {
			"ARCANE_RES", -- [1]
			"FIRE_RES", -- [2]
			"FROST_RES", -- [3]
			"NATURE_RES", -- [4]
			"SHADOW_RES", -- [5]
		},
		["nature resistance"] = {
			"NATURE_RES", -- [1]
		},
		["pvp resilience"] = {
			"RESILIENCE_RATING", -- [1]
		},
		["Nature Resist"] = {
			"NATURE_RES", -- [1]
		},
		["Increases ranged attack speed"] = false,
		["pvp power"] = {
			"PVP_POWER", -- [1]
		},
		["Damage Spells"] = {
			"SPELL_DMG", -- [1]
		},
		["increases your mastery"] = {
			"MASTERY_RATING", -- [1]
		},
		["% threat"] = {
			"MOD_THREAT", -- [1]
		},
		["Increases your PvP resilience"] = {
			"RESILIENCE_RATING", -- [1]
		},
		["increases healing"] = {
			"HEAL", -- [1]
		},
		["increases defense"] = {
			"DEFENSE_RATING", -- [1]
		},
		["Increases the Holy damage of your Judgments"] = false,
		["Increases your haste"] = {
			"MELEE_HASTE_RATING", -- [1]
			"RANGED_HASTE_RATING", -- [2]
			"SPELL_HASTE_RATING", -- [3]
		},
		["Healing"] = {
			"HEAL", -- [1]
		},
		["Increases damage done by Holy spells and effects"] = {
			"HOLY_SPELL_DMG", -- [1]
		},
		["Increases damage done by Frost spells and effects"] = {
			"FROST_SPELL_DMG", -- [1]
		},
		["increases the damage done by fire spells and effects"] = {
			"FIRE_SPELL_DMG", -- [1]
		},
		["shadow resistance"] = {
			"SHADOW_RES", -- [1]
		},
		["weapon damage"] = {
			"MELEE_DMG", -- [1]
		},
		["attack power when fighting undead"] = {
			"AP_UNDEAD", -- [1]
		},
		["Mastery"] = {
			"MASTERY_RATING", -- [1]
		},
		["Improves ranged hit"] = {
			"RANGED_HIT_RATING", -- [1]
		},
		["Increases your expertise"] = {
			"EXPERTISE_RATING", -- [1]
		},
		["Increases spell power"] = {
			"SPELL_DMG", -- [1]
			"HEAL", -- [2]
		},
		["critical strike avoidance (ranged)"] = {
			"RANGED_CRIT_AVOID_RATING", -- [1]
		},
		["hit avoidance (ranged)"] = {
			"RANGED_HIT_AVOID_RATING", -- [1]
		},
		["Improves spell haste"] = {
			"SPELL_HASTE_RATING", -- [1]
		},
		["increases ranged attack speed"] = false,
		["increases your healing"] = {
			"HEAL", -- [1]
		},
		["hp"] = {
			"HEALTH", -- [1]
		},
		["expertise"] = {
			"EXPERTISE_RATING", -- [1]
		},
		["spell healing"] = {
			"HEAL", -- [1]
		},
		["ranged attack power"] = {
			"RANGED_AP", -- [1]
		},
		["Agility"] = {
			"AGI", -- [1]
		},
		["Increases your parry"] = {
			"PARRY_RATING", -- [1]
		},
		["damage"] = {
			"SPELL_DMG", -- [1]
		},
		["fishing skill increased"] = {
			"FISHING", -- [1]
		},
		["damage spells"] = {
			"SPELL_DMG", -- [1]
		},
		["attack power versus undead"] = {
			"AP_UNDEAD", -- [1]
		},
		["critical strike avoidance"] = {
			"MELEE_CRIT_AVOID_RATING", -- [1]
			"RANGED_CRIT_AVOID_RATING", -- [2]
			"SPELL_CRIT_AVOID_RATING", -- [3]
		},
		["improves melee hit"] = {
			"MELEE_HIT_RATING", -- [1]
		},
		["critical strike (ranged)"] = {
			"RANGED_CRIT_RATING", -- [1]
		},
		["Increases attack power"] = {
			"AP", -- [1]
		},
		["hit avoidance (melee)"] = {
			"MELEE_HIT_AVOID_RATING", -- [1]
		},
		["agility"] = {
			"AGI", -- [1]
		},
		["healing"] = {
			"HEAL", -- [1]
		},
		["improves spell critical avoidance"] = {
			"SPELL_CRIT_AVOID_RATING", -- [1]
		},
		["experience gained is increased%"] = false,
		["increases the damage done by shadow spells and effects"] = {
			"SHADOW_SPELL_DMG", -- [1]
		},
		["Increases your dodge"] = {
			"DODGE_RATING", -- [1]
		},
		["Increases spell penetration"] = {
			"SPELLPEN", -- [1]
		},
		["improves hit avoidance"] = {
			"MELEE_HIT_AVOID_RATING", -- [1]
			"RANGED_HIT_AVOID_RATING", -- [2]
			"SPELL_HIT_AVOID_RATING", -- [3]
		},
		["mastery"] = {
			"MASTERY_RATING", -- [1]
		},
		["Spell Damage and Healing"] = {
			"SPELL_DMG", -- [1]
			"HEAL", -- [2]
		},
		["Hit (Spell)"] = {
			"SPELL_HIT_RATING", -- [1]
		},
		["Increases the damage done by Shadow spells and effects"] = {
			"SHADOW_SPELL_DMG", -- [1]
		},
		["haste (ranged)"] = {
			"RANGED_HASTE_RATING", -- [1]
		},
		["nature spell damage"] = {
			"NATURE_SPELL_DMG", -- [1]
		},
		["defense"] = {
			"DEFENSE_RATING", -- [1]
		},
		["increases spell power"] = {
			"SPELL_DMG", -- [1]
			"HEAL", -- [2]
		},
		["Increases your healing"] = {
			"HEAL", -- [1]
		},
		["Haste"] = {
			"MELEE_HASTE_RATING", -- [1]
			"RANGED_HASTE_RATING", -- [2]
			"SPELL_HASTE_RATING", -- [3]
		},
		["Increases damage done by Arcane spells and effects"] = {
			"ARCANE_SPELL_DMG", -- [1]
		},
		["Hit Avoidance"] = {
			"MELEE_HIT_AVOID_RATING", -- [1]
			"RANGED_HIT_AVOID_RATING", -- [2]
			"SPELL_HIT_AVOID_RATING", -- [3]
		},
		["improves ranged critical avoidance"] = {
			"RANGED_CRIT_AVOID_RATING", -- [1]
		},
		["arcane resist"] = {
			"ARCANE_RES", -- [1]
		},
		["Increases damage done by Shadow spells and effects"] = {
			"SHADOW_SPELL_DMG", -- [1]
		},
		["hit"] = {
			"MELEE_HIT_RATING", -- [1]
			"RANGED_HIT_RATING", -- [2]
			"SPELL_HIT_RATING", -- [3]
		},
		["Fishing skill increased"] = {
			"FISHING", -- [1]
		},
		["increases your parry"] = {
			"PARRY_RATING", -- [1]
		},
		["increases your shield block"] = {
			"BLOCK_RATING", -- [1]
		},
		["fire resistance"] = {
			"FIRE_RES", -- [1]
		},
		["spell power"] = {
			"SPELL_DMG", -- [1]
			"HEAL", -- [2]
		},
		["Fire Spell Damage"] = {
			"FIRE_SPELL_DMG", -- [1]
		},
		["Improves spell critical strike"] = {
			"SPELL_CRIT_RATING", -- [1]
		},
		["Haste (Melee)"] = {
			"MELEE_HASTE_RATING", -- [1]
		},
		["Fire Resistance"] = {
			"FIRE_RES", -- [1]
		},
		["Increases damage done by Nature spells and effects"] = {
			"NATURE_SPELL_DMG", -- [1]
		},
		["Frost Spell Damage"] = {
			"FROST_SPELL_DMG", -- [1]
		},
		["Critical Strike Avoidance (Ranged)"] = {
			"RANGED_CRIT_AVOID_RATING", -- [1]
		},
		["hit (melee)"] = {
			"MELEE_HIT_RATING", -- [1]
		},
		["increases your dodge"] = {
			"DODGE_RATING", -- [1]
		},
		["increases spell penetration"] = {
			"SPELLPEN", -- [1]
		},
		["Damage per Second"] = {

			"DPS", -- [1]
		},
		["critical strike avoidance (melee)"] = {
			"MELEE_CRIT_AVOID_RATING", -- [1]
		},
		["sec"] = false,
		["increases attack powerwhen fighting undead"] = {
			"AP_UNDEAD", -- [1]
		},
		["increases attack powerwhen fighting demons"] = {
			"AP_DEMON", -- [1]
		},
		["increases damage and healing done by magical spells and effects of all party members within 30 yards"] = {
			"SPELL_DMG", -- [1]
			"HEAL", -- [2]
		},
		["Herbalism; does not need to be equipped"] = {
			"HERBALISM", -- [1]
		},
		["increases damage done by frost spells and effects"] = {
			"FROST_SPELL_DMG", -- [1]
		},
		["frost resist"] = {
			"FROST_RES", -- [1]
		},
		["Mana Regeneration"] = {
			"COMBAT_MANA_REGEN", -- [1]
		},
		["critical strike avoidance (spell)"] = {
			"SPELL_CRIT_AVOID_RATING", -- [1]
		},
		["Hit Avoidance (Ranged)"] = {
			"RANGED_HIT_AVOID_RATING", -- [1]
		},
		["Healing Spells"] = {
			"HEAL", -- [1]
		},
		["Frost Damage"] = {
			"FROST_SPELL_DMG", -- [1]
		},
		["arcane resistance"] = {
			"ARCANE_RES", -- [1]
		},
		["increases the damage done by holy spells and effects"] = {
			"HOLY_SPELL_DMG", -- [1]
		},
		["restores health per 5 sec"] = {
			"COMBAT_HEALTH_REGEN", -- [1]
		},
		["increases damage and healing done by magical spells and effects"] = {
			"SPELL_DMG", -- [1]
			"HEAL", -- [2]
		},
		["arcane spell damage"] = {
			"ARCANE_SPELL_DMG", -- [1]
		},
		["Improves melee haste"] = {
			"MELEE_HASTE_RATING", -- [1]
		},
		["improves melee hit avoidance"] = {
			"MELEE_HIT_AVOID_RATING", -- [1]
		},
		["herbalism"] = {
			"HERBALISM", -- [1]
		},
		["damage and healing spells"] = {
			"SPELL_DMG", -- [1]
			"HEAL", -- [2]
		},
		["Increases damage done to Undead and Demons by magical spells and effects"] = {
			"SPELL_DMG_UNDEAD", -- [1]
			"SPELL_DMG_DEMON", -- [2]
		},
		["increases healing donemagical spells and effects"] = {
			"HEAL", -- [1]
		},
		["Fishing"] = {
			"FISHING", -- [1]
		},
		["% shield block value"] = {
			"MOD_BLOCK_VALUE", -- [1]
		},
		["Strength"] = {
			"STR", -- [1]
		},
		["parry"] = {
			"PARRY_RATING", -- [1]
		},
		["nature resist"] = {
			"NATURE_RES", -- [1]
		},
		["Improves melee critical avoidance"] = {
			"MELEE_CRIT_AVOID_RATING", -- [1]
		},
		["armor"] = {
			"BONUS_ARMOR", -- [1]
		},
		["increases the damage done by nature spells and effects"] = {
			"NATURE_SPELL_DMG", -- [1]
		},
		["increases attack powerwhen fighting undead and demons"] = {
			"AP_UNDEAD", -- [1]
			"AP_DEMON", -- [2]
		},
		["health regeneration"] = {
			"COMBAT_HEALTH_REGEN", -- [1]
		},
		["healing and spell damage"] = {
			"SPELL_DMG", -- [1]
			"HEAL", -- [2]
		},
		["fishing"] = {
			"FISHING", -- [1]
		},
		["increases your effective stealth level"] = {
			"STEALTH_LEVEL", -- [1]
		},
		["Increases the damage done by Nature spells and effects"] = {
			"NATURE_SPELL_DMG", -- [1]
		},
		["spell penetration"] = {
			"SPELLPEN", -- [1]
		},
		["Holy Resistance"] = {
			"HOLY_RES", -- [1]
		},
		["Restores mana per 5 sec"] = {
			"COMBAT_MANA_REGEN", -- [1]
		},
		["Holy Spell Damage"] = {
			"HOLY_SPELL_DMG", -- [1]
		},
		["skinning"] = {
			"SKINNING", -- [1]
		},
		["fire damage"] = {
			"FIRE_SPELL_DMG", -- [1]
		},
		["Your attacks ignoreof your opponent's armor"] = {
			"IGNORE_ARMOR", -- [1]
		},
		["Increases healing done by magical spells and effects of all party members within 30 yards"] = {
			"HEAL", -- [1]
		},
		["increases damage done to undead and demons by magical spells and effects"] = {
			"SPELL_DMG_UNDEAD", -- [1]
			"SPELL_DMG_DEMON", -- [2]
		},
		["improves melee haste"] = {
			"MELEE_HASTE_RATING", -- [1]
		},
		["mana"] = {
			"MANA", -- [1]

		},

		["arcane damage"] = {
			"ARCANE_SPELL_DMG", -- [1]
		},
		["Shadow Damage"] = {
			"SHADOW_SPELL_DMG", -- [1]
		},
		["increases the holy damage of your judgments"] = false,
		["increases healing done by magical spells and effects of all party members within 30 yards"] = {
			"HEAL", -- [1]
		},
		["your attacks ignoreof your opponent's armor"] = {
			"IGNORE_ARMOR", -- [1]

		},
		["damage donefor all magical spells"] = {

			"SPELL_DMG", -- [1]
		},
		["Increases your effective stealth level"] = {
			"STEALTH_LEVEL", -- [1]
		},
		["Spell Healing"] = {
			"HEAL", -- [1]
		},
		["critical strike"] = {
			"MELEE_CRIT_RATING", -- [1]
			"RANGED_CRIT_RATING", -- [2]
			"SPELL_CRIT_RATING", -- [3]
		},
		["Critical Strike Avoidance (Melee)"] = {
			"MELEE_CRIT_AVOID_RATING", -- [1]
		},
		["Nature Resistance"] = {
			"NATURE_RES", -- [1]
		},
		["improves ranged hit avoidance"] = {
			"RANGED_HIT_AVOID_RATING", -- [1]
		},
		["hit avoidance"] = {
			"MELEE_HIT_AVOID_RATING", -- [1]
			"RANGED_HIT_AVOID_RATING", -- [2]
			"SPELL_HIT_AVOID_RATING", -- [3]
		},
		["increases the critical effect chance of your flash of light%"] = false,
		["armor penetration"] = {
			"ARMOR_PENETRATION_RATING", -- [1]
		},
		["Increases healing done"] = {
			"HEAL", -- [1]
		},
		["shadow damage"] = {
			"SHADOW_SPELL_DMG", -- [1]
		},
		["improves melee critical avoidance"] = {
			"MELEE_CRIT_AVOID_RATING", -- [1]
		},
		["Arcane Spell Damage"] = {
			"ARCANE_SPELL_DMG", -- [1]
		},
		["improves spell haste"] = {
			"SPELL_HASTE_RATING", -- [1]
		},
		["Improves melee critical strike"] = {
			"MELEE_CRIT_RATING", -- [1]
		},
		["Increases damage and healing done by magical spells and effects"] = {
			"SPELL_DMG", -- [1]
			"HEAL", -- [2]
		},
		["Increases damage done to Undead by magical spells and effects"] = {
			"SPELL_DMG_UNDEAD", -- [1]
		},
		["restores mana per 5 sec"] = {
			"COMBAT_MANA_REGEN", -- [1]
		},
		["healing spells"] = {
			"HEAL", -- [1]
		},
		["increases your armor penetration"] = {
			"ARMOR_PENETRATION_RATING", -- [1]
		},
		["haste (melee)"] = {
			"MELEE_HASTE_RATING", -- [1]
		},
		["spell damage and healing"] = {
			"SPELL_DMG", -- [1]
			"HEAL", -- [2]
		},
		["Arcane Resistance"] = {
			"ARCANE_RES", -- [1]
		},
		["Skinning; does not need to be equipped"] = {
			"SKINNING", -- [1]
		},
		["Increases attack powerwhen fighting Undead and Demons"] = {
			"AP_UNDEAD", -- [1]
			"AP_DEMON", -- [2]
		},
		["Increases attack powerin Cat, Bear, Dire Bear, and Moonkin forms only"] = {
			"FERAL_AP", -- [1]
		},
		["herbalism; does not need to be equipped"] = {
			"HERBALISM", -- [1]
		},
		["% Threat"] = {
			"MOD_THREAT", -- [1]
		},
		["Defense"] = {
			"DEFENSE_RATING", -- [1]
		},
		["Increases healing donemagical spells and effects"] = {
			"HEAL", -- [1]
		},
		["Experience gained from killing monsters and completing quests increased%"] = false,
		["increases damage done by fire spells and effects"] = {
			"FIRE_SPELL_DMG", -- [1]
		},
		["increases damage done by nature spells and effects"] = {
			"NATURE_SPELL_DMG", -- [1]
		},
		["Ranged Attack Power"] = {
			"RANGED_AP", -- [1]
		},
		["scope (damage)"] = {
			"RANGED_DMG", -- [1]
		},
		["increases the damage done by frost spells and effects"] = {
			"FROST_SPELL_DMG", -- [1]
		},
		["Shadow Resist"] = {
			"SHADOW_RES", -- [1]
		},
		["damage per second"] = {
			"DPS", -- [1]
		},
		["Increases the critical effect chance of your Flash of Light%"] = false,
		["improves ranged haste"] = {
			"RANGED_HASTE_RATING", -- [1]
		},
		["Increases attack powerwhen fighting Undead.  It also allows the acquisition of Scourgestones on behalf of the Argent Dawn"] = {
			"AP_UNDEAD", -- [1]
		},
		["PvP Power"] = {
			"PVP_POWER", -- [1]
		},
		["haste (spell)"] = {
			"SPELL_HASTE_RATING", -- [1]
		},
		["mining; does not need to be equipped"] = {
			"MINING", -- [1]
		},
		["Increases damage and healing done by magical spells and effects of all party members within 30 yards"] = {
			"SPELL_DMG", -- [1]
			"HEAL", -- [2]
		},
		["Spell Penetration"] = {
			"SPELLPEN", -- [1]

		},
		["Improves melee hit avoidance"] = {
			"MELEE_HIT_AVOID_RATING", -- [1]
		},
		["increases attack power"] = {
			"AP", -- [1]
		},
		["Increases defense"] = {
			"DEFENSE_RATING", -- [1]
		},
		["increases healing done"] = {
			"HEAL", -- [1]
		},
		["spirit"] = {
			"SPI", -- [1]
		},
		["Increases the damage done by Arcane spells and effects"] = {
			"ARCANE_SPELL_DMG", -- [1]
		},
		["improves spell critical strike"] = {
			"SPELL_CRIT_RATING", -- [1]
		},
		["PvP Resilience"] = {
			"RESILIENCE_RATING", -- [1]
		},
		["Damage and Healing Spells"] = {
			"SPELL_DMG", -- [1]
			"HEAL", -- [2]
		},
		["holy spell damage"] = {
			"HOLY_SPELL_DMG", -- [1]
		},
		["increases your critical strike"] = {
			"MELEE_CRIT_RATING", -- [1]
			"RANGED_CRIT_RATING", -- [2]
			"SPELL_CRIT_RATING", -- [3]
		},
		["healing Spells"] = {
			"HEAL", -- [1]
		},
		["Experience gained is increased%"] = false,
		["critical strike (spell)"] = {
			"SPELL_CRIT_RATING", -- [1]
		},
		["Increases Healing"] = {
			"HEAL", -- [1]
		},
		["Armor Penetration"] = {
			"ARMOR_PENETRATION_RATING", -- [1]
		},
		["arcane and fire spell power"] = {
			"ARCANE_SPELL_DMG", -- [1]
			"FIRE_SPELL_DMG", -- [2]
		},
		["Health Regeneration"] = {
			"COMBAT_HEALTH_REGEN", -- [1]
		},
		["all stats"] = {
			"STR", -- [1]
			"AGI", -- [2]
			"STA", -- [3]
			"INT", -- [4]
			"SPI", -- [5]
		},
		["Increases your hit"] = {
			"MELEE_HIT_RATING", -- [1]
			"RANGED_HIT_RATING", -- [2]
			"SPELL_HIT_RATING", -- [3]
		},
		["Critical Strike Avoidance (Spell)"] = {
			"SPELL_CRIT_AVOID_RATING", -- [1]
		},
		["strength"] = {
			"STR", -- [1]
		},
		["Parry"] = {
			"PARRY_RATING", -- [1]
		},
		["Increases the damage done by Holy spells and effects"] = {
			"HOLY_SPELL_DMG", -- [1]
		},
		["Restores health per 5 sec"] = {
			"COMBAT_HEALTH_REGEN", -- [1]
		},
		["Increases your shield block"] = {
			"BLOCK_RATING", -- [1]
		},
		["Hit"] = {
			"MELEE_HIT_RATING", -- [1]
			"RANGED_HIT_RATING", -- [2]
			"SPELL_HIT_RATING", -- [3]
		},
		["increases damage done by shadow spells and effects"] = {
			"SHADOW_SPELL_DMG", -- [1]

		},
		["All Resistances"] = {
			"ARCANE_RES", -- [1]
			"FIRE_RES", -- [2]
			"FROST_RES", -- [3]
			"NATURE_RES", -- [4]
			"SHADOW_RES", -- [5]
		},
		["frost spell damage"] = {
			"FROST_SPELL_DMG", -- [1]
		},
		["improves ranged hit"] = {
			"RANGED_HIT_RATING", -- [1]
		},
	},
}
]]


--Returns info about provided scores format
--Possible values: "pawnv1"
function GA:GetScoresTextInfo(text)
    if text:find("Pawn: v1:") then
        return "pawnv1"
    end
end

function GA:ImportScoresFromText(text)

    if GA:GetScoresTextInfo(text) == "pawnv1" then
    
        local pawn2dugisScoreName_map = {}
                
        pawn2dugisScoreName_map["Agility"]           = "AGI"
        pawn2dugisScoreName_map["Armor"]             = "ARMOR"
        pawn2dugisScoreName_map["Avoidance"]         = "AVOIDANCE_RATING"
        pawn2dugisScoreName_map["CritRating"]        = "MELEE_CRIT_RATING or SPELL_CRIT_RATING or RANGED_CRIT_RATING"
        pawn2dugisScoreName_map["HasteRating"]       = "MELEE_HASTE_RATING or RANGED_HASTE_RATING or SPELL_HASTE_RATING"
        pawn2dugisScoreName_map["Intellect"]         = "INT"
        pawn2dugisScoreName_map["Leech"]             = "LEECH_RATING"
        pawn2dugisScoreName_map["MasteryRating"]     = "MASTERY_RATING"
        pawn2dugisScoreName_map["Stamina"]           = "STA"
        pawn2dugisScoreName_map["Strength"]          = "STR"
        pawn2dugisScoreName_map["Versatility"]       = "VERSATILITY_RATING"
        pawn2dugisScoreName_map["Ap"]                = "AP"
        pawn2dugisScoreName_map["Dps"]               = "DPS and DPS|MAIN and DPS|OFF"

        text = string.gsub(text, '^.*\:', '')
        text = string.gsub(text, ' [)]', '')
        text = string.gsub(text, '[)]', '')
        
        local scores = LuaUtils:split(text, ",")
        
        local specIndex = tonumber(DugisGuideViewer.Modules.GearAdvisor.selectedSpecIndex)
        local classId = GA:GetCurrentSelectedClassIdentifier()
        local dugiScoretable = DugisGuideUser.userCustomWeights_v4[classId][specIndex]
        
        LuaUtils:foreach(scores, function(score)
            score = LuaUtils:trim(score)
            local scoreName, scoreValue = unpack(LuaUtils:split(score, "="))
            local weightIdentifier = pawn2dugisScoreName_map[scoreName]
            
            if weightIdentifier then
                if string.find(weightIdentifier, " and ") then
                    for _, finalWeightIdentifier in pairs(LuaUtils:split(weightIdentifier, " and ")) do
                        dugiScoretable[finalWeightIdentifier] = tonumber(scoreValue)
                    end
                    return
                end
                
                if string.find(weightIdentifier, " or ") then
                    for _, finalWeightIdentifier in pairs(LuaUtils:split(weightIdentifier, " or ")) do
                        if dugiScoretable[finalWeightIdentifier] ~= nil then
                            dugiScoretable[finalWeightIdentifier] = tonumber(scoreValue)
                        end                    
                    end
                    return
                end
            
                dugiScoretable[weightIdentifier] = tonumber(scoreValue)
            end
        end)
        
        GA:UpdateWeightsTextboxes()
        
    end
    

end
