--[[
##########################################################
S V U I   By: Munglunch
##########################################################
LOCALIZED LUA FUNCTIONS
##########################################################
]]--
--[[ GLOBALS ]]--
local _G = _G;
local unpack 	= _G.unpack;
local select 	= _G.select;
local pairs 	= _G.pairs;
local string 	= _G.string;
local math 		= _G.math;
--[[ STRING METHODS ]]--
local find, format, match, split, join = string.find, string.format, string.match, string.split, string.join;
local sub, byte = string.sub, string.byte;
--[[ MATH METHODS ]]--
local ceil, floor, round = math.ceil, math.floor, math.round;
--[[ TABLE METHODS ]]--
local tremove, tcopy, twipe, tsort, tcat = table.remove, table.copy, table.wipe, table.sort, table.concat;
--[[
##########################################################
GET ADDON DATA
##########################################################
]]--
local SV = select(2, ...)
local L = SV.L
--[[
##########################################################
LOCAL VARS
##########################################################
]]--
local NewHook = hooksecurefunc;
local ParseItemLevel, ParseGearSlots;
local GEAR_CACHE, GEARSET_LISTING = {}, {};
local EquipmentSlots = {
    ["HeadSlot"] = {true,true},
    ["NeckSlot"] = {true,false},
    ["ShoulderSlot"] = {true,true},
    ["BackSlot"] = {true,false},
    ["ChestSlot"] = {true,true},
    ["WristSlot"] = {true,true},
    ["MainHandSlot"] = {true,true,true},
    ["SecondaryHandSlot"] = {true,true},
    ["HandsSlot"] = {true,true,true},
    ["WaistSlot"] = {true,true,true},
    ["LegsSlot"] = {true,true,true},
    ["FeetSlot"] = {true,true,true},
    ["Finger0Slot"] = {true,false,true},
    ["Finger1Slot"] = {true,false,true},
    ["Trinket0Slot"] = {true,false,true},
    ["Trinket1Slot"] = {true,false,true}
}
--[[
	Quick explaination of what Im doing with all of these locals...
	Unlike many of the other modules, Inventory has to continuously
	reference config settings which can start to get sluggish. What
	I have done is set local variables for every database value
	that the module can read efficiently. The function "UpdateLocals"
	is used to refresh these any time a change is made to configs
	and once when the mod is loaded.
]]--
local COLOR_KEYS = { [0] = "|cffff0000", [1] = "|cff00ff00", [2] = "|cffffff88" };
local LIVESET, LASTSET, BG_SET, SPEC_SET, SHOW_DURABILITY, ONLY_DAMAGED, AVG_LEVEL, MAX_LEVEL;
local SPEC_SWAP, BG_SWAP;
local SHOW_CHAR_LEVEL, SHOW_BAG_LEVEL, SHOW_CHAR_SET, SHOW_BAG_SET;
local iLevelFilter = ITEM_LEVEL:gsub( "%%d", "(%%d+)" )
--[[
##########################################################
LOCAL FUNCTIONS
##########################################################
]]--
local GearHandler = CreateFrame("Frame", nil);

local function encodeSub(i, j, k)
	local l = j;
	while((k > 0) and (l <= #i)) do
		local m = byte(i, l)
		if(m > 240) then
			l = l + 4;
		elseif(m > 225) then
			l = l + 3;
		elseif(m > 192) then
			l = l + 2;
		else
			l = l + 1;
		end
		k = k - 1;
	end
	return i:sub(j, (l - 1))
end

do
    local _heirlooms80 = {
      44102,42944,44096,42943,42950,48677,42946,42948,42947,42992,
      50255,44103,44107,44095,44098,44097,44105,42951,48683,48685,
      42949,48687,42984,44100,44101,44092,48718,44091,42952,48689,
      44099,42991,42985,48691,44094,44093,42945,48716
    }
    local _heirlooms90h = {105689,105683,105686,105687,105688,105685,105690,105691,105684,105692,105693}
    local _heirlooms90n = {104399,104400,104401,104402,104403,104404,104405,104406,104407,104408,104409}
    local _heirlooms90f = {105675,105670,105672,105671,105674,105673,105676,105677,105678,105679,105680}

    -- DEPRECATED
    -- local _heirloom_regex = "|?c?f?f?(%x*)|?H?([^:]*):?(%d+):?(%d*):?(%d*):?(%d*):?(%d*):?(%d*):?(%-?%d*):?(%-?%d*):?(%d*):?(%d*)|?h?%[?([^%[%]]*)%]?|?h?|?r?";

    local _slots = {
        ["HeadSlot"] = {true, true},            ["NeckSlot"] = {true, false},
        ["ShoulderSlot"] = {true, true},        ["BackSlot"] = {true, false},
        ["ChestSlot"] = {true, true},           ["WristSlot"] = {true, true},
        ["MainHandSlot"] = {true, true, true},  ["SecondaryHandSlot"] = {true, true},
        ["HandsSlot"] = {true, true, true},     ["WaistSlot"] = {true, true, true},
        ["LegsSlot"] = {true, true, true},      ["FeetSlot"] = {true, true, true},
        ["Finger0Slot"] = {true, false, true},  ["Finger1Slot"] = {true, false, true},
        ["Trinket0Slot"] = {true, false, true}, ["Trinket1Slot"] = {true, false, true}
    }

    local function _justthetip()
        for i=1, #GameTooltip.shoppingTooltips do
            if(not GameTooltip.shoppingTooltips[i]:IsShown()) then
                return GameTooltip.shoppingTooltips[i]
            end
        end
    end

    local function _getHeirloomLevel(unit, itemId)
        if(not itemId) then return; end

        local baseLevel = UnitLevel(unit)
        if baseLevel > 85 then
            for i=1, #_heirlooms90h do
              if(_heirlooms90h[i] == itemId) then
                baseLevel = 582
                break
              end
            end

            for i=1, #_heirlooms90n do
              if(_heirlooms90n[i] == itemId) then
                baseLevel = 569
                break
              end
            end

            for i=1, #_heirlooms90f do
              if(_heirlooms90f[i] == itemId) then
                baseLevel = 548
                break
              end
            end

            return baseLevel
        elseif baseLevel > 80 then
            for i=1, #_heirlooms80 do
                if(_heirlooms80[i] == itemId) then
                    baseLevel = 80;
                    break
                end
            end
        end

        if(baseLevel > 80) then
            return (((baseLevel - 81) * 12.2) + 272)
        elseif(baseLevel > 67) then
            return (((baseLevel - 68) * 6) + 130)
        elseif(baseLevel > 59) then
            return (((baseLevel - 60) * 3) + 85)
        end
        return baseLevel
    end

    function ParseItemLevel(unit, itemLink)
        local name, link, quality;
        local iLevel = 0;
        if(itemLink and type(itemLink) == "string") then
          name, link, quality, iLevel = GetItemInfo(itemLink)
          local itemId = tonumber(itemLink:match("item:%d+:%d+:%d+:%d+:%d+:%d+:%-?%d+:%-?%d+:%d+:%d+:(%d+)"))
          if iLevel and itemId then
              if(quality == 7) then
                  iLevel = _getHeirloomLevel(unit, itemId)
              end
          end
        end
        return iLevel or 0
    end

    local function _getEquippedItemLevel(unit, itemLink)
        local tooltip = _justthetip();
        if(not tooltip) then return ParseItemLevel(unit, itemLink) end
        tooltip:SetOwner(UIParent, "ANCHOR_NONE");
        tooltip:SetHyperlink(itemLink);
        tooltip:Show();

        local iLevel = 0;
        local tname = tooltip:GetName().."TextLeft%s";
        for i = 2, tooltip:NumLines() do
            local text = _G[tname:format(i)]:GetText();
            if(text and text ~= "") then
                local value = tonumber(text:match(iLevelFilter));
                if(value) then
                    iLevel = value;
                end
            end
        end

        tooltip:Hide();
        return iLevel
    end

    local function _setLevelDisplay(frame, iLevel)
      if(not frame or (not frame.ItemLevel)) then return; end
      frame.ItemLevel:SetText('')
      if(SHOW_CHAR_LEVEL and iLevel) then
        local key = (iLevel < (AVG_LEVEL - 10)) and 0 or (iLevel > (AVG_LEVEL + 10)) and 1 or 2;
        frame.ItemLevel:SetFormattedText("%s%d|r", COLOR_KEYS[key], iLevel)
      end
    end

    local function _setDurabilityDisplay(frame, slotId)
      if(not frame or (not frame.DurabilityInfo)) then return; end
      if(SHOW_DURABILITY) then
        local current,total,actual,perc,r,g,b;
        current,total = GetInventoryItemDurability(slotId)
        if(current and total) then
          frame.DurabilityInfo.bar:SetMinMaxValues(0, 100)
          if(current == total and ONLY_DAMAGED) then
            frame.DurabilityInfo:Hide()
          else
            if(current ~= total) then
              actual = current / total;
              perc = actual * 100;
              r,g,b = SV:ColorGradient(actual,1,0,0,1,1,0,0,1,0)
              frame.DurabilityInfo.bar:SetValue(perc)
              frame.DurabilityInfo.bar:SetStatusBarColor(r,g,b)
              if not frame.DurabilityInfo:IsShown() then
                frame.DurabilityInfo:Show()
              end
            else
              frame.DurabilityInfo.bar:SetValue(100)
              frame.DurabilityInfo.bar:SetStatusBarColor(0, 1, 0)
            end
          end
        else
          frame.DurabilityInfo:Hide()
        end
      else
        frame.DurabilityInfo:Hide()
      end
    end

    function ParseGearSlots(unit, category, setLevel, setDurability)
        local averageLevel,totalSlots = 0,0;

        for slotName, flags in pairs(_slots) do
            local slotId = GetInventorySlotInfo(slotName);
            local iLink = GetInventoryItemLink(unit, slotId)
            local iLevel;
            if(iLink and type(iLink) == "string") then
                iLevel = _getEquippedItemLevel(unit, iLink)
                if(iLevel and iLevel > 0) then
                    totalSlots = totalSlots + 1;
                    averageLevel = averageLevel + iLevel
                end
            end

            if(setLevel and flags[1]) then
              _setLevelDisplay(_G[category .. slotName], iLevel)
            end
            if(setDurability and (slotId ~= nil) and (not inspecting) and flags[2]) then
              _setDurabilityDisplay(_G[category .. slotName], slotId)
            end
        end
        return averageLevel,totalSlots
    end
end

function SV:ParseGearSlots(unit, inspecting, setLevel, setDurability)
    local category = (inspecting) and "Inspect" or "Character";
    local averageLevel,totalSlots = ParseGearSlots(unit, category, setLevel, setDurability);
    if(averageLevel < 1 or totalSlots < 15) then return end
    return floor(averageLevel / totalSlots)
end

function SV:SetGearLabels(frame, bagID, slotID, itemLink, quality, equipSlot)
  quality = quality or 0;
  equipSlot = equipSlot or '';

  if(frame.GearInfo) then
    local loc = format("%d_%d", bagID, slotID);
    if((not SHOW_BAG_SET) or (not GEARSET_LISTING[loc])) then
      frame.GearInfo:SetText('')
  	else
      local setNumber = #GEARSET_LISTING[loc] < 4 and #GEARSET_LISTING[loc] or 3;
  		if(setNumber == 1) then
        frame.GearInfo:SetFormattedText("|cffffffaa%s|r", encodeSub(GEARSET_LISTING[loc][1], 1, 4))
  		elseif(setNumber == 2) then
        frame.GearInfo:SetFormattedText("|cffffffaa%s %s|r", encodeSub(GEARSET_LISTING[loc][1], 1, 4), encodeSub(GEARSET_LISTING[loc][2], 1, 4))
  		elseif(setNumber == 3) then
        frame.GearInfo:SetFormattedText("|cffffffaa%s %s %s|r", encodeSub(GEARSET_LISTING[loc][1], 1, 4), encodeSub(GEARSET_LISTING[loc][2], 1, 4), encodeSub(GEARSET_LISTING[loc][3], 1, 4))
  		else
        frame.GearInfo:SetText('')
  		end
  	end
  end

  if(frame.ItemLevel) then
    local iLevel = ParseItemLevel('player', itemLink)
  	if((not SHOW_BAG_LEVEL) or (iLevel <= 1) or (quality == 7) or (not equipSlot:find('INVTYPE'))) then
      frame.ItemLevel:SetText('')
  	else
  		local key = (iLevel < (AVG_LEVEL - 10)) and 0 or (iLevel > (AVG_LEVEL + 10)) and 1 or 2;
      frame.ItemLevel:SetFormattedText("%s%d|r", COLOR_KEYS[key], iLevel)
  	end
  end
end

local function GetActiveGear()
	local count = GetNumEquipmentSets()
	local resultSpec = GetActiveSpecGroup()
	local resultSet
  BG_SET = "none"
	SPEC_SET = "none"
	if(resultSpec and GetSpecializationInfo(resultSpec) and (resultSpec ~= 1)) then
    SPEC_SET = SV.db.Gear.specialization.secondary
    BG_SET = SV.db.Gear.battleground.secondary
  else
    SPEC_SET = SV.db.Gear.specialization.primary
    BG_SET = SV.db.Gear.battleground.primary
	end
	if(count == 0) then
		return resultSpec,false
	end
	for i=1, count do
		local setName,_,_,setUsed = GetEquipmentSetInfo(i)
		if setUsed then
			resultSet = setName
			break
		end
	end
	return resultSpec,resultSet
end

local function SetDisplayStats(arg)
	for slotName, flags in pairs(EquipmentSlots) do
		local globalName = format("%s%s", arg, slotName)
		local frame = _G[globalName]

		if(flags[1]) then
			frame.ItemLevel = frame:CreateFontString(nil, "OVERLAY")
			frame.ItemLevel:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 2, 1)
			frame.ItemLevel:SetFontObject(SVUI_Font_Default)
		end

		if(arg == "Character" and flags[2]) then
			frame.DurabilityInfo = CreateFrame("Frame", nil, frame)
			frame.DurabilityInfo:SetWidth(7)
			if flags[3] then
				frame.DurabilityInfo:SetPoint("TOPRIGHT", frame, "TOPLEFT", -1, 1)
				frame.DurabilityInfo:SetPoint("BOTTOMRIGHT", frame, "BOTTOMLEFT", -1, -1)
			else
				frame.DurabilityInfo:SetPoint("TOPLEFT", frame, "TOPRIGHT", 1, 1)
				frame.DurabilityInfo:SetPoint("BOTTOMLEFT", frame, "BOTTOMRIGHT", 1, -1)
			end
			frame.DurabilityInfo:SetFrameLevel(frame:GetFrameLevel()-1)
			frame.DurabilityInfo:SetBackdrop(SV.media.backdrop.glow)
			frame.DurabilityInfo:SetBackdropColor(0, 0, 0, 0.5)
			frame.DurabilityInfo:SetBackdropBorderColor(0, 0, 0, 0.8)
			frame.DurabilityInfo.bar = CreateFrame("StatusBar", nil, frame.DurabilityInfo)
			frame.DurabilityInfo.bar:InsetPoints(frame.DurabilityInfo, 2, 2)
			frame.DurabilityInfo.bar:SetStatusBarTexture(SV.media.statusbar.default)
			frame.DurabilityInfo.bar:SetOrientation("VERTICAL")
			frame.DurabilityInfo.bg = frame.DurabilityInfo:CreateTexture(nil, "BORDER")
			frame.DurabilityInfo.bg:InsetPoints(frame.DurabilityInfo, 2, 2)
			frame.DurabilityInfo.bg:SetTexture([[Interface\BUTTONS\WHITE8X8]])
			frame.DurabilityInfo.bg:SetVertexColor("VERTICAL", 0.5, 0.53, 0.55, 0.8, 0.8, 1)
		end
	end
end
--[[
##########################################################
CORE FUNCTIONS
##########################################################
]]--
local function RefreshInspectedGear()
	if(not SV.GearBuildComplete) then return end
	if(InCombatLockdown()) then
		GearHandler:RegisterEvent("PLAYER_REGEN_ENABLED", RefreshInspectedGear)
		return
	else
		GearHandler.WaitingOnInspect = nil
	end

	local unit = InspectFrame and InspectFrame.unit or "player";
	if(not unit or (unit and not CanInspect(unit,false))) then return end

  SV:ParseGearSlots(unit, true, true)
end

local function UpdateLocals()
  SPEC_SWAP = SV.db.Gear.specialization.enable
  BG_SWAP = SV.db.Gear.battleground.enable
	SHOW_CHAR_LEVEL = SV.db.Gear.labels.characterItemLevel
  SHOW_BAG_LEVEL = SV.db.Gear.labels.inventoryItemLevel
  SHOW_CHAR_SET = SV.db.Gear.labels.characterGearSet
  SHOW_BAG_SET = SV.db.Gear.labels.inventoryGearSet
	SHOW_DURABILITY = SV.db.Gear.durability.enable
	ONLY_DAMAGED = SV.db.Gear.durability.onlydamaged
	MAX_LEVEL, AVG_LEVEL = GetAverageItemLevel()
end

function SV:BuildEquipmentMap()
  UpdateLocals()
	for key, gearData in pairs(GEARSET_LISTING) do
		twipe(gearData);
	end
	local set, player, bank, bags, slotIndex, bagIndex, loc, _;
	for i = 1, GetNumEquipmentSets() do
		set = GetEquipmentSetInfo(i);
		GEAR_CACHE = GetEquipmentSetLocations(set);
		if(GEAR_CACHE) then
			for key, location in pairs(GEAR_CACHE) do
				if(type(location) ~= "string") then
					player, bank, bags, _, slotIndex, bagIndex = EquipmentManager_UnpackLocation(location);
					if((bank or bags) and (slotIndex and bagIndex)) then
						loc = format("%d_%d", bagIndex, slotIndex);
						GEARSET_LISTING[loc] = GEARSET_LISTING[loc] or {};
            local gslCount = #GEARSET_LISTING[loc] + 1
						GEARSET_LISTING[loc][gslCount] = set;
					end
				end
			end
		end
	end
end

function SV:UpdateGearInfo()
	if(not SV.GearBuildComplete) then return end
	if(InCombatLockdown()) then
		GearHandler:RegisterEvent("PLAYER_REGEN_ENABLED")
		return
	end
	UpdateLocals()
  SV:ParseGearSlots("player", false, true, true)
end

local Gear_UpdateTabs = function()
	GearHandler.WaitingOnInspect = true
	SV.Timers:ExecuteTimer(RefreshInspectedGear, 0.2)
end

function SV:GearSwap()
	if(InCombatLockdown()) then return; end
	local gearSpec, gearSet = GetActiveGear()
	if(not gearSet) then return; end

	if(BG_SWAP and (BG_SET ~= "none" and BG_SET ~= gearSet)) then
		local inDungeon,dungeonType = IsInInstance()
		if(inDungeon and dungeonType == "pvp" or dungeonType == "arena") then
			if BG_SET ~= "none" and BG_SET ~= gearSet then
				LIVESET = BG_SET;
				UseEquipmentSet(BG_SET)
			end
			return
		end
	end

	if(SPEC_SWAP and (SPEC_SET ~= "none" and SPEC_SET ~= gearSet)) then
		LIVESET = SPEC_SET;
		UseEquipmentSet(SPEC_SET)
	end
end

local MSG_PREFIX = "You have equipped equipment set: "

local GearHandler_OnEvent = function(self, event, ...)
	if(event == "PLAYER_REGEN_ENABLED") then
		self:UnregisterEvent("PLAYER_REGEN_ENABLED")
		if(self.WaitingOnInspect) then
			SV.Timers:ExecuteTimer(RefreshInspectedGear, 0.2)
		else
			SV:UpdateGearInfo()
		end
	elseif(event == "EQUIPMENT_SWAP_FINISHED") then
		if LIVESET then
			local strMsg = ("%s%s"):format(MSG_PREFIX, LIVESET)
			SV:AddonMessage(strMsg)
			LIVESET = nil
		end
	else
		SV:UpdateGearInfo()
	end
end

local function InitializeGearInfo()
	MSG_PREFIX = L["You have equipped equipment set: "]
  SHOW_CHAR_LEVEL = SV.db.Gear.labels.characterItemLevel
  SHOW_BAG_LEVEL = SV.db.Gear.labels.inventoryItemLevel
  SHOW_CHAR_SET = SV.db.Gear.labels.characterGearSet
  SHOW_BAG_SET = SV.db.Gear.labels.inventoryGearSet
	SHOW_DURABILITY = SV.db.Gear.durability.enable
	ONLY_DAMAGED = SV.db.Gear.durability.onlydamaged
	MAX_LEVEL, AVG_LEVEL = GetAverageItemLevel()

  GearHandler:RegisterEvent("PLAYER_ENTERING_WORLD")
	GearHandler:RegisterEvent("UPDATE_INVENTORY_DURABILITY")
	GearHandler:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
	GearHandler:RegisterEvent("SOCKET_INFO_UPDATE")
	GearHandler:RegisterEvent("COMBAT_RATING_UPDATE")
	GearHandler:RegisterEvent("MASTERY_UPDATE")
	GearHandler:RegisterEvent("EQUIPMENT_SWAP_FINISHED")
	GearHandler:SetScript("OnEvent", GearHandler_OnEvent)

	LoadAddOn("Blizzard_InspectUI")
	SetDisplayStats("Character")
	SetDisplayStats("Inspect")
	NewHook('InspectFrame_UpdateTabs', Gear_UpdateTabs)
	SV.Timers:ExecuteTimer(SV.UpdateGearInfo, 10)
	SV:GearSwap()
	SV.GearBuildComplete = true
end

SV:NewScript(InitializeGearInfo)
