--TODO:This is godawful and needs to be rewritten to avoid frame drops, see "godawful" tags throughout for further info. It is too much to tackle now, but globals were removed

local DGV = DugisGuideViewer
if not DGV then return end

--------------- Configuration -----------------

--higher value => longer processing =>  higher FPS
local CALCULATIONS_TIME_FOR_EMPTY_CACHE = 5  --Used in case new character was crated ot the player level has changed

--higher value => longer processing =>  higher FPS
local CALCULATIONS_TIME_FOR_FILLED_CACHE = 1  --For the same player level

--To check the performance on level change you can  run: /run DugisCharacterCache.CalculateScore_cache_v12 = {}    and then   /reload

local gearFinderDebug = false
local listItemsLimit = 6
-----------------------------

--godawful: use module
if not DugisGearFinder then
    DugisGearFinder = {}
end

local StatLogic = LibStub("LibStatLogic-1.2-Dugi")

local DGF = DugisGearFinder
local GA = DGV.Modules.GearAdvisor

--Variables for CacheItemsForGearFinder
DGF.retryQueue = {}
DGF.retryCounter = {}


--{slot1 = {itemId1 = item1, itemId2 =item2,}, slot2 = {itemId1 = item1, itemId2 =item2,}}
DGF.itemsBySlot = {}

local LuaUtils = LuaUtils

--{{control, iteratedItemLink}, {control, iteratedItemLink}, ...}
DGF.GeadAdvisorItemIterator_cache = {}

DGF.IsQuestCompleted_cache = {}

----------------------------
local DGV = DugisGuideViewer

local GearFinderModule, GA = DGV:RegisterModule("GearFinder", "GearAdvisor")
local DebugPrint = DGV.DebugPrint

function GearFinderModule:ShouldLoad()
	return DugisGuideViewer.chardb.EssentialsMode < 1 and DugisGuideViewer:GuideOn()
end

function GearFinderModule:Initialize()
    if DGF.allGearIds then
        DGV.InitializeGearFinder()
    end
    GearFinderModule.loaded = true
end

local GetItemQualityColor
function GearFinderModule:Load()
    GetItemQualityColor = C_Item.GetItemQualityColor
    if DGF.allGearIds and DugisGuideViewer:UserSetting(DGV_ENABLEDGEARFINDER) then
        GearFinderModule.loaded = true
    end
end

local function GetAuctionHouseCategoryName(classID, subClassID, inventoryType)
	local name = "";
	if inventoryType then
		name = GetItemInventorySlotInfo(inventoryType);
	elseif classID and subClassID then
		name = GetItemSubClassInfo(classID, subClassID);
	elseif classID then
		name = GetItemClassInfo(classID);
	end
	return name
end

local function GetAuctionItemSubClasses_dugis(classIndex)
    if classIndex == 1 then
        return {
            [0] = GetAuctionHouseCategoryName(Enum.ItemClass.Weapon, Enum.ItemWeaponSubclass.Axe1H       ) ,
            [1] = GetAuctionHouseCategoryName(Enum.ItemClass.Weapon, Enum.ItemWeaponSubclass.Axe2H       ) ,
            [2] = GetAuctionHouseCategoryName(Enum.ItemClass.Weapon, Enum.ItemWeaponSubclass.Bows        ) ,
            [3] = GetAuctionHouseCategoryName(Enum.ItemClass.Weapon, Enum.ItemWeaponSubclass.Guns        ) ,
            [4] = GetAuctionHouseCategoryName(Enum.ItemClass.Weapon, Enum.ItemWeaponSubclass.Mace1H      ) ,
            [5] = GetAuctionHouseCategoryName(Enum.ItemClass.Weapon, Enum.ItemWeaponSubclass.Mace2H      ) ,
            [6] = GetAuctionHouseCategoryName(Enum.ItemClass.Weapon, Enum.ItemWeaponSubclass.Polearm     ) ,
            [7] = GetAuctionHouseCategoryName(Enum.ItemClass.Weapon, Enum.ItemWeaponSubclass.Sword1H     ) ,
            [8] = GetAuctionHouseCategoryName(Enum.ItemClass.Weapon, Enum.ItemWeaponSubclass.Sword2H     ) ,
            [9] = GetAuctionHouseCategoryName(Enum.ItemClass.Weapon, Enum.ItemWeaponSubclass.Warglaive   ) ,
            [10] = GetAuctionHouseCategoryName(Enum.ItemClass.Weapon, Enum.ItemWeaponSubclass.Staff      ) ,
            [13] = GetAuctionHouseCategoryName(Enum.ItemClass.Weapon, Enum.ItemWeaponSubclass.Unarmed    ) ,
            [14] = GetAuctionHouseCategoryName(Enum.ItemClass.Weapon, Enum.ItemWeaponSubclass.Generic    ) ,
            [15] = GetAuctionHouseCategoryName(Enum.ItemClass.Weapon, Enum.ItemWeaponSubclass.Dagger     ) ,
            [16] = GetAuctionHouseCategoryName(Enum.ItemClass.Weapon, Enum.ItemWeaponSubclass.Thrown     ) ,
            [18] = GetAuctionHouseCategoryName(Enum.ItemClass.Weapon, Enum.ItemWeaponSubclass.Crossbow   ) ,
            [19] = GetAuctionHouseCategoryName(Enum.ItemClass.Weapon, Enum.ItemWeaponSubclass.Wand       ) ,            
        }
    end
    
    --AUCTION_CATEGORY_ARMOR
    if classIndex == 2 then
        return {
            [0] = GetAuctionHouseCategoryName(Enum.ItemClass.Armor, Enum.ItemArmorSubclass.Generic  ) ,
            [1] = GetAuctionHouseCategoryName(Enum.ItemClass.Armor, Enum.ItemArmorSubclass.Cloth    ) ,
            [2] = GetAuctionHouseCategoryName(Enum.ItemClass.Armor, Enum.ItemArmorSubclass.Leather  ) ,
            [3] = GetAuctionHouseCategoryName(Enum.ItemClass.Armor, Enum.ItemArmorSubclass.Mail     ) ,
            [4] = GetAuctionHouseCategoryName(Enum.ItemClass.Armor, Enum.ItemArmorSubclass.Plate    ) ,
            [5] = GetAuctionHouseCategoryName(Enum.ItemClass.Armor, Enum.ItemArmorSubclass.Cosmetic ) ,
            [6] = GetAuctionHouseCategoryName(Enum.ItemClass.Armor, Enum.ItemArmorSubclass.Shield   ) 
        }
    end
end

--godawful: the cachening
local allOwnedItems = {}
local itemId2IsOwned = {}

function GearFinderModule:Unload()
    HideUIPanel(CharacterFrame)
    if DGV:UserSetting(DGV_UNLOADMODULES) then
        wipe(DGF.gearId2GearInfos_map)
        wipe(DGF.guideTitle2GearIds_map)
        wipe(DGF.allGearGuides)
        wipe(DGF.gearId2DroppedByBoss_map)
        wipe(DGF.gearId2Quests_map)
        wipe(DGF.gearId2PossibleDifficulties_map)
        wipe(DGF.allGearIds)
        wipe(DGF.itemsBySlot)
        wipe(DGF.retryQueue)
        wipe(DGF.retryCounter)
        wipe(DGF.GeadAdvisorItemIterator_cache)
        wipe(DugisCharacterCache.CalculateScore_cache_v12)
        wipe(DGF.IsQuestCompleted_cache)

        DGF.gearId2GearInfos_map = {}
        DGF.guideTitle2GearIds_map = {}
        DGF.allGearGuides = {}
        DGF.gearId2DroppedByBoss_map = {}
        DGF.gearId2Quests_map = {}
        DGF.gearId2PossibleDifficulties_map = {}
        DGF.allGearIds = {}
        DGF.itemsBySlot = {}
        DGF.retryQueue = {}
        DGF.retryCounter = {}
        DGF.GeadAdvisorItemIterator_cache = {}
        DGF.IsQuestCompleted_cache = {}
        DugisCharacterCache.CalculateScore_cache_v12 = {}
        CacheItemsForGearFinder_invoked = false

        collectgarbage("step", 100000)

        GearFinderModule.loaded = false

        DGF:UpdateTabsForGearFinder()
    end
end
---------------------------

local equipementSlots = {
    "INVTYPE_HEAD",
    "INVTYPE_NECK",
    "INVTYPE_HAND",
    --INVTYPE_WEAPON_MERGED  Always displayed - except Hunters
    --INVTYPE_OFFHAND_MERGED  Always Displayed, except Hunters
    --INVTYPE_2HWEAPON  Displayed if ITEM_CLASS_WEAPON,2,6,7,9,10
    --INVTYPE_RANGED_MERGED   Displayed if ITEM_CLASS_WEAPON,3,4,15
    "INVTYPE_SHOULDER",
    "INVTYPE_CLOAK",
    "INVTYPE_CHEST_MERGED",
    "INVTYPE_WRIST",
    "INVTYPE_WAIST",
    "INVTYPE_LEGS",
   "INVTYPE_FEET",
   "INVTYPE_FINGER",
   "INVTYPE_TRINKET",
}


local localizedClass, englishClass, classIndex = UnitClass("Player");

function DGF:Slot2VirtualSlot(slot)
    if slot == "INVTYPE_ROBE" or slot == "INVTYPE_CHEST" then
        return "INVTYPE_CHEST_MERGED"
    end

    if slot == "INVTYPE_RANGED" or slot == "INVTYPE_RANGEDRIGHT" then
        return "INVTYPE_RANGED_MERGED"
    end

     if slot == "INVTYPE_WEAPONOFFHAND" or slot == "INVTYPE_HOLDABLE" or slot == "INVTYPE_SHIELD" then
        return "INVTYPE_OFFHAND_MERGED"
    end

    if slot == "INVTYPE_WEAPON" or slot == "INVTYPE_WEAPONMAINHAND" then
        return "INVTYPE_WEAPON_MERGED"
    end

    return slot
end

--slot - virtual slot
local function IsArmorSpecSlot(slot)
    return
        slot=="INVTYPE_CHEST_MERGED" or
        slot=="INVTYPE_FEET" or
        slot=="INVTYPE_HAND" or
        slot=="INVTYPE_HEAD" or
        slot=="INVTYPE_LEGS" or
        slot=="INVTYPE_SHOULDER" or
        slot=="INVTYPE_WAIST" or
        slot=="INVTYPE_WRIST"
end

function DGF:LocalizeSlot(slot)
   local map = {
        ["INVTYPE_2HWEAPON"] = "Two Hand" ,
        ["INVTYPE_CHEST_MERGED"] = "Chest",
        ["INVTYPE_HAND"] = "Hand",
        ["INVTYPE_RANGED_MERGED"] = "Ranged",
        ["INVTYPE_WEAPON_MERGED"] = "Weapon",
        ["INVTYPE_OFFHAND_MERGED"] = "Off-hand"
    }

    slot = DGF:Slot2VirtualSlot(slot)

    if map[slot] then
        return map[slot]
    end

    return _G[slot]
end

local itemButtons = {}
local extraButtons = {}
local tooltipItems = {}

function DGF:Print(...)
    if gearFinderDebug then
        print(...)
    end
end

--{[difficultyId] => shouldInclude:true/false}
function DGF:GetDifficultyFilters()
    local filers = {}

    LuaUtils:loop(100, function(diffId)
        filers[diffId] = true
    end)

    filers[1] =  DGV:UserSetting(DGV_INCLUDE_DUNG_NORMAL)
    filers[2] =  DGV:UserSetting(DGV_INCLUDE_DUNG_HEROIC)

    filers[7] =  DGV:UserSetting(DGV_INCLUDE_RAIDS_RAIDFINDER)

    filers[23] = DGV:UserSetting(DGV_INCLUDE_DUNG_MYTHIC)
    filers[24] = DGV:UserSetting(DGV_INCLUDE_DUNG_TIMEWALKING)

    filers[17] = DGV:UserSetting(DGV_INCLUDE_RAIDS_RAIDFINDER)
    filers[14] = DGV:UserSetting(DGV_INCLUDE_RAIDS_NORMAL)
    filers[15] = DGV:UserSetting(DGV_INCLUDE_RAIDS_HEROIC)
    filers[16] = DGV:UserSetting(DGV_INCLUDE_RAIDS_MYTHIC)

    return filers
end

function DGF:IsQuestCompleted(questId)
    return DGF.IsQuestCompleted_cache[questId]
end

function DGF:UpdateQuestsCompletedCache()
    DGF.IsQuestCompleted_cache = {}

    for i, qId in pairs(C_QuestLog.GetAllCompletedQuestIDs()) do
        DGF.IsQuestCompleted_cache[qId] = true
    end
end

DGF:UpdateQuestsCompletedCache()

function DGF:PlayerHasEnoughLevel(suggestion)
    local currenLevel = UnitLevel("player")

    if suggestion.item.info.reqlevelByQuest ~= nil then
        return currenLevel >= suggestion.item.info.reqlevelByQuest
    end

    if suggestion.item.info.reqlevel ~= 0 and suggestion.item.info.reqlevel ~= nil then
        return currenLevel >= suggestion.item.info.reqlevel
    end

    return true
end

function DGF:StartGuide(guide)
    if DGV.DisplayViewTabInThread and DugisGuideViewer.chardb.EssentialsMode ~= 1 and DugisGuideViewer:GuideOn() then
        DGV:DisplayViewTabInThread(guide)
    end
end

function DGF:LoadGuideButtonOnClick(self)
    local suggestion = self.suggestion

    if suggestion then
        local theBestGuide = DGF:GetTheBestGuideForGearId(self.suggestion.item.info.itemid, true)
        DGF:StartGuide(theBestGuide)
		print("|cff11ff11Dugi Guides: |r"..DGV:GetFormattedTitle(theBestGuide).."|cff11ff11 selected.|r")
		LuaUtils:PlaySoundFile(567458)
        --Print("Guide Loaded: ", self.suggestion.item.info.equipslot, self.suggestion.item.info.name, self.suggestion.item.info.itemid, " Used guide:", DugisGuideViewer:GetFormattedTitle(theBestGuide))
    else
        DGF:Print("Guide Loaded: ", self.slot)
    end
end

function DGF:HideAllMoreButtons()
    for _, item in pairs(itemButtons) do
        item.loadGuideButton:Hide()
    end
    for _, item in pairs(extraButtons) do
        item.loadGuideButton:Hide()
    end
end

function DGF:SetToNormalAllMoreButtons()
    for _, item in pairs(itemButtons) do
        EquipmentFlyoutPopoutButton_SetReversed(item.moreButton, false)
        item.moreButton.reversed = false
        item.SelectedBar:Hide()
    end
    for _, item in pairs(extraButtons) do
        EquipmentFlyoutPopoutButton_SetReversed(item.moreButton, false)
        item.moreButton.reversed = false
        item.SelectedBar:Hide()
    end
end

local boxSize = 49
local boxSizeTwoLabels = 36


local function ItemTexture(suggestion)
    return suggestion.item.info.texture
end

local function ItemName(suggestion)
    return suggestion.item.info.name
end

function DGF.ItemQuality(suggestion)
    return suggestion.item.info.quality
end

--The layout can be: "two-labels", "three-labels"
function DGF:GetCreateGuideBox(parent, x, y, layout, index, reuseItems)
    local slotButtonName = nil
    
    if reuseItems then
        slotButtonName = "GearFinderSlotButton"..index 
    end

    local box 
    
    if _G[slotButtonName] and reuseItems then
        box = _G[slotButtonName]
    else
        box = CreateFrame("Button", slotButtonName ,parent, "GearSetButtonTemplate")
        
        local topLabel = box:CreateFontString(nil,nil,"SystemFont_Med1")
        topLabel:ClearAllPoints()
        topLabel:SetPoint("TOPLEFT",box,"TOPLEFT",5, -3)
        topLabel:SetTextColor(1, 1, 1)
        topLabel:SetNonSpaceWrap(true)

        local bottomLabel = box:CreateFontString(nil,nil,"SystemFont_Shadow_Small")
        bottomLabel:ClearAllPoints()
        bottomLabel:SetPoint("BOTTOMLEFT",box,"BOTTOMLEFT",36, 4)
        bottomLabel:SetText("")
        bottomLabel:SetTextColor(1, 1, 1)
        bottomLabel:SetNonSpaceWrap(true)

        local middleLabel = box:CreateFontString(nil,nil,"SystemFont_Shadow_Small")
        middleLabel:ClearAllPoints()
        middleLabel:SetPoint("BOTTOMLEFT",box,"BOTTOMLEFT",36, 18)
        middleLabel:SetTextColor(1, 1, 1)
        middleLabel:SetNonSpaceWrap(true)
        middleLabel:SetText("Loading..")

        topLabel:SetJustifyH("LEFT")
        middleLabel:SetWordWrap(false)
        middleLabel:SetJustifyH("LEFT")

        topLabel:SetWordWrap(false)
        bottomLabel:SetWordWrap(false)
        bottomLabel:SetJustifyH("LEFT")

        if layout == "two-labels" then
            bottomLabel:SetPoint("BOTTOMLEFT",box,"BOTTOMLEFT", 38, 4)
            topLabel:SetPoint("TOPLEFT",box,"TOPLEFT", 38, -7)
            middleLabel:Hide()
        end

        local iconFrame = CreateFrame("Frame", nil, box)
        iconFrame:SetSize(28, 28)

        iconFrame:SetPoint("BOTTOMLEFT",box,"BOTTOMLEFT", 5, 3)

        local itemTexture = iconFrame:CreateTexture()
        itemTexture:SetAllPoints(iconFrame)
        iconFrame:Show()

        local moreButton = CreateFrame("Button", nil, box, "GearFinderMoreTemplate")
        moreButton:SetScript("OnLoad", nil)
        moreButton.slotItem = box
        moreButton:Show()

        moreButton:SetHeight(32);
        moreButton:SetWidth(16);
        moreButton:GetNormalTexture():SetTexCoord(0.15625, 0.5, 0.84375, 0.5, 0.15625, 0, 0.84375, 0);
        moreButton:GetHighlightTexture():SetTexCoord(0.15625, 1, 0.84375, 1, 0.15625, 0.5, 0.84375, 0.5);
        moreButton:ClearAllPoints();
        moreButton:SetPoint("LEFT", box, "RIGHT", -9, -7);
        moreButton:Hide()
    
        if box.SpecRing then
            box.SpecRing:Hide()
        end

        moreButton:SetScript("OnClick", function(self)
            if LuaUtils:ThreadInProgress("MoreButtonClicked") then
                return
            end

            LuaUtils:CreateThread("MoreButtonClicked", function()
                        local wasReverser = self.reversed
                        DGF:SetToNormalAllMoreButtons()
                        self.reversed = not wasReverser

                        EquipmentFlyoutPopoutButton_SetReversed(self, self.reversed)

                        self.slotItem.SelectedBar:Show()

                        if not self.reversed then
                            GearFinderExtraItemsFrame:Hide()
                            self.slotItem.SelectedBar:Hide()
                            return
                        end

                        GearFinderExtraItemsFrame:Show()

                        local localizedSlotName = DGF:LocalizeSlot(self.slotItem.suggestion.item.info.equipslot)
                        GearFinderExtraItemsFrame.headerLabel:SetText(localizedSlotName)

                        LuaUtils:loop(5, function(index)
                            extraButtons[index]:Hide()
                        end)

                        local deltaY = 0

                        if self.slotItem.top5suggestions then
                            for index, suggestion in pairs(self.slotItem.top5suggestions) do
                                if extraButtons[index] then
                                    extraButtons[index].topLabel:SetText("X")

                                    local shortName = ItemName(suggestion)
                                    local requiredLevelInfo = ""

                                    extraButtons[index].itemTexture:SetTexture(ItemTexture(suggestion))

                                    local r, g, b, hex = GetItemQualityColor(DGF.ItemQuality(suggestion))

                                    local requiredLevelInfo = ""
                                    local requiredLevelInfoBefore = ""
                                    if not DGF:PlayerHasEnoughLevel(suggestion) then
                                        local lvl = suggestion.item.info.reqlevel

                                        if suggestion.item.info.reqlevelByQuest ~= nil then
                                            lvl = suggestion.item.info.reqlevelByQuest
                                        end

                                        if string.len(shortName) > 18 then
                                            requiredLevelInfoBefore = "|c00FF0000@L"..lvl.."|r "
                                        else
                                            requiredLevelInfoBefore = "|c00FF0000@ Level "..lvl.."|r "
                                        end
                                    end

                                    extraButtons[index].topLabel:SetText(requiredLevelInfoBefore.."|c"..hex..shortName.."|r")
                                    local guide = DGF:GetTheBestGuideForGearId(suggestion.item.info.itemid, true)

                                    if DugisGuideViewer.GetFormattedTitle then
                                        guide = DugisGuideViewer:GetFormattedTitle(guide)
                                    end

                                    extraButtons[index].bottomLabel:SetText("|cFFFFFFFF"..guide.."|r" )
                                    extraButtons[index].bottomLabel:SetWidth(210)
                                    extraButtons[index].topLabel:SetWidth(195)

                                    extraButtons[index].suggestion = suggestion
                                    extraButtons[index]:Show()

                                    deltaY = deltaY + boxSizeTwoLabels

                                    GearFinderExtraItemsFrame:SetHeight(deltaY + 65)
                                end

                                GearFinderExtraItemsFrame.headerLabel:SetText(localizedSlotName .." " .. index.."/"..#self.slotItem.top5suggestions.."..")
                            end

                            GearFinderExtraItemsFrame.headerLabel:SetText(localizedSlotName)
                        end
                end
            )
        end)

        moreButton:SetScript("OnEnter", function()
            GameTooltip:SetOwner(moreButton, "ANCHOR_RIGHT")
            GameTooltip:AddLine("Load more suggestions", 1, 1, 1)
            GameTooltip:Show()
        end)

        moreButton:SetScript("OnLeave", function()
            GameTooltip:Hide()
        end)

        -------------Info button---------------------------
        local loadGuideButton = CreateFrame("Button", nil, box)
        loadGuideButton:SetSize(28, 28)
        loadGuideButton.slotItem = box
        loadGuideButton:SetPoint("TOPRIGHT",box,"TOPRIGHT",5, 5)
        loadGuideButton:SetNormalTexture("Interface\\MINIMAP\\UI-Minimap-MinimizeButtonUp-Up.blp")
        loadGuideButton:SetHighlightTexture("Interface\\MINIMAP\\UI-Minimap-MinimizeButtonUp-Highlight.blp")
        loadGuideButton:SetPushedTexture("Interface\\MINIMAP\\UI-Minimap-MinimizeButtonUp-Down.blp")
        loadGuideButton:Hide()
        loadGuideButton:SetScript("OnClick", function(self)
            LuaUtils:CreateThread("LoadGuideButtonOnClick", function()
                DGF:LoadGuideButtonOnClick(box)
            end)
        end)

        loadGuideButton.box = box

        loadGuideButton:SetScript("OnEnter", function(self)
            LuaUtils:CreateThread("LoadGuideButtonOnEnter", function()
                DGF:UpdateQuestsCompletedCache()
            
                local gearId = box.suggestion.item.info.itemid

                local guideTitle, gearInfo = DGF:GetTheBestGuideForGearId(gearId, true)

                if not gearInfo then
                    return
                end

                local questName = ""

                if gearInfo.questIds then
                    for _, questId in pairs(gearInfo.questIds) do
                        if not DGF:IsQuestCompleted(questId) then
                            if DugisGuideViewer.NPCJournalFrame.GetQuestInfo then
                                local questInfo = DugisGuideViewer.NPCJournalFrame:GetQuestInfo(tonumber(questId))
                                if questInfo then
                                    questName = questInfo.name
                                end
                            end
                        end
                    end
                end

                GameTooltip:SetOwner(moreButton, "ANCHOR_RIGHT")

                local guideTitleFormatted = guideTitle
                local guideExists = true

                if not DugisGuideViewer:isValidGuide(guideTitle) then
                    guideExists = false
                end

                if  DugisGuideViewer.GetFormattedTitle then
                    guideTitleFormatted = DugisGuideViewer:GetFormattedTitle(guideTitle)
                end

                if guideTitleFormatted then
                    if gearInfo.reputationId then
                        GameTooltip:AddLine("Reputation With: |cFFFFFFFF"..guideTitleFormatted.."|r" , 1, 0.8, 0.0)
                    else
                        GameTooltip:AddLine("Found In: |cFFFFFFFF"..guideTitleFormatted.."|r" , 1, 0.8, 0.0)
                    end
                end

                if gearInfo.bossId then
                    local numericBossId = tonumber(gearInfo.bossId)
                    local droppedBy
                    local bossName

                    if numericBossId > 0 then
                        bossName =  DugisGuideViewer:GetLocalizedNPC(numericBossId)
                    else
                        if gearInfo.encounterId then
                            bossName = EJ_GetEncounterInfo(gearInfo.encounterId)
                        end
                    end

                    if not bossName then
                        droppedBy = "Boss "..gearInfo.bossId
                    else
                        droppedBy = bossName
                    end
                    GameTooltip:AddLine("Dropped by: |cFFFFFFFF"..droppedBy.."|r" , 1, 0.8, 0.0)
                end

                if questName ~= "" then
                    GameTooltip:AddLine("Reward From: |cFFFFFFFF"..questName.."|r" , 1, 0.8, 0.0)
                end

                if guideExists then
                    GameTooltip:AddLine("Click to load the guide", 0, 1, 0)
                else
                    GameTooltip:AddLine("Guide not available", 1, 0, 0)
                end
                GameTooltip:Show()

                if self.box.top5suggestions == nil or #self.box.top5suggestions < 2 then
                    self.box.moreButton:Hide()
                else
                    self.box.moreButton:Show()
                end
            
            
            end)
        end)

        loadGuideButton:SetScript("OnLeave", function()
            GameTooltip:Hide()
        end)
        
        
       box:SetScript("OnEnter", function(self)
            if self.suggestion and self.suggestion.item.info.itemid then
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT");
                
                local link = DGF.SetBonusesAndLevelForItemLink(self.suggestion.item)
                GameTooltip:SetHyperlink(link);
            end

            DGF:HideAllMoreButtons()

            if self.suggestion then
                self.loadGuideButton:Show()
            end

            if self.suggestion then
                self.HighlightBar:Show()
            end
        end)

        box:SetScript("OnLeave", function(self)
            GameTooltip:Hide()
            self.HighlightBar:Hide();
        end)

        box:SetScript("OnClick", nil)
        

        box.topLabel = topLabel
        box.middleLabel = middleLabel
        box.bottomLabel = bottomLabel
        box.moreButton = moreButton
        box.loadGuideButton = loadGuideButton
        box.itemTexture = itemTexture
        
    end

    box:SetSize(168, boxSize)

    if layout == "two-labels" then
        box:SetSize(257, boxSizeTwoLabels)
    end

    box:SetPoint("TOPLEFT",x, y)
    box.texture = box:CreateTexture()
    box.texture:SetAllPoints(box)
    --box.texture:SetTexture("Interface\\CHARACTERFRAME\\BarHighlight.blp")
  
    return box
end

function DGF:UpdateGearButtons(itemButtons, showShadows)
    local STRIPE_COLOR = {r=0.9, g=0.9, b=1}

	for i = 1, #itemButtons do
        local button = itemButtons[i];

        button.Check:Hide();
        button.icon:Hide()

        if i == 1 and showShadows then
            button.BgTop:Show();
            button.BgMiddle:SetPoint("TOP", itemButtons[i].BgTop, "BOTTOM");
        else
            button.BgTop:Hide();
            button.BgMiddle:SetPoint("TOP");
        end

        if i == numRows and showShadows then
            button.BgBottom:Show();
            button.BgMiddle:SetPoint("BOTTOM", itemButtons[i].BgBottom, "TOP");
        else
            button.BgBottom:Hide();
            button.BgMiddle:SetPoint("BOTTOM");
        end

        if i % 2 == 0 then
            button.Stripe:SetColorTexture(STRIPE_COLOR.r, STRIPE_COLOR.g, STRIPE_COLOR.b);
            button.Stripe:SetAlpha(0.1);
            button.Stripe:Show();
        else
            button.Stripe:Hide();
        end

        if not showShadows then
             button.BgMiddle:SetAlpha(0.01)
        else
         button.BgMiddle:SetAlpha(1)
        end
	end
end

function DGF:CreateExtraItemsFrame()
    if _G["GearFinderExtraItemsFrame"] then
        return
    end

    local frame = CreateFrame("Frame", "GearFinderExtraItemsFrame", UIParent, "BackdropTemplate")
    frame:EnableMouse(true)
    frame:SetClampedToScreen(true)
    frame:RegisterForDrag("LeftButton")

    frame:SetWidth(285)
    frame:SetHeight(299)

    frame:Show()

     frame:SetBackdrop({
    bgFile = [[Interface\GLUES\Models\UI_MainMenu_Cataclysm\UI_BLACKCOLOR01.BLP]]
    ,edgeFile =  DugisGuideViewer:GetBorderPath(),
                                            tile = false, tileSize = 30, edgeSize = 32,
                                            insets = { left = 10, right = 5, top = 10, bottom = 5 }})

    frame:SetBackdropColor(1,1,1,1)

	DugisGuideViewer.ApplyElvUIColor(frame)
	
    GearFinderExtraItemsFrame:SetPoint("TOPRIGHT",CharacterFrame, 283, 5)

    frame:SetParent(CharacterFrame)
    frame:Hide()

    --Header
    local headerLabel = frame:CreateFontString(nil, nil, "SystemFont_Med3")
    headerLabel:ClearAllPoints()
    headerLabel:SetPoint("TOPLEFT", frame, "TOPLEFT", 20, -25)
    headerLabel:SetText("Top gear guides")
    headerLabel:SetTextColor(1,0.8,0)
    headerLabel:SetNonSpaceWrap(true)
    GearFinderExtraItemsFrame.headerLabel = headerLabel

    --Close button
    LuaUtils:loop(5, function(index)
        local button = DGF:GetCreateGuideBox(frame, 15,  -index * (boxSizeTwoLabels) - 10, "two-labels")
        button.moreButton:Hide()
        extraButtons[#extraButtons + 1] = button
    end)

    DGF:UpdateGearButtons(extraButtons, false)
end

function DGF:GearFinderTooltipFrame()
    if _G["GearFinderTooltipFrame"] then
        return
    end

    local frame = CreateFrame("Frame", "GearFinderTooltipFrame", UIParent, "BackdropTemplate")
    frame:EnableMouse(true)
    frame:SetClampedToScreen(true)
    frame:RegisterForDrag("LeftButton")

    frame:SetWidth(285)
    frame:SetHeight(299)
    frame:SetClampedToScreen(true)

    frame:Show()

    frame:SetBackdrop({
    bgFile = [[Interface\GLUES\Models\UI_MainMenu_Cataclysm\UI_BLACKCOLOR01.BLP]]
    ,edgeFile =  DugisGuideViewer:GetBorderPath(),
                                            tile = false, tileSize = 30, edgeSize = 32,
                                            insets = { left = 10, right = 5, top = 10, bottom = 5 }})

    frame:SetBackdropColor(1,1,1,1)
	
	DugisGuideViewer.ApplyElvUIColor(frame)

    GearFinderTooltipFrame:SetPoint("TOPRIGHT",CharacterFrame, 283, 5)

    --Header
    local headerLabel = frame:CreateFontString(nil, nil, "SystemFont_Med3")
    headerLabel:ClearAllPoints()
    headerLabel:SetPoint("TOPLEFT", frame, "TOPLEFT", 20, -25)
    headerLabel:SetText("Items found:")
    headerLabel:SetTextColor(1,0.8,0)
    headerLabel:SetNonSpaceWrap(true)
    GearFinderTooltipFrame.headerLabel = headerLabel

    --Close button
    LuaUtils:loop(18, function(index)
        local button = DGF:GetCreateGuideBox(frame, 15,  -index * (boxSizeTwoLabels) - 10, "two-labels")
         tooltipItems[#tooltipItems + 1] = button

         button.topLabel:SetNonSpaceWrap(true)
         button.topLabel:SetWidth(210)
    end)

    for _, itemButton in pairs(tooltipItems) do
        itemButton:Hide()
    end

    DGF:UpdateGearButtons(tooltipItems, false)
end

function DGF:HideExtraButtonsFrame()
    DGF:SetToNormalAllMoreButtons()
    if GearFinderExtraItemsFrame then
        GearFinderExtraItemsFrame:Hide()
    end
end

local function UpdateCurrentGuideTtile()
    --Updating the tooltip

    for _, itemButton in pairs(tooltipItems) do
        itemButton:Hide()
    end

    local yIndex = 1
    for _, itemButton in pairs(itemButtons) do
        if itemButton.suggestion and itemButton.suggestedGuide == DugisGearFinderFrame.suggestedGuide.suggestedGuideTitle then
            tooltipItems[yIndex].bottomLabel:SetText(DGF:LocalizeSlot(itemButton.suggestion.item.info.equipslot))
            local itemName = ItemName(itemButton.suggestion)
            local r, g, b, hex = GetItemQualityColor(DGF.ItemQuality(itemButton.suggestion))
            tooltipItems[yIndex].topLabel:SetText("|c"..hex..itemName.."|r")
            tooltipItems[yIndex].itemTexture:SetTexture(ItemTexture(itemButton.suggestion))
            tooltipItems[yIndex]:Show()
            yIndex = yIndex + 1
        end
    end

    GearFinderTooltipFrame:SetHeight(yIndex * boxSizeTwoLabels + 25)
end

function DGF:BuildSlots()

    local equipementSlotsCopy = LuaUtils:clone(equipementSlots)
    
    local allowedWeaponSubclassIndices = GA:GetGearAdvisorScoringValues("LE_ITEM_CLASS_WEAPON")
	local allowedArmorSubclassIndices = GA:GetGearAdvisorScoringValues("LE_ITEM_CLASS_ARMOR")

    --Adding extra slots to the table
    if
        LuaUtils:isInTable("2", allowedWeaponSubclassIndices) or
        LuaUtils:isInTable("3", allowedWeaponSubclassIndices) or
        LuaUtils:isInTable("18", allowedWeaponSubclassIndices)
    then
        table.insert(equipementSlotsCopy, 4, "INVTYPE_RANGED_MERGED")
    end

    if
        LuaUtils:isInTable("1", allowedWeaponSubclassIndices) or
        LuaUtils:isInTable("5", allowedWeaponSubclassIndices) or
        LuaUtils:isInTable("6", allowedWeaponSubclassIndices) or
        LuaUtils:isInTable("8", allowedWeaponSubclassIndices) or
        LuaUtils:isInTable("10", allowedWeaponSubclassIndices)
    then
        table.insert(equipementSlotsCopy, 4, "INVTYPE_2HWEAPON")
    end

    if
	    LuaUtils:isInTable("0", allowedWeaponSubclassIndices) or
        LuaUtils:isInTable("4", allowedWeaponSubclassIndices) or
        LuaUtils:isInTable("7", allowedWeaponSubclassIndices) or
        LuaUtils:isInTable("13", allowedWeaponSubclassIndices) or
        LuaUtils:isInTable("15", allowedWeaponSubclassIndices) or
        LuaUtils:isInTable("19", allowedWeaponSubclassIndices) or
		LuaUtils:isInTable("6", allowedArmorSubclassIndices)
	then
        table.insert(equipementSlotsCopy, 4, "INVTYPE_OFFHAND_MERGED")
        table.insert(equipementSlotsCopy, 4, "INVTYPE_WEAPON_MERGED")
    end


    --todo: hide all existing slots
    
    for _, item in pairs(itemButtons) do
        item:Hide()
    end
    
    itemButtons = {}
    
    for index, value in pairs(equipementSlotsCopy) do
        local slotText = DGF:LocalizeSlot(value)

        local itemFrame = DGF:GetCreateGuideBox(DugisGearFinderFrame.ScrollChild, 0, -index * boxSize , "three-labels", index, true)
        itemFrame.topLabel:SetText(slotText)
        itemFrame.slot = value
        
        itemFrame:Show()

        itemButtons[#itemButtons + 1] = itemFrame
    end

    DGF:UpdateGearButtons(itemButtons, true)
end

function DGF:InitializeGearFinderUI()
    if InitializeGearFinderUI_initialized then
        return
    end

    local GA = DGV.Modules.GearAdvisor
    if not GA then return end

    InitializeGearFinderUI_initialized = true

    --LuaUtils.Profiler:Start("InitializeGearFinderUI")

    CreateFrame("ScrollFrame", "DugisGearFinderFrame", PaperDollFrame, "ScrollFrameTemplate")

    DugisGearFinderFrame:SetScript("OnEvent", DGF.ItemInfoEventHandler)
	DugisGearFinderFrame:RegisterEvent("GET_ITEM_INFO_RECEIVED")
	DugisGearFinderFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
	DugisGearFinderFrame:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")


    DugisGearFinderFrame:SetPoint("TOPLEFT", CharacterFrameInsetRight, "TOPLEFT", 5, -4)
    DugisGearFinderFrame:SetPoint("BOTTOMRIGHT",CharacterFrameInsetRight,"BOTTOMRIGHT",-27, 4)
    DugisGearFinderFrame:Hide()

    DugisGearFinderFrame.ScrollChild = CreateFrame("Frame", nil ,DugisGearFinderFrame)
    DugisGearFinderFrame.ScrollChild:SetSize(270, 950)
    DugisGearFinderFrame.ScrollChild:SetPoint("TOPLEFT")
    DugisGearFinderFrame:SetScrollChild(DugisGearFinderFrame.ScrollChild)

    CreateFrame("Frame", "SuggestedGearGuide", DugisGearFinderFrame.ScrollChild)
    SuggestedGearGuide:SetSize(270, 40)
    SuggestedGearGuide:SetPoint("TOPLEFT", 0, 0)
    SuggestedGearGuide:Show()

    -- Preloder
    CreateFrame("Frame", "GearFinderPreloader" , DugisGearFinderFrame, "DugisPreloader")
    GearFinderPreloader:SetSize(171, 40)
    GearFinderPreloader:SetParent(DugisGearFinderFrame)
    GearFinderPreloader:SetPoint("BOTTOMLEFT", 0, -2)
    GearFinderPreloader:Hide()

    local animationGroup = GearFinderPreloader.Icon:CreateAnimationGroup()
    animationGroup:SetLooping("REPEAT")
    local animation = animationGroup:CreateAnimation("Rotation")
    animation:SetDegrees(-360)
    animation:SetDuration(1)
    animation:SetOrder(1)
    DGF.preloaderAnimationGroup = animationGroup

    local suggestedGuide = SuggestedGearGuide:CreateFontString(nil, nil, "SystemFont_Med1")
    suggestedGuide:ClearAllPoints()
    suggestedGuide:SetPoint("TOPLEFT", SuggestedGearGuide, "TOPLEFT", 2, -2)
    suggestedGuide:SetText("Best Guide")
    suggestedGuide:SetTextColor(1,0.8,0)
    suggestedGuide:SetNonSpaceWrap(true)

    local suggestedGuideSubtitle = SuggestedGearGuide:CreateFontString(nil, nil, "SystemFont_Shadow_Small")
    suggestedGuideSubtitle:ClearAllPoints()
    suggestedGuideSubtitle:SetPoint("TOPLEFT", SuggestedGearGuide, "TOPLEFT", 7, -20)
    suggestedGuideSubtitle:SetText("-")
    suggestedGuideSubtitle:SetTextColor(1,0.8,0)
    suggestedGuideSubtitle:SetNonSpaceWrap(true)

    SuggestedGearGuide:SetScript("OnMouseDown", function()
        if suggestedGuide.suggestedGuideTitle then
            DGF:StartGuide(suggestedGuide.suggestedGuideTitle)
			print("|cff11ff11Dugi Guides: |r"..DGV:GetFormattedTitle(suggestedGuide.suggestedGuideTitle).."|cff11ff11 selected.|r")
			LuaUtils:PlaySoundFile(567458)
            --DGF:Print("Guide Loaded: ", suggestedGuide.suggestedGuideTitle)
        end
    end)

    ---------------------------------
    CreateFrame("Button", "SuggestedGuideButtonDG", SuggestedGearGuide)
    SuggestedGuideButtonDG:SetSize(28, 28)
    SuggestedGuideButtonDG:SetPoint("TOPLEFT",SuggestedGearGuide,"TOPLEFT", 145, 2)
    SuggestedGuideButtonDG:SetNormalTexture("Interface\\MINIMAP\\UI-Minimap-MinimizeButtonUp-Up.blp")
    SuggestedGuideButtonDG:SetHighlightTexture("Interface\\MINIMAP\\UI-Minimap-MinimizeButtonUp-Highlight.blp")
    SuggestedGuideButtonDG:SetPushedTexture("Interface\\MINIMAP\\UI-Minimap-MinimizeButtonUp-Down.blp")
    SuggestedGuideButtonDG:Hide()
    SuggestedGuideButtonDG:SetScript("OnClick", function(self)
        if suggestedGuide.suggestedGuideTitle then
            DGF:StartGuide(suggestedGuide.suggestedGuideTitle)
			print("|cff11ff11Dugi Guides: |r"..DGV:GetFormattedTitle(suggestedGuide.suggestedGuideTitle).."|cff11ff11 selected.|r")
			LuaUtils:PlaySoundFile(567458)
        end
    end)

    SuggestedGuideButtonDG:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(SuggestedGuideButtonDG, "ANCHOR_RIGHT")

        local guideExists = true
        if not DugisGuideViewer:isValidGuide(suggestedGuide.suggestedGuideTitle) then
            guideExists = false
        end

        if guideExists then
            GameTooltip:AddLine("Click to load the guide", 1, 1, 1)
        else
            GameTooltip:AddLine("Guide not available", 1, 0, 0)
        end

        GameTooltip:Show()
    end)

    SuggestedGuideButtonDG:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)

    SuggestedGearGuide:SetScript("OnLeave", function(self)
        if not SuggestedGuideButtonDG:IsMouseOver() then
            SuggestedGuideButtonDG:Hide()
        end

        GearFinderTooltipFrame:Hide()
    end)

    SuggestedGearGuide:SetScript("OnEnter", function(self)
        if suggestedGuideSubtitle:GetText() ~= "-" then
            SuggestedGuideButtonDG:Show()
        end

        UpdateCurrentGuideTtile()

        if tooltipItems[1]:IsShown() and not GearFinderExtraItemsFrame:IsShown() then
            GearFinderTooltipFrame:Show()
        end
    end)

    DugisGearFinderFrame.suggestedGuide = suggestedGuide
    DugisGearFinderFrame.suggestedGuideSubtitle = suggestedGuideSubtitle

    DGF:BuildSlots()

    hooksecurefunc(CharacterFrame, "Collapse", function()
        DGF:HideExtraButtonsFrame()
    end)

    hooksecurefunc("PaperDollFrame_UpdateSidebarTabs", function()
        if not DugisGearFinderFrame:IsShown() then
            DGF:SetToNormalAllMoreButtons()
            if GearFinderExtraItemsFrame then
                GearFinderExtraItemsFrame:Hide()
            end
        end
    end)

    --uaUtils.Profiler:Stop("InitializeGearFinderUI")
end


local DungeonUtils = {}

DungeonUtils.expansionLevel2Limit = {
	[0] = 50,
	[1] = 50,
	[2] = 50,
	[3] = 50,
	[4] = 50,
	[5] = 50,
	[6] = 50,
	[7] = 50,
}

-- GetLFGDungeonInfo doesn't return any data for the following dungeons so values must be added manualy.
DungeonUtils.dungeon2DungeonInfo = {    
	-- Legion                                                                          
	["dun_777"]  = {expansionLevel = 6, minLevel = 45, difficulty = 23   },  --"Assault on Violet Hold"        
	["dun_740"]  = {expansionLevel = 6, minLevel = 45, difficulty = 23   },  --"Black Rook Hold"               
	["dun_800"]  = {expansionLevel = 6, minLevel = 45, difficulty = 23   },  --"Court of Stars"                
	["dun_762"]  = {expansionLevel = 6, minLevel = 45, difficulty = 23   },  --"Darkheart Thicket"             
	["dun_721"]  = {expansionLevel = 6, minLevel = 45, difficulty = 23   },  --"Halls of Valor"                
	["dun_727"]  = {expansionLevel = 6, minLevel = 45, difficulty = 23   },  --"Maw of Souls"                  
	["dun_767"]  = {expansionLevel = 6, minLevel = 45, difficulty = 23   },  --"Neltharion's Lair"             
	["dun_726"]  = {expansionLevel = 6, minLevel = 45, difficulty = 23   },  --"The Arcway"                    
	["dun_707"]  = {expansionLevel = 6, minLevel = 45, difficulty = 23   },  --"Vault of the Wardens"          
	["dun_860"]  = {expansionLevel = 6, minLevel = 45, difficulty = 23   },  --"Return to Karazhan"            
	["dun_900"]  = {expansionLevel = 6, minLevel = 45, difficulty = 23   },  --"Cathedral of Eternal Night"    
	-- BFA                                                              
	["dun_968"]   = {expansionLevel = 7, minLevel = 50, difficulty = 23  },   --"Atal'Dazar"                  
	["dun_1001"]  = {expansionLevel = 7, minLevel = 50, difficulty = 23  },   --"Freehold"                    
	["dun_1041"]  = {expansionLevel = 7, minLevel = 50, difficulty = 23  },   --"Kings' Rest"                 
	["dun_1036"]  = {expansionLevel = 7, minLevel = 50, difficulty = 23  },   --"Shrine of the Storm"         
	["dun_1023"]  = {expansionLevel = 7, minLevel = 50, difficulty = 23  },   --"Siege of Boralus"            
	["dun_1030"]  = {expansionLevel = 7, minLevel = 50, difficulty = 23  },   --"Temple of Sethraliss"        
	["dun_1012"]  = {expansionLevel = 7, minLevel = 50, difficulty = 23  },   --"The Motherlode!!"            
	["dun_1022"]  = {expansionLevel = 7, minLevel = 50, difficulty = 23  },   --"The Underrot"                
	["dun_1002"]  = {expansionLevel = 7, minLevel = 50, difficulty = 23  },   --"Tol Dagor"                   
	["dun_1021"]  = {expansionLevel = 7, minLevel = 50, difficulty = 23  },   --"Waycrest Manor"              
}

-- BFA dungeons have different min levels per faction
DungeonUtils.dungeon2MinLevel = { 
	[1672] = { Alliance = 10, Horde = 10 }, -- Freehold
	[1774] = { Alliance = 35, Horde = 35 }, -- Shrine of the Storm
	[1705] = { Alliance = 25, Horde = 25 }, -- Waycrest Manor
	[1778] = { Alliance = 30, Horde = 30 }, -- Tol Dagor
	[1668] = { Alliance = 10, Horde = 10 }, -- Atal´dazar
	[1694] = { Alliance = 35, Horde = 35 }, -- Temple of Sethraliss
	[1777] = { Alliance = 25, Horde = 25 }, -- The Underrot
	[1707] = { Alliance = 30, Horde = 30 }, -- The Motherlode!!
}

local playerFaction = UnitFactionGroup("player")

function DungeonUtils.GetDungeonInfo(id)
    if not id then return end

    if type(id)=="string" and not DungeonUtils.dungeon2DungeonInfo[id] then return end 

    local _, typeID, minLevel, expansionLevel, difficulty, isHoliday
    
    if DungeonUtils.dungeon2DungeonInfo[id] then
        local dungeonInfo = DungeonUtils.dungeon2DungeonInfo[id]
        expansionLevel, minLevel, difficulty = dungeonInfo.expansionLevel, dungeonInfo.minLevel, dungeonInfo.difficulty
    else
        typeID, _, minLevel, _, _, _, _, expansionLevel, _, _, difficulty, _, _, isHoliday = GetLFGDungeonInfo(id)
    end

    if expansionLevel ~= nil and typeID ~= 4 then
        local result = {}

        result.id = id
        result.name = name
        result.difficulty = difficulty
        result.isHoliday = isHoliday
        if DungeonUtils.dungeon2MinLevel[id] then
            result.minLevel = DungeonUtils.dungeon2MinLevel[id][playerFaction]
        else
            result.minLevel = minLevel
        end
        result.expansionLevel = expansionLevel
        result.maxScaleLevel = math.max(DungeonUtils.expansionLevel2Limit[expansionLevel] or 0, result.minLevel or 0) 

        return result
    end
end

function DungeonUtils.IsMythicDungeon(itemInfo)
    local originalItemInfo = itemInfo.info.originalItemInfo

	local dungeon_ = originalItemInfo.dungeon
	if dungeon_ == 0 then 
        dungeon_ = "dun_"..originalItemInfo.instanceId
    end

	local dungeon = DungeonUtils.GetDungeonInfo(dungeon_)
    
	if not dungeon or dungeon.expansionLevel > GetExpansionLevel() or dungeon.isHoliday then 
        return false 
    end
    
	return dungeon.difficulty == 23 and (not MAX_PLAYER_LEVEL_TABLE or dungeon.maxScaleLevel == MAX_PLAYER_LEVEL_TABLE[GetAccountExpansionLevel()])
end


-- Dugi, fancy seeing you here
DGF.mythicKeystones = {
	 [1] = {"4819:1512", "4819:1512"},  -- 340   340
	 [2] = {"5018:1517", "5018:1512"},  -- 345    340
	 [3] = {"5019:1517", "5019:1512"},  -- 345    340
	 [4] = {"5020:1522", "5020:1532"},  -- 350    355
	 [5] = {"5021:1527", "5021:1532"},  -- 355    355
	 [6] = {"5022:1527", "5022:1532"},  -- 355    355
	 [7] = {"5023:1532", "5023:1542"},  -- 360    370
	 [8] = {"5024:1537", "5024:1542"},  -- 365    370
	 [9] = {"5025:1537", "5025:1542"},  -- 365    370
	 [10] = {"5026:1542", "5026:1557"}, -- 370   385
}


-- {...} Contains pairs:  position1, value1, position2, value2 ...
function DGF.SetValuesForItemLink(itemlink, ...)	
	local _, itemstring = itemlink:match("(.*)item:([0-9-:]*)(.*)")
	if not itemstring then 
        return itemlink
    end

	local itemLinkParts = {strsplit(":", itemstring)}
    
	for i = 2, 13 do 
        itemLinkParts[i] = itemLinkParts[i] or "" 
    end 

	for i = 1, select("#", ...), 2 do 
        itemLinkParts[select(i, ...)] = select(i + 1, ...) 
    end

	itemlink = table.concat(itemLinkParts, ":")
    
	wipe(itemLinkParts)

	return "item:"..itemlink 
end

function DGF.RemoveBonusFromItemLink(itemlink,bonusid)
	if not itemlink or  not bonusid then 
        return itemlink
    end

	local itemlink = DGF.SetValuesForItemLink(itemlink)
	itemlink = itemlink:gsub(":"..bonusid, "")
	local itemLinkParts = {strsplit(":", itemlink)}

	itemLinkParts[14] = (tonumber(itemLinkParts[14]) or 1) - 1
	table.insert(itemLinkParts, bonuses)
	return table.concat(itemLinkParts, ":")
end

function DGF.AddBonusForItemLink(itemlink, bonuses)
    if not itemlink or not bonuses then 
        return itemlink
    end
    
    -- clean up decorations
    local itemlink = DGF.SetValuesForItemLink(itemlink)
    local itemLinkParts = {strsplit(":", itemlink)}

    local _, count = string.gsub(bonuses, ":", "")
    itemLinkParts[14] = (itemLinkParts[14] or 0) + count + 1
    
    table.insert(itemLinkParts, bonuses)
    return table.concat(itemLinkParts, ":")
end


function DGF.SetBonusesAndLevelForItemLink(itemInfo)
    local itemlink = itemInfo.info.itemlink
    
    local mythicDifficulty = LuaUtils:Round(DugisGuideViewer:UserSetting(DGV_MYTHIC_DIFFICULTY) or 1)
    
    local mythic = DungeonUtils.IsMythicDungeon(itemInfo)
	local mythicBonus = DGF.mythicKeystones[mythicDifficulty]

    if mythic == true and mythicBonus then
        itemlink = DGF.RemoveBonusFromItemLink(itemlink, 3524)
        if C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItemByID(itemlink) then
          itemlink = DGF.AddBonusForItemLink(itemlink, mythicBonus[2])
        else
           itemlink = DGF.AddBonusForItemLink(itemlink, mythicBonus[1])
        end
    end
    
    return itemlink
end

function DGF:GetItemInfoById(itemId, threading, originalItemInfo)
    local itemlink = ""
    local numericItemId = tonumber(itemId)
    if numericItemId == nil or numericItemId == 0 then
        itemlink = itemId
    else
        itemlink="item:"..itemId..":0:0:0:0:0:0:0:0:0:0:0"
    end

    local name,link,quality,ilevel, reqlevel, class, subclass, maxstack, equipslot, texture, vendorprice = LuaUtils.GetItemInfo_dugi(itemlink, threading)

    reqlevel = StatLogic:GetRequiredPlayerLevel(itemlink, threadingMode) or reqlevel
        
    --Min
    local reqlevelByQuest = nil

    if not name then
        return nil
    end

    -- Loads stats into item.stats
    local stats = C_Item.GetItemStats(itemlink)
    if not stats then
        return nil
    end

    if reqlevel == nil or reqlevel == 0 or (reqlevel == 1 and ilevel > 10) then
       local relatedQuests = DGF.gearId2Quests_map[itemId]

       --This condition is needed because GetItemInfoById is used also to get information about possesed gears (not nesseserly avaliable in GearInfoData lua file)
       if relatedQuests then
           local requiredLevelbyQuestMin = 0
           for questId,_ in pairs(relatedQuests) do
               local levels = DGV.ReqLevel[questId]
               if levels then
                   if reqlevelByQuest == nil then
                        reqlevelByQuest = levels[2]
                   else
                        if levels[2] < reqlevelByQuest then
                            reqlevelByQuest = levels[2]
                        end
                   end
               end
           end
       end
    end

   -- DGF:Print(reqlevelByQuest)
    local item

    if DugisGearFinder.optimized then
        item = {
                info = {
                --original item id
                itemid = itemId
                , name = name
                , reqlevel = reqlevel
                , reqlevelByQuest = reqlevelByQuest
                , class = class
                , quality = quality
                , subclass = subclass
                , texture = texture
                , equipslot = equipslot
                , itemlink = itemlink
                },
                tooltip={}
            }
    else
        item = {
            info = {
            --original item id
            itemid = itemId
            , name = name
            , itemlink = itemlink
            , quality = quality
            , ilevel = ilevel
            , reqlevel = reqlevel
            , reqlevelByQuest = reqlevelByQuest
            , class = class
            , subclass = subclass
            , texture = texture
            , equipslot = equipslot
            , vendorprice = vendorprice
            },
            stats = stats,
            tooltip={}
        }
    end
    
    if originalItemInfo then
        item.info.originalItemInfo = originalItemInfo
    else
        local infos = DGF.gearId2GearInfos_map[itemId] or DGF.gearId2GearInfos_map[itemlink]
        if infos then
            item.info.originalItemInfo = infos[1]
        end
    end

    return item
end

--godawful: ItemIterator is designed to be used within the confines of an autoroutine and not otherwise, or it will not preempt or pool tables properly
--in fact, creation and use of anything called "thread" or "threading" everywhere is naive and ignores existing asynchronous patterns
function DGF:GetAllOwnedItems(onlyBelowPlayerLevel)
    local result = {}

    local itemInvariant = DugisGuideViewer:GetCreateTable()
    itemInvariant.first = itemMustWin
    itemInvariant.skip = skip

    for control, iteratedItemLink in GA.ItemIterator, itemInvariant do
        local itemInfo = DGF:GetItemInfoById(iteratedItemLink, true)

        if itemInfo == nil then
            return
        end
        
		LuaUtils:RestIfNeeded(true)
        
        --SCORE
        local level = UnitLevel("player")

        local uniqueInventorySlot = GetDefaultUniqueInventorySlot(itemInfo.info.equipslot)

        local score = CalculateScoreForGearFinder(itemInfo.info.itemlink, GetSpecialization(), nil, level, uniqueInventorySlot)
        
        local lvl = itemInfo.info.reqlevel

        if itemInfo.info.reqlevelByQuest ~= nil then
            lvl = itemInfo.info.reqlevelByQuest
        end

        if (not onlyBelowPlayerLevel) or (level >= lvl ) then
            result[#result + 1] = {link=iteratedItemLink, itemId = itemInfo.info.itemid, item = itemInfo, score = score}
        end

       -- result[#result + 1] =  itemInfo
    end

    --LuaUtils.Profiler:Stop("GetAllOwnedItems")

    return result
end

--{itemId1 = true, itemid2 = true, ...}
function DGF:GetAllOwnedItemIds(noThread)
    local result = {}

    for _, value in pairs(allOwnedItems) do
        result[value.itemId] = true
    end

    return result
end

--/run GetTheBestOwnedItemBySlot("INVTYPE_WRIST")
--In case slot is a "double" slot then this function should return then next after the best item if exists.
--This way in case that in slotX1 there is item with score 100 and in slotX2 item is with score 150 but the considered item has score 120 
--it will suggest that new one to be on slotX1
function DGF:GetTheBestOwnedItemBySlot(slot, onlyBelowPlayerLevel)
    if #allOwnedItems == 0 then
        return nil
    end

    local theBest = nil
    local secondTheBest = nil
    
    local isDoubleSlot = slot == "INVTYPE_WEAPON_MERGED" or slot == "INVTYPE_FINGER" or slot == "INVTYPE_TRINKET"
    
    DGF:Print("-------------------------")
    DGF:Print("POSSESSED items for ",slot," slot:")
    for index, item in pairs(allOwnedItems) do
        if DGF:Slot2VirtualSlot(item.item.info.equipslot) == slot then
            DGF:Print("itemId:", item.item.info.itemid, " SCORE:", item.score)
        end

        if DGF:Slot2VirtualSlot(item.item.info.equipslot) == slot and (theBest == nil or item.score > theBest.score) then
            theBest = item
        end
    end
    
    if theBest then
        for index, item in pairs(allOwnedItems) do
            if DGF:Slot2VirtualSlot(item.item.info.equipslot) == slot and (not secondTheBest or item.score > secondTheBest.score) and item.score <= theBest.score and theBest ~= item then
                secondTheBest = item
            end
        end
    end

    if not theBest then
        DGF:Print("-none")
    end

    if theBest then
        DGF:Print("AMONG POSSESSED THE BEST IS:"..theBest.link, " itemId:", theBest.item.info.itemid, " (SCORE: ", theBest.score, ")")
    else
        DGF:Print("THE BEST WAS NOT FOUND AMONG POSSESSED")
    end

    if secondTheBest and secondTheBest ~= theBest and isDoubleSlot then
        return secondTheBest
    else
         return theBest
    end
  
end

function DGF:CanBeGearObtaind(gearId)
    if not DGF.gearId2DroppedByBoss_map[gearId] then
    
        if DugisGearFinder.gearId2LevelRange[gearId] then
            return true
        end
        
        DGF:UpdateQuestsCompletedCache()
    
        --Checking if all quests are not completed
        local allRelatedQuests = DGF.gearId2Quests_map[gearId]
        local allQuestsCompleted = true

        if allRelatedQuests then
            for questId, _ in pairs(allRelatedQuests) do
                if not DGF:IsQuestCompleted(questId) then
                    allQuestsCompleted = false
                end
            end
        end

        if allQuestsCompleted then
            return false
        end
    end

    return true
end

--Returns the score of the guide (takes into account only not owned items)
function DGF:ScoreGuide(guideTitle, noThread, yields)
    local guideScore = 0
	
	if DGV.GearFinderScoreGuide_cache_v1 == nil then
		DGV.GearFinderScoreGuide_cache_v1 = {}
	end

    local gearIds = DGF.guideTitle2GearIds_map[guideTitle] or {}
	
	local gearControlSum = 0;
	
    for _, gearId in pairs(gearIds) do
		gearControlSum = gearControlSum + (tonumber(gearId) or 0)
	end
	
	if DGV.GearFinderScoreGuide_cache_v1[guideTitle..gearControlSum] then
		return DGV.GearFinderScoreGuide_cache_v1[guideTitle..gearControlSum]
	end

    if not yields then
        yields = 0
    end

    local level = UnitLevel("player")

    local itemId2IsOwned = DGF:GetAllOwnedItemIds(noThread)

    for _, gearId in pairs(gearIds) do

        local shouldBeConsidered = true

        if itemId2IsOwned[gearId] == true then
            shouldBeConsidered = false
        end

        if not DGF:CanBeGearObtaind(gearId) then
            shouldBeConsidered = false
        end

       if shouldBeConsidered then
           local itemInfo = DGF:GetItemInfoById(gearId, noThread ~= true)

            if itemInfo ~= nil then
                if not noThread then
					LuaUtils:RestIfNeeded(true)
                    LuaUtils:WaitForCombatEnd()
                end

               local uniqueInventorySlot = GetDefaultUniqueInventorySlot(itemInfo.info.equipslot)
               local score = CalculateScoreForGearFinder(DGF.SetBonusesAndLevelForItemLink(itemInfo), GetSpecialization(), nil, level, uniqueInventorySlot)
               guideScore = guideScore + score
           else
           end
       end
    end

	DGV.GearFinderScoreGuide_cache_v1[guideTitle..gearControlSum] = guideScore
    return guideScore
end

--gearGuidesSet - set of guides to analyse. If nil then = DGF.allGearGuides
function DGF:GetTheBestGuide(gearGuidesSet, noThread, yields)
    if not gearGuidesSet then
        gearGuidesSet = DGF.allGearGuides
    end

    local theBestTitle, theBestScore, theBestIndex = nil, nil, nil
    if gearGuidesSet then
        for index, guideTitle in pairs(gearGuidesSet) do

            LuaUtils.Profiler:Start("ScoreGuide")
            local guideScore = DGF:ScoreGuide(guideTitle, noThread, yields)
            LuaUtils.Profiler:Stop("ScoreGuide")

            if theBestScore == nil or guideScore > theBestScore then
                theBestScore = guideScore
                theBestTitle = guideTitle
                theBestIndex = index
            end
        end
    end

    return theBestTitle, theBestScore, theBestIndex
end

--Returns theBestTitle, related gearInfo
function DGF:GetTheBestGuideForGearId(gearId, noThread, yields)
    local gearInfos = DGF.gearId2GearInfos_map[gearId]
    local guidesList = {}

    local filters = DGF:GetDifficultyFilters()

    for _, gearInfo in pairs(gearInfos) do
        --Filtering by difficulty:
        local passedByFilter = false
        local difficulty = gearInfo.dungeonDifficulty

        if difficulty == nil or filters[difficulty] then
            guidesList[#guidesList + 1] = gearInfo.guideTitle
        end
    end
    local theBestTitle, theBestScore, theBestIndex = DGF:GetTheBestGuide(guidesList, noThread, yields)

    if theBestTitle then
        return theBestTitle, gearInfos[theBestIndex]
    end
end

local allowedWeaponClassNames = {}
local allowedArmoClassNames = {}


local function addItem2itemsBySlot(item)

    local equipslot = item.info.equipslot

    if IsEquipment(equipslot) then
        local virtualSlot = DGF:Slot2VirtualSlot(equipslot)

        if not DGF.itemsBySlot[virtualSlot] then
            DGF.itemsBySlot[virtualSlot] = {}
        end

        --Filtering items by class and specialization
        local canBePassed = true

        if item.info.class == "Armor" then
            if not LuaUtils:isInTable(item.info.subclass, allowedArmoClassNames) then
                --TODO
                canBePassed = false
            else
            end
        end
        if item.info.class == "Weapon" then
            if not LuaUtils:isInTable(item.info.subclass, allowedWeaponClassNames) then
                --TODO
                canBePassed = false
            else
            end
        end

        if canBePassed then
            DGF.itemsBySlot[virtualSlot][item.info.itemid] = item
        else
        end
    else
    end

    
end

--Updating suggested gears in case settings were changed
function DGV.OnGearFinderSettingsChanged()
    DGF:CacheItemsForGearFinder()
end

--Updates data needed by GearFinder for calculations. It collects for example current owned gears.
function DGF:UpdateDynamicDataForGearFinder()
    if LuaUtils:ThreadInProgress("UpdateDynamicDataForGearFinder") or LuaUtils:ThreadInProgress("CacheItemsForGearFinder") or LuaUtils:ThreadInProgress("SetSuggestedItemGuides")  then
        return
    end

    LuaUtils:CreateThread("UpdateDynamicDataForGearFinder", function() --godawful: anonymous function allocation in hot path
        --If it is before GearAdvisor initialization return
        while not GA.ItemIterator or UnitAffectingCombat("player") or 
        LuaUtils:ThreadInProgress("SetSuggestedItemGuides") or LuaUtils:ThreadInProgress("CacheItemsForGearFinder") do
            LuaUtils:RestIfNeeded(true)
        end
        
        itemId2IsOwned = {}
        --godawful: ItemIterator misuse
        --godawful: this loop is redundant since GetAllOwnedItems will do so as well
        for control, iteratedItemLink in GA.ItemIterator, itemInvariant do
            if iteratedItemLink then
                itemId2IsOwned[iteratedItemLink] = true
            end
        end
        
         
        local allOwnedItems_ = DGF:GetAllOwnedItems()

        if allOwnedItems_ then
            allOwnedItems = allOwnedItems_
        end
        
        
        --- Updating Allowed subclasses
        local GA = DGV.Modules.GearAdvisor
        
        if GA and DugisGuideViewer:IsModuleLoaded("GearAdvisor") then
            local allowedWeaponSubclassIndices = GA:GetGearAdvisorScoringValues("LE_ITEM_CLASS_WEAPON")
            local allowedArmorSubclassIndices = GA:GetGearAdvisorScoringValues("LE_ITEM_CLASS_ARMOR")

            local allWaponSubclasses = GetAuctionItemSubClasses_dugis(1)
            local allArmorSubclasses = GetAuctionItemSubClasses_dugis(2)

            allowedWeaponClassNames = {}
            allowedArmoClassNames = {}

            for _, index in pairs(allowedWeaponSubclassIndices) do
                local index = tonumber(index)
                allowedWeaponClassNames[#allowedWeaponClassNames + 1] = allWaponSubclasses[index]
            end

            for _, index in pairs(allowedArmorSubclassIndices) do
                local index = tonumber(index)
                allowedArmoClassNames[#allowedArmoClassNames + 1] = allArmorSubclasses[index]
            end
        end        
        
    end)
end

function DGF.ItemInfoEventHandler(self, event, ...)
    if event == "GET_ITEM_INFO_RECEIVED" then
        local itemId = ...
        
        --Protecting from adding not needed items
        if not DGF.gearId2GearInfos_map[itemId] and not itemId2IsOwned[itemId] then
            return
        end

        local item = DGF:GetItemInfoById(itemId, false)

        if item then
            addItem2itemsBySlot(item)
        end
    end
    
    if event == "PLAYER_EQUIPMENT_CHANGED" then
        DugisGearFinder:UpdateDynamicDataForGearFinder()
    end 
    
    if event == "PLAYER_SPECIALIZATION_CHANGED" then
		DGF:BuildSlots()
		if GetSpecialization() ~= DugisGuideViewer.specializaton then
			DGV.OnGearFinderSettingsChanged()
			DugisGuideViewer.specializaton = GetSpecialization()
		end
    end
end

function DGF:MakeSuggestions()
    if GearFinderPreloader then
      GearFinderPreloader:Hide()
      DGF.preloaderAnimationGroup:Stop()
    end

    LuaUtils:CreateThread("SetSuggestedItemGuides", function()
            if GearFinderPreloader then
                GearFinderPreloader:Show()
                DGF.preloaderAnimationGroup:Play()
            end
            DGF:SetSuggestedItemGuides()
        end,
        function()
            if GearFinderPreloader then
                GearFinderPreloader:Hide()
                DGF.preloaderAnimationGroup:Stop()
            end
        end
    )
end


local cacheIntensity = 50
local CacheItemsForGearFinder_invoked = false
local CacheItemsForGearFinderIsInQueue = false

function DGF:CacheItemsForGearFinder()
    --Preventing invoked caching many times on settings change
    if CacheItemsForGearFinderIsInQueue then
        return
    end
    
    LuaUtils:CreateThread("WaitForCacheItemsForGearFinderEnd", function()
    
        GearFinderPreloader:Show()
        DGF.preloaderAnimationGroup:Play()
    
        CacheItemsForGearFinderIsInQueue = true
        while LuaUtils:ThreadInProgress("CacheItemsForGearFinder") do
            coroutine.yield()
        end
		
		if allowedWeaponClassNames == nil or allowedArmoClassNames == nil or (#allowedWeaponClassNames == 0 and #allowedArmoClassNames == 0) then
			DugisGearFinder:UpdateDynamicDataForGearFinder()
		end	

		while LuaUtils:ThreadInProgress("UpdateDynamicDataForGearFinder") do
            coroutine.yield()
        end

        LuaUtils:CreateThread("CacheItemsForGearFinder", function()
            while  LuaUtils:ThreadInProgress("UpdateDynamicDataForGearFinder") or LuaUtils:ThreadInProgress("SetSuggestedItemGuides")  do
                coroutine.yield()
            end
            CacheItemsForGearFinderIsInQueue = false
        
            if CacheItemsForGearFinder_invoked then
                return
            end

            CacheItemsForGearFinder_invoked = true

            DGV.InitializeGearFinderData()



            local allGearsAmount = #DGF.allGearIds

            for i=1, #DGF.allGearIds do
                LuaUtils:RestIfNeeded(true)

                local itemInfo = DGF.allGearIds[i]

                if type(itemInfo.gearId) == "string" then
                    local item = DGF:GetItemInfoById(itemInfo.gearId, true, itemInfo)
                    
                    --Speeding up the caching
                    for j = i, i + 10 do
                        if DGF.allGearIds[j] then
                            C_Item.GetItemInfo(DGF.allGearIds[j].gearId)
                        end
                    end
                    
                    if item then
                        addItem2itemsBySlot(item)
                    end
                else
                    local name = C_Item.GetItemInfo(itemInfo.gearId)

                    --Available in game cache
                    if name then
                        local item = DGF:GetItemInfoById(itemInfo.gearId, true, itemInfo)
                        if item then
                            addItem2itemsBySlot(item)
                        end
                    end
                end

                local progress = (LuaUtils:Round(100 * i/allGearsAmount/3, 0) * 3)
                if progress == 0 then progress = 3 end
                GearFinderPreloader.TexWrapper.Text:SetText("Preparing "..progress.."%..")
            end

            return "success"

        end,
        --on end
        function()


            DGF:MakeSuggestions()
        end
        , 1, 0.01)
            
        
    end)

end

function DGF:GetSuggesedGearBySlot(invslot, yields, slotButton)

    if not yields then
        yields = 1
    end

    if not DGF.itemsBySlot[invslot] then
        return nil
    end

    local ownedItemsIds = {}

    for index, value in pairs(allOwnedItems) do
       ownedItemsIds[value.item.info.itemid] = true
    end

    local level = UnitLevel("player")
    local levelAbove49 = (level > 49)  --no longer needed with patch 7.0 but still handy for armor specilization bonus
    local _, characterClass = UnitClass("player")

    if not DGF.itemsBySlot[invslot] then
        return
    end
    local itemsBelowPlayerLevel = {}
    local itemsAbovePlayerLevel = {}	-- upgrades with restrictions

    local theBestForSlot = DGF:GetTheBestOwnedItemBySlot(invslot, true)

    local amount = 0

    for _, _ in pairs(DGF.itemsBySlot[invslot]) do
        amount = amount + 1
    end

    local filters = DGF:GetDifficultyFilters()

    slotButton.middleLabel:SetText("Loading...")
    slotButton.itemTexture:Hide()

    local i = 0
    for itemId, item in pairs(DGF.itemsBySlot[invslot]) do
            i = i + 1
            if amount > 0 then
                slotButton.bottomLabel:SetText("|cFF888888"..(LuaUtils:Round(100 * i/amount/5, 0) * 5).."%|r")
            end

            local uniqueInventorySlot = GetDefaultUniqueInventorySlot(item.info.equipslot)

            LuaUtils:RestIfNeeded(true)

            local tooLowPlayerLevel = ((item.info.reqlevel ~= nil and item.info.reqlevel ~= 0 and level < item.info.reqlevel)
            or (item.info.reqlevelByQuest ~= nil and item.info.reqlevelByQuest ~= 0 and level < item.info.reqlevelByQuest))

            --Filtering already owned:
            local itemAlreadyOwned = ownedItemsIds[item.info.itemid]
            --Filtering by difficulty:
            local passedByFilter = false
            local difficulties = DGF.gearId2PossibleDifficulties_map[item.info.itemid]
            if difficulties == nil then
                difficulties = {}
            end
            for difficultyId, v in pairs(difficulties) do
                if difficultyId == "empty-difficulty" or filters[difficultyId] then
                    passedByFilter = true
                    break
                end
            end
            
            local passedByLevelRange = true
            
            if DugisGearFinder.gearId2LevelRange[itemId] then
                local levelMin = DugisGearFinder.gearId2LevelRange[itemId][1]
                local levelMax = DugisGearFinder.gearId2LevelRange[itemId][2]
                
                if levelMax and level > levelMax then
                    passedByLevelRange = false
                end
                
                if levelMin and level < levelMin then
                    passedByLevelRange = false
                end
            end            

            local passedByArmorSpecBonusExclusion = true
            local slot = DGF:Slot2VirtualSlot(item.info.equipslot)

            if levelAbove49 and IsArmorSpecSlot(slot) then --no longer needed with patch 7.0 but still handy for armor specilization bonus
                if (characterClass == "SHAMAN" or characterClass == "HUNTER") and item.info.subclass ~= "Mail" then
                    --score = score - 50
                    --Please comment out the line below in case the item should not be completely excluded but just positioned 50 scores below. The value 50 can be also adjusted.
                    passedByArmorSpecBonusExclusion = false
                end
                if (characterClass == "PALADIN" or characterClass == "WARRIOR") and item.info.subclass ~= "Plate" then
                    --score = score - 50
                    --Please comment out the line below in case the item should not be completely excluded but just positioned 50 scores below. The value 50 can be also adjusted.
                    passedByArmorSpecBonusExclusion = false
                end
                if (characterClass == "DRUID" or characterClass == "ROGUE" or characterClass == "MONK") and item.info.subclass ~= "Leather" then
                    --score = score - 50
                    --Please comment out the line below in case the item should not be completely excluded but just positioned 50 scores below. The value 50 can be also adjusted.
                    passedByArmorSpecBonusExclusion = false
                end			
            end

            --Filtering by option "Search for quest gears"
            local passedByQuestsGears = itemId ~= nil and ((DGF.gearId2Quests_map[itemId] and (DGF.gearId2Quests_map == nil or (
            DGV:UserSetting(DGV_GEARS_FROM_QUEST_GUIDES) == true
            or not DGF.gearId2Quests_map[itemId].amountMoreThan0)) ) or (DGF.gearId2isReputation and DGF.gearId2isReputation[itemId] == true) )  

            --Filtering by already existing the best score

            if not itemAlreadyOwned
            and DGF:CanBeGearObtaind(itemId)
            and passedByFilter
            and passedByQuestsGears
            and passedByArmorSpecBonusExclusion
            and passedByLevelRange
            then
				LuaUtils:RestIfNeeded(true)
				LuaUtils:WaitForCombatEnd()
            
                local score = CalculateScoreForGearFinder(DGF.SetBonusesAndLevelForItemLink(item), GetSpecialization(), nil, level, uniqueInventorySlot)
                
                local rightScore = true
                if theBestForSlot and theBestForSlot.score >= score then
                  --todo test
                     rightScore = false
                end

                if rightScore then
                    if tooLowPlayerLevel then
                        tinsert (itemsAbovePlayerLevel,{item=item,score=score})
                    else
                        --if rightScore then
                        tinsert (itemsBelowPlayerLevel,{item=item,score=score})
                      --  end
                    end
                end
            else
        end
    end

    --Sorting itemsBelowPlayerLevel by score (from hight to low)  {highest, ..., lowest}
    table.sort(itemsBelowPlayerLevel, function(a,b)
        return a.score > b.score
    end)

     --Sorting itemsAbovePlayerLevel by required level  Sorted from low to high
    table.sort(itemsAbovePlayerLevel, function(a,b)
        local lvla = a.item.info.reqlevel
        local lvlb = b.item.info.reqlevel

        if a.item.info.reqlevelByQuest ~= nil then
            lvla = a.item.info.reqlevelByQuest
        end

        if b.item.info.reqlevelByQuest ~= nil then
           lvlb = b.item.info.reqlevelByQuest
        end

        if lvla == lvlb then
            return a.score > b.score
        end

        return lvla < lvlb
    end)
    DGF:Print("-------------------------")
    DGF:Print("--SET  \"Below\" of items: ",invslot,"  (please see google doc, possessed are excluded, limit "..listItemsLimit.." items, so some may not be displayed)")
    for i=1, #itemsBelowPlayerLevel do
        if i < listItemsLimit then
            DGF:Print("#",i, invslot,  ItemName(itemsBelowPlayerLevel[i]), itemsBelowPlayerLevel[i].item.info.itemid, "SCORE: ", itemsBelowPlayerLevel[i].score, " Req lvl:", itemsBelowPlayerLevel[i].item.info.reqlevel, " Req lvl by guide:", itemsBelowPlayerLevel[i].item.info.reqlevelByQuest  )
        end
    end
    DGF:Print("--SET \"Above\" of items: ", invslot,"  (please see google doc, possessed are excluded, limit "..listItemsLimit.." items, so some may not be displayed)")
    for i=1, #itemsAbovePlayerLevel do
        if i < listItemsLimit then
            DGF:Print("#",i, invslot, ItemName(itemsAbovePlayerLevel[i]), itemsAbovePlayerLevel[i].item.info.itemid, "SCORE: ", itemsAbovePlayerLevel[i].score, " Req lvl:", itemsAbovePlayerLevel[i].item.info.reqlevel, " Req lvl by guide:", itemsAbovePlayerLevel[i].item.info.reqlevelByQuest   )
        end
    end

    -- STEP 1 (https://docs.google.com/document/d/1pPHzCCmkcLuFGGat8qZJTc54uPpldOFJWSlH8aoa610/edit)

    if #itemsBelowPlayerLevel > 0 then
        --todo descruption - log
        DGF:Print("STEP 1 (the best possessed for slot doesn't exist)...")
        DGF:Print("**** SUGGESTED GUIDE: ",invslot, ItemName(itemsBelowPlayerLevel[1]), itemsBelowPlayerLevel[1].item.info.itemid, "  Req lvl:", itemsBelowPlayerLevel[1].item.info.reqlevel, " Req lvl by guide:", itemsBelowPlayerLevel[1].item.info.reqlevelByQuest )

        local top5Suggestions = {}
        for i=1, 5 do
            if itemsBelowPlayerLevel[i] then
                top5Suggestions[#top5Suggestions + 1] = itemsBelowPlayerLevel[i]
            end
        end

        if #top5Suggestions < 5 and #itemsAbovePlayerLevel > 0 then
            for i = 1, 5 do
                if #top5Suggestions < 5 and itemsAbovePlayerLevel[i] then
                    top5Suggestions[#top5Suggestions + 1] = itemsAbovePlayerLevel[i]
                end
            end
        end

        return itemsBelowPlayerLevel[1], top5Suggestions
    end

    -- STEP 2
    if #itemsAbovePlayerLevel > 0 then
        DGF:Print("STEP 2")
        DGF:Print("**** SUGGESTED GUIDE: ", invslot,  ItemName(itemsAbovePlayerLevel[1])..(" Req lvl: "..itemsAbovePlayerLevel[1].item.info.reqlevel.." Req lvl by guide: ".. (itemsAbovePlayerLevel[1].item.info.reqlevelByQuest or "nil")), itemsAbovePlayerLevel[1].item.info.itemid)

        local top5Suggestions = {}
        for i=1, 5 do
            if itemsAbovePlayerLevel[i] then
                top5Suggestions[#top5Suggestions + 1] = itemsAbovePlayerLevel[i]
            end
        end
        return  itemsAbovePlayerLevel[1], top5Suggestions
    end
end

local inSetSuggestedItemGuides = false

function DGF:SetSuggestedItemGuides()

    if inSetSuggestedItemGuides then
        return
    end

    inSetSuggestedItemGuides = true
    DGF:HideExtraButtonsFrame()

    LuaUtils.Profiler:Start("SetSuggestedItemGuides")
    ----------------to be optimized

    GearFinderPreloader.TexWrapper.Text:SetText("Searching for gears")

    local yields = CALCULATIONS_TIME_FOR_FILLED_CACHE
    if not DugisCharacterCache.CalculateScore_cache_v12 or LuaUtils:isTableEmpty(DugisCharacterCache.CalculateScore_cache_v12) then
        yields = CALCULATIONS_TIME_FOR_EMPTY_CACHE
    end

    LuaUtils.Profiler:Start("Searching for gears")
    for _, slotButton in pairs(itemButtons) do

        local slot = slotButton.slot
        local suggestedItem, top5suggestions = DGF:GetSuggesedGearBySlot(slot, yields, slotButton)

        slotButton.suggestedGuide = nil

        if suggestedItem then
            local theBestGuide = DGF:GetTheBestGuideForGearId(suggestedItem.item.info.itemid, false, yields)
            local shortName = ItemName(suggestedItem)
            local slotText = DGF:LocalizeSlot(slotButton.slot)

            local requiredLevelInfo = ""

            if not DGF:PlayerHasEnoughLevel(suggestedItem) then

                local lvl = suggestedItem.item.info.reqlevel

                if suggestedItem.item.info.reqlevelByQuest ~= nil then
                    lvl = suggestedItem.item.info.reqlevelByQuest
                end

                if string.len(slotText) > 15 then
                    requiredLevelInfo = " |c00FF0000@L"..lvl.."|r"
                else
                    requiredLevelInfo = " |c00FF0000@ Level "..lvl.."|r"
                end
            end

            local r, g, b, hex = GetItemQualityColor(suggestedItem.item.info.quality)
            slotButton.middleLabel:SetText("|c"..hex..suggestedItem.item.info.name.."|r")
            local formattedTitle = theBestGuide
            if DugisGuideViewer.GetFormattedTitle then
               formattedTitle = DugisGuideViewer:GetFormattedTitle(theBestGuide)
            end

            slotButton.suggestedGuide = theBestGuide
            if formattedTitle then
                slotButton.bottomLabel:SetText("|cFFFFFFFF" .. formattedTitle .. "|r")
            end
            slotButton.bottomLabel:SetWidth(130)
            slotButton.middleLabel:SetWidth(130)

            slotButton.itemTexture:SetTexture( ItemTexture(suggestedItem))
            slotButton.itemTexture:Show()

            slotButton.topLabel:SetText(slotText..requiredLevelInfo)

            slotButton.suggestion = suggestedItem
            slotButton.top5suggestions = top5suggestions
            slotButton.middleLabel:Show()
        else
            local slotText = DGF:LocalizeSlot(slot)
            slotButton.topLabel:SetText(slotText)
            slotButton.bottomLabel:SetText("|cFF888888Cannot find better gear|r")
            slotButton.middleLabel:Hide()
            slotButton.top5suggestions = {}
            slotButton.suggestion = nil
            slotButton.itemTexture:Hide()
        end

        if slotButton.top5suggestions and #slotButton.top5suggestions >= 2 then
            slotButton.moreButton:Show()
        else
            slotButton.moreButton:Hide()
        end
    end
    ----------------END to be optimized
    LuaUtils.Profiler:Stop("Searching for gears")

    --Updating the best
    LuaUtils.Profiler:Start("Updating the best")

    LuaUtils.Profiler:Start("Searching for guide")

    GearFinderPreloader.TexWrapper.Text:SetText("Searching for guide")


    local atLeastOneBelowPlayerLevel = false

    for _, itemButton in pairs(itemButtons) do
        if itemButton.suggestedGuide and DGF:PlayerHasEnoughLevel(itemButton.suggestion) then
            atLeastOneBelowPlayerLevel = true
        end
    end

    local scoreByGuide = {}
    local theBestGuideScore = nil
    local theBestGuide = nil
    ----------------------------------
    --Searching for suggested guide by the best score
--   for _, itemButton in pairs(itemButtons) do
--       if itemButton.suggestedGuide and (DGF:PlayerHasEnoughLevel(itemButton.suggestion) or (atLeastOneBelowPlayerLevel == false)) then
--           local score = itemButton.suggestion.score
--           if theBestGuideScore == nil or theBestGuideScore < score then
--               theBestGuideScore = score
--               theBestGuide = itemButton.suggestedGuide
--           end
--       end
--   end
 
    --Searching for suggested guide by the highest score sum of suggested gears
    --{"guide title 1" => 30052, "guide title 2" => 3452}
    local guide2gearsScoreSum = {}
    for _, itemButton in pairs(itemButtons) do
        if itemButton.suggestedGuide and (DGF:PlayerHasEnoughLevel(itemButton.suggestion) or (atLeastOneBelowPlayerLevel == false)) then
            if not guide2gearsScoreSum[itemButton.suggestedGuide] then
                guide2gearsScoreSum[itemButton.suggestedGuide] = itemButton.suggestion.score
            else
                guide2gearsScoreSum[itemButton.suggestedGuide] = guide2gearsScoreSum[itemButton.suggestedGuide] + itemButton.suggestion.score
            end
        end
    end
    
    local theHighestSum = nil
    for guide, sum in pairs(guide2gearsScoreSum) do
        if theHighestSum == nil or sum > theHighestSum then
            theHighestSum = sum
            theBestGuide = guide
        end
    end
    
    -----------------------------------

    DugisGearFinderFrame.suggestedGuide.suggestedGuideTitle  = theBestGuide

    LuaUtils.Profiler:Stop("Searching for guide")
    if DugisGearFinderFrame.suggestedGuide.suggestedGuideTitle then
        local formattedTitle = DugisGearFinderFrame.suggestedGuide.suggestedGuideTitle
        if DugisGuideViewer.GetFormattedTitle then
           formattedTitle = DugisGuideViewer:GetFormattedTitle(DugisGearFinderFrame.suggestedGuide.suggestedGuideTitle)
        end

        DugisGearFinderFrame.suggestedGuideSubtitle:SetText("|cFFFFFFFF"..formattedTitle.."|r")
        DugisGearFinderFrame.suggestedGuideSubtitle:SetWordWrap(true)
        DugisGearFinderFrame.suggestedGuideSubtitle:SetWidth(150)
        DugisGearFinderFrame.suggestedGuideSubtitle:SetJustifyH("LEFT")
    else
        DugisGearFinderFrame.suggestedGuideSubtitle:SetText("-")
    end

    for index, box in pairs(itemButtons) do
        box:SetPoint("TOPLEFT", 0, -index * boxSize - DugisGearFinderFrame.suggestedGuideSubtitle:GetHeight() + 20)
    end

    LuaUtils.Profiler:Stop("Updating the best")


    LuaUtils.Profiler:Stop("SetSuggestedItemGuides")

    inSetSuggestedItemGuides = false
end


local InitializeGearFinderUI_initialized = false

function DGF:UpdatetabsPosition()
    local isZygorLoaded = nil --DugisGuideViewer.zygorloaded

    local tabsShift = 0

    if isZygorLoaded then
        tabsShift = 24
    end

    PaperDollSidebarTab3:SetPoint("BOTTOMRIGHT",PaperDollSidebarTabs,"BOTTOMRIGHT",-60 - tabsShift, 0)
end

local orig_GetPaperDollSideBarFrame
function DGF:CreateGearFinderTabButton()
    if DugisGearFinderButton then
        return
    end

    local isZygorLoaded = nil --DugisGuideViewer.zygorloaded

    tinsert(PAPERDOLL_SIDEBARS,
    {
        name='Dugi Guides Gear Finder', 
        frame = "DugisGearFinderFrame", 
        icon="Interface\\AddOns\\DugisGuideViewerZ\\Artwork\\UpgradeArrow", 
        texCoords = {0, 1, 0,1},
        disabledTooltip = nil;
		IsActive = function() return true; end
    })
    orig_GetPaperDollSideBarFrame = orig_GetPaperDollSideBarFrame or GetPaperDollSideBarFrame
    GetPaperDollSideBarFrame = function(index)
        return orig_GetPaperDollSideBarFrame(index) or _G[PAPERDOLL_SIDEBARS[index].frame]
    end

    local tabIndex = 4
    local buttonLeftShift = 4

    if isZygorLoaded then
        tabIndex = 5
        buttonLeftShift = 41
    end

    PaperDollSidebarTabs:SetWidth(168 + 30)
    DGF:UpdatetabsPosition()

    CreateFrame("Button", "DugisGearFinderButton", PaperDollSidebarTabs, "PaperDollSidebarTabTemplate", tabIndex);
    DugisGearFinderButton:SetPoint("LEFT", PaperDollSidebarTab3, "RIGHT", buttonLeftShift, 0)
    DugisGearFinderButton:SetScript("OnLoad", nil)
    DugisGearFinderButton:HookScript("OnClick", function()
        if LuaUtils:ThreadInProgress("SetSuggestedItemGuides") or LuaUtils:ThreadInProgress("CacheItemsForGearFinder") then
            return
        end

        DGF:CacheItemsForGearFinder()
    end)
    DugisGearFinderButton.Icon:SetSize(27, 27)
    DugisGearFinderButton.Icon:SetPoint("BOTTOM", DugisGearFinderButton, "BOTTOM", 1, 2)
end

local wasGearFinderInitialize = false


function DGF:UpdateTabsForGearFinder()
    if DugisGuideViewer.chardb.EssentialsMode == 1 or not DugisGuideViewer:GuideOn() or not DugisGuideViewer:UserSetting(DGV_ENABLEDGEARFINDER) then
        if DugisGearFinderButton ~= nil then
            DugisGearFinderButton:Hide()
        end
        PaperDollSidebarTab3:SetPoint("BOTTOMRIGHT",PaperDollSidebarTabs,"BOTTOMRIGHT",-60, 0)
        return
    end

    DGF:CreateGearFinderTabButton()
    DugisGearFinderButton:Show()
    DGF:UpdatetabsPosition()

    DGF:CreateExtraItemsFrame()
    DGF:GearFinderTooltipFrame()

end

function DGV.InitializeGearFinder(invokedByIUChange)
    if DugisGuideViewer.chardb.EssentialsMode ~= 1 and DugisGuideViewer:GuideOn() and DugisGuideViewer:UserSetting(DGV_ENABLEDGEARFINDER) then
        DGF:InitializeGearFinderUI()

        CharacterFrame:HookScript("OnShow", function()
            DGF:UpdateTabsForGearFinder()
            if GearFinderTooltipFrame then
                GearFinderTooltipFrame:Hide()
            end
        end)

        if invokedByIUChange then
            HideUIPanel(CharacterFrame)
        end
    end
end


