local _G, select, tonumber, pairs, hooksecurefunc = _G, select, tonumber, pairs, hooksecurefunc
local tconcat, tinsert = table.concat, table.insert
local CreateFrame = CreateFrame

local UnitAura, UnitBuff, UnitDebuff, UnitGUID = UnitAura, UnitBuff, UnitDebuff, UnitGUID
local GetAchievementInfo, GetAchievementCriteriaInfo, GetPreviousAchievement = GetAchievementInfo, GetAchievementCriteriaInfo, GetPreviousAchievement
local GetGlyphSocketInfo, GetGlyphLink, GetGlyphInfo = GetGlyphSocketInfo, GetGlyphLink, GetGlyphInfo
local GetActionInfo, GetSpellInfo, GetItemGem, GetItemInfo = GetActionInfo, GetSpellInfo, GetItemGem, GetItemInfo
local GetTalentLink = GetTalentLink
local GetTradeSkillRecipeLink = GetTradeSkillRecipeLink

local idWoW = CreateFrame('Frame')
idWoW:RegisterEvent('ADDON_LOADED')
idWoW:SetScript('OnEvent', function(self, event, addon)
  if self[addon] then self[addon]() end
end)

idWoW.debug = true
idWoW.options = {
  ---- units
  object = true,
  unit = true,
  guid = true,
  spawn_counter = true,
  ---- achievements
  achievement = true,
  criteria = true,
  ---- item
  item = true,
  enchant = true,
  bonus = true,
  gem = true,
  ---- spell
  spell = true,
  glyph = true,
  talent = true,
  ---- other
  quest = true,
  icon = true,
  ---- misc indexes
  index_macro = true,
  index_talent = true,
  slot_actionbar = true,
  slot_inventory = true,
}
idWoW.kinds = {
  ---- units
  object = 'ObjectID', -- only for GM
  unit = 'NPC ID',
  guid = 'GUID',
  spawn_counter = 'Spawn counter',
  ---- achievements
  achievement = 'AchievementID',
  criteria = 'CriteriaID',
  ---- item
  item = 'ItemID',
  enchant = 'EnchantID',
  bonus = 'BonusID',
  gem = 'GemID',
  ---- spell
  spell = 'SpellID',
  glyph = 'GlyphID',
  talent = 'TalentID',
  ---- other
  quest = 'QuestID',
  icon = 'Icon',
  ---- misc indexes
  index_macro = 'Macro index',
  index_talent = 'Talent index',
  slot_actionbar = 'Slot index',
  slot_inventory = 'Slot index',
}

local function cropTexturePath(path)
  return path:gsub('Interface\\Icons\\', ''):gsub('INTERFACE\\ICONS\\', '')
end

local function SetTooltipPoint(frame, anchor, point, relativePoint, offsetX, offsetY)
  GameTooltip:SetOwner(frame, anchor or 'ANCHOR_NONE')
  GameTooltip:SetPoint(point or 'TOPLEFT', frame, relativePoint or 'TOPRIGHT', offsetX or 0, offsetY or 0)
end

local function parseGUID(guid)
  if not guid then return end
  -- guid: 0xAABCCCDDDDEEEEEE
  local AA      = tonumber((guid):sub(3 , 4 ), 16)     -- unknown; Possible battleground identifier when used for players.
  local B       = tonumber((guid):sub(5 , 5 ), 16) % 8 -- Unit type, mask with 0x7 to get: 0 for players, 1 for world objects, 3 for NPCs, 4 for permanent pets (including Water Elemental), 5 for vehicles. Temporary pets (Treants, Spirit Wolves, Mirror Images, and Ghouls) are considered NPCs (3), even if talents or glyphs prevent them from expiring.
  local CCCDDDD = tonumber((guid):sub(6 , 12), 16)     -- If the unit is a pet, CCCDDDD forms a unique ID for the pet based on creation order; if a world object, CCCDDDD is the object ID; otherwise unknown.
  local DDDD    = tonumber((guid):sub(9 , 12), 16)     -- If the unit is an NPC, this is the hexadecimal representation of the NPC id.
  local EEEEEE  = tonumber((guid):sub(13, 18), 16)     -- If the unit NOT is a player, this is a spawn counter based on spawn order.
  local DEEEEEE = tonumber((guid):sub(12, 18), 16)     -- If the unit is a player, this is a unique identifier based on creation order (For players it can be 7 digits long, so that its DEEEEEE).

  local id, type, subtype, spawn_counter
  if B == 0 then -- Player
    id = DEEEEEE
    type = 'unit'
    subtype = 'player'
  elseif B == 1 then -- Object
    id = CCCDDDD
    type = 'object'
  elseif B == 3 then -- NPC
    id = DDDD
    type = 'unit'
    spawn_counter = EEEEEE
  elseif B == 4 then -- Pet
    id = CCCDDDD
    type = 'unit'
    subtype = 'pet'
  elseif B == 5 then -- Vehicle
    id = DDDD
    type = 'unit'
    subtype = 'vehicle'
    spawn_counter = EEEEEE
  end
  return id, spawn_counter, type, subtype
end

function idWoW:AddHookScript(tooltip, script)
  tooltip:HookScript(script, function(self, ...) idWoW.scripts[script](self, ...) end)
end

function idWoW:AddHookMethod(tooltip, method)
  hooksecurefunc(tooltip, method, function(self, ...) idWoW.methods[method](self, ...) end)
end

function idWoW:AddPreHookFunction(func_name)
  self.hooks.orig_funcs[func_name] = _G[func_name]
  _G[func_name] = self.hooks[func_name]
end

function idWoW:addData(tooltip, id, kind_type, subtext, handler)
  if not id then return end
  local kind = self.kinds[kind_type]
  if not kind then
    local text = ('|cffffffffUnsupported link type |cffff0000%s|r'):format(kind_type)
    if handler then
      text = text..('|cffffffff received from |cff00ff00[%s]|r!'):format(handler)
    end
    tooltip:AddLine(text, nil, nil, nil, nil, true)
    tooltip:Show()
    return
  end
  if not self.options[kind_type] then return end
  if not id or id == '' then return end

  local left_text, right_text
  if type(id) == 'table' then
    left_text = kind .. 's'
    right_text = tconcat(id, ', ')
  else
    left_text = kind..(subtext and ' ('..subtext..')' or '')
    if kind_type == 'icon' then
      right_text = cropTexturePath(id)
    else
      right_text = id
    end
  end
  if self.debug and handler then
    left_text = '|cff00ff00['..handler..']|r '..left_text
  end
  tooltip:AddDoubleLine(left_text, right_text, 1, .82, 0, 1, 1, 1)
  tooltip:Show()
end

idWoW.hooks = {
  orig_funcs = {},
  GetQuestLogTitle = function(questIndex)
    local questTitle, level, questTag, suggestedGroup, isHeader, isCollapsed, isComplete, isDaily, questID = idWoW.hooks.orig_funcs.GetQuestLogTitle(questIndex)
    if questTitle and not isHeader then
      questTitle = '['..questID..'] '..questTitle
    end
    return questTitle, level, questTag, suggestedGroup, isHeader, isCollapsed, isComplete, isDaily, questID
  end
}
idWoW.scripts = {
  OnTooltipSetUnit = function(self)
    local unit = select(2, self:GetUnit())
    if not unit then return end
    local guid = UnitGUID(unit)
    local id, spawn_counter, kind, subtext = parseGUID(guid)
    idWoW:addData(self, guid, 'guid', nil, 'OnTooltipSetUnit')
    idWoW:addData(self, id, kind, subtext, 'OnTooltipSetUnit')
    idWoW:addData(self, spawn_counter, 'spawn_counter', nil, 'OnTooltipSetUnit')
  end,
  OnTooltipSetSpell = function(self)
    local id = select(3, self:GetSpell())
    if not id then return end
    local icon = select(3, GetSpellInfo(id))
    idWoW:addData(self, id, 'spell', nil, 'OnTooltipSetSpell')
    idWoW:addData(self, icon, 'icon', nil, 'OnTooltipSetSpell')
  end,
  OnTooltipSetItem = function(self)
    local link = select(2, self:GetItem())
    if not link then return end
    local itemString = link:match('item:([%-?%d:]+)')

    local itemSplit = {}
    for v in itemString:gmatch('(%d+)') do
      itemSplit[#itemSplit + 1] = v
    end
    local itemID = itemSplit[1]
    local enchantID = itemSplit[2]
    local gemID1 = itemSplit[3]
    local gemID2 = itemSplit[4]
    local gemID3 = itemSplit[5]
    local gemID4 = itemSplit[6]
    local suffixID = itemSplit[7]
    local uniqueID = itemSplit[8]
    local linkLevel = itemSplit[9]

    local gems = {}
    for i=1, 4 do
      local _,gemLink = GetItemGem(link, i)
      if gemLink then
        gems[#gems + 1] = gemLink:match('item:(%d+)')
      end
    end

    idWoW:addData(self, itemID, 'item', nil, 'OnTooltipSetItem')
    if enchantID ~= '0' then
      idWoW:addData(self, enchantID, 'enchant', nil, 'OnTooltipSetItem')
    end
    if suffixID ~= '0' then
      if uniqueID ~= '0' then
        local data = {}
        data[1] = '-'..suffixID -- negative number
        data[2] = uniqueID
        idWoW:addData(self, data, 'bonus', nil, 'OnTooltipSetItem')
      elseif uniqueID == '0' then
        idWoW:addData(self, suffixID, 'bonus', nil, 'OnTooltipSetItem')
      end
    end
    if #gems ~= 0 then
      idWoW:addData(self, gems, 'gem', nil, 'OnTooltipSetItem')
    end
    local icon = select(10, GetItemInfo(itemID))
    idWoW:addData(self, icon, 'icon', nil, 'OnTooltipSetItem')
  end,
  OnTooltipSetAchievement = function(self)
    if not arg1 or type(arg1) ~= 'string' then return end
    local id = arg1:match('achievement:(%d+)')
    local icon = select(10, GetAchievementInfo(id))
    idWoW:addData(self, id, 'achievement', nil, 'OnTooltipSetAchievement')
    idWoW:addData(self, icon, 'icon', nil, 'OnTooltipSetAchievement')
  end,
  OnTooltipSetQuest = function(self)
    if not arg1 or type(arg1) ~= 'string' then return end
    local id = arg1:match('quest:(%d+)')
    idWoW:addData(self, id, 'quest', nil, 'OnTooltipSetQuest')
  end,
}
idWoW.methods = {
  SetHyperlink = function(self, link)
    local kind, id = link:match('^(%a+):(%d+)')
    if not kind or kind == 'enchant' or kind == 'trade' or kind == 'item' or kind == 'spell' or kind == 'quest' then return end
    idWoW:addData(self, id, kind, nil, 'SetHyperlink')
  end,
  SetUnitAura = function(self, unit, index, filter)
    local icon, _,  _,  _,  _,  _,  _,  _, id = select(3, UnitAura(unit, index, filter))
    idWoW:addData(self, id, 'spell', nil, 'SetUnitAura')
    idWoW:addData(self, icon, 'icon', nil, 'SetUnitAura')
  end,
  SetUnitBuff = function(self, unit, index, filter)
    local icon, _,  _,  _,  _,  _,  _,  _, id = select(3, UnitBuff(unit, index, filter))
    idWoW:addData(self, id, 'spell', nil, 'SetUnitBuff')
    idWoW:addData(self, icon, 'icon', nil, 'SetUnitBuff')
  end,
  SetUnitDebuff = function(self, unit, index, filter)
    local icon, _,  _,  _,  _,  _,  _,  _, id = select(3, UnitDebuff(unit, index, filter))
    idWoW:addData(self, id, 'spell', nil, 'SetUnitDebuff')
    idWoW:addData(self, icon, 'icon', nil, 'SetUnitDebuff')
  end,
  SetGlyph = function(self, index, talentGroup)
    local spellID, icon = select(3, GetGlyphSocketInfo(index, talentGroup))
    local glyphID = GetGlyphLink(index, talentGroup):match('glyph:(%d+)')
    idWoW:addData(self, spellID, 'spell', nil, 'SetGlyph')
    idWoW:addData(self, glyphID, 'glyph', nil, 'SetGlyph')
    idWoW:addData(self, icon, 'icon', nil, 'SetGlyph')
  end,
  SetAction = function(self, slot)
    idWoW:addData(self, slot, 'slot_actionbar', 'ActionBar', 'SetAction')
    local kind, id = GetActionInfo(slot)
    if kind == 'macro' then
      idWoW:addData(self, id, 'index_macro', nil, 'SetAction')
    end
  end,
  SetInventoryItem = function(self, unit, slot)
    idWoW:addData(self, slot, 'slot_inventory', 'Inventory', 'SetInventoryItem')
  end,
  SetTalent = function(self, tabIndex, talentIndex)
    local id = GetTalentLink(tabIndex, talentIndex):match('talent:(%d+)')
    idWoW:addData(self, id, 'talent', nil, 'SetTalent')
    local index = MAX_NUM_TALENTS*(tabIndex-1)+talentIndex
    idWoW:addData(self, index, 'index_talent', 'global', 'SetTalent')
  end,
}

if idWoW.options.unit then
  idWoW:AddHookScript(GameTooltip, 'OnTooltipSetUnit')
end

if idWoW.options.item then
  idWoW:AddHookScript(GameTooltip, 'OnTooltipSetItem')
  idWoW:AddHookScript(ItemRefTooltip, 'OnTooltipSetItem')
  idWoW:AddHookScript(ShoppingTooltip1, 'OnTooltipSetItem')
  idWoW:AddHookScript(ShoppingTooltip2, 'OnTooltipSetItem')
  idWoW:AddHookScript(ShoppingTooltip3, 'OnTooltipSetItem')
  idWoW:AddHookScript(ItemRefShoppingTooltip1, 'OnTooltipSetItem')
  idWoW:AddHookScript(ItemRefShoppingTooltip2, 'OnTooltipSetItem')
  idWoW:AddHookScript(ItemRefShoppingTooltip3, 'OnTooltipSetItem')
end

if idWoW.options.quest then
  idWoW:AddHookScript(GameTooltip, 'OnTooltipSetQuest')
  idWoW:AddHookScript(ItemRefTooltip, 'OnTooltipSetQuest')

  idWoW:AddPreHookFunction('GetQuestLogTitle')
end

if idWoW.options.spell then
  idWoW:AddHookScript(GameTooltip, 'OnTooltipSetSpell')
  idWoW:AddHookScript(ItemRefTooltip, 'OnTooltipSetSpell')

  idWoW:AddHookMethod(GameTooltip, 'SetUnitAura')
  idWoW:AddHookMethod(GameTooltip, 'SetUnitBuff')
  idWoW:AddHookMethod(GameTooltip, 'SetUnitDebuff')
  idWoW:AddHookMethod(GameTooltip, 'SetHyperlink')
  idWoW:AddHookMethod(ItemRefTooltip, 'SetHyperlink')
end

if idWoW.options.achievement then
  idWoW:AddHookScript(GameTooltip, 'OnTooltipSetAchievement')
  idWoW:AddHookScript(ItemRefTooltip, 'OnTooltipSetAchievement')
end

if idWoW.options.index_macro or idWoW.options.slot_actionbar then
  idWoW:AddHookMethod(GameTooltip, 'SetAction')
end

if idWoW.options.talent then
  idWoW:AddHookMethod(GameTooltip, 'SetTalent')
end

if idWoW.options.glyph then
  idWoW:AddHookMethod(GameTooltip, 'SetGlyph')
end

if idWoW.options.slot_inventory then
  idWoW:AddHookMethod(GameTooltip, 'SetInventoryItem')
end

function idWoW:Blizzard_AchievementUI()
  if not idWoW.options.achievement then return end
  local previousAchievements = {}
  local function Achievement_OnEnter(self)
    SetTooltipPoint(self)
    local name, _,  _,  _,  _,  _,  _, _, icon = select(2, GetAchievementInfo(self.id))
    GameTooltip:SetText(name)
    idWoW:addData(GameTooltip, self.id, 'achievement', nil, 'hook:OnEnter')
    idWoW:addData(GameTooltip, icon, 'icon', nil, 'hook:OnEnter')
    GameTooltip:Show()
  end
  local function AchievementCriteria_OnEnter(self)
    SetTooltipPoint(self)
    local sourceFrame = self:GetParent() and self:GetParent():GetParent()
    if not sourceFrame or not sourceFrame.id then return end
    local index = self:GetName():match('(%d+)')
    local name, criteriaType, _, _, _, _, _, assetID, _, criteriaID = GetAchievementCriteriaInfo(sourceFrame.id, index)
    GameTooltip:SetText(name)
    idWoW:addData(GameTooltip, criteriaID, 'criteria', nil, 'hook:OnEnter')
    if criteriaType == CRITERIA_TYPE_ACHIEVEMENT and assetID then
      local icon = select(10, GetAchievementInfo(assetID))
      idWoW:addData(GameTooltip, assetID, 'achievement', 'criteria', 'hook:OnEnter')
      idWoW:addData(GameTooltip, icon, 'icon', nil, 'hook:OnEnter')
    end
    GameTooltip:Show()
  end

  for _,button in pairs(AchievementFrameAchievementsContainer.buttons) do
    button:HookScript('OnEnter', Achievement_OnEnter)
    button:HookScript('OnLeave', GameTooltip_Hide)
  end
  for _,button in pairs(AchievementFrameComparisonContainer.buttons) do
    button:HookScript('OnEnter', Achievement_OnEnter)
    button:HookScript('OnLeave', GameTooltip_Hide)
  end
  hooksecurefunc('AchievementFrameSummary_LocalizeButton', function(button)
    button:HookScript('OnEnter', Achievement_OnEnter)
    button:HookScript('OnLeave', GameTooltip_Hide)
  end)
  hooksecurefunc('AchievementButton_LocalizeMiniAchievement', function(frame)
    -- when achievement has previous achievements
    frame:HookScript('OnEnter', function(self)
      local sourceFrame = self:GetParent():GetParent()
      local sourceAchievementID = sourceFrame.id
      if not previousAchievements[sourceAchievementID] then
        previousAchievements[sourceAchievementID] = {}
        tinsert(previousAchievements[sourceAchievementID], 1, sourceAchievementID)
        local previousAchievementID = GetPreviousAchievement(sourceAchievementID)
        while previousAchievementID do
          tinsert(previousAchievements[sourceAchievementID], 1, previousAchievementID)
          previousAchievementID = GetPreviousAchievement(previousAchievementID)
        end
      end
      local index = tonumber(self:GetName():match('(%d+)'))
      local miniAchievementID = previousAchievements[sourceAchievementID][index]
      local icon = select(10, GetAchievementInfo(miniAchievementID))
      idWoW:addData(GameTooltip, miniAchievementID, 'achievement', nil, 'hook:OnEnter')
      idWoW:addData(GameTooltip, icon, 'icon', nil, 'hook:OnEnter')
      GameTooltip:Show()
    end)
  end)
  hooksecurefunc('AchievementFrame_LocalizeCriteria', function(frame)
    -- when criteria as text
    frame:HookScript('OnEnter', AchievementCriteria_OnEnter)
    frame:HookScript('OnLeave', GameTooltip_Hide)
  end)
  hooksecurefunc('AchievementButton_LocalizeMetaAchievement', function(frame)
    -- when criteria as achievement with criterions
    frame:HookScript('OnEnter', AchievementCriteria_OnEnter)
    frame:HookScript('OnLeave', GameTooltip_Hide)
  end)
end

function idWoW:Blizzard_TradeSkillUI()
  if not idWoW.options.spell then return end
  for i=1,TRADE_SKILLS_DISPLAYED do
    local line = _G['TradeSkillSkill'..i]
    line:HookScript('OnEnter', function(self)
      SetTooltipPoint(self)
      local index = self:GetID()
      local link = GetTradeSkillRecipeLink(index)
      if not link then return end
      local id = link:match('enchant:(%d+)')
      idWoW:addData(GameTooltip, id, 'spell', 'tradeskill', 'hook:OnEnter')
    end)
    line:HookScript('OnLeave', GameTooltip_Hide)
  end
end

function idWoW:Blizzard_MacroUI()
  if not idWoW.options.icon then return end
  MacroFrameSelectedMacroButton:HookScript('OnEnter', function(self)
    local icon = MacroFrameSelectedMacroButtonIcon:GetTexture()
    SetTooltipPoint(self, nil, nil, 'BOTTOMLEFT', -4, -2)
    idWoW:addData(GameTooltip, icon, 'icon', nil, 'hook:OnEnter')
  end)
  MacroFrameSelectedMacroButton:HookScript('OnLeave', GameTooltip_Hide)
  for i=1,MAX_ACCOUNT_MACROS do
    local macroIcon = _G["MacroButton"..i.."Icon"]
    local macroButton = _G["MacroButton"..i]
    macroButton:HookScript('OnEnter', function(self)
      local icon = macroIcon:GetTexture()
      SetTooltipPoint(self, nil, nil, 'BOTTOMLEFT', -4, -2)
      idWoW:addData(GameTooltip, icon, 'icon', nil, 'hook:OnEnter')
    end)
    macroButton:HookScript('OnLeave', GameTooltip_Hide)
  end
  for i=1,NUM_MACRO_ICONS_SHOWN do
    local macroPopupIcon = _G["MacroPopupButton"..i.."Icon"]
    local macroPopupButton = _G["MacroPopupButton"..i]
    macroPopupButton:HookScript('OnEnter', function(self)
      local icon = macroPopupIcon:GetTexture()
      SetTooltipPoint(self, nil, nil, 'BOTTOMLEFT', -4, -2)
      idWoW:addData(GameTooltip, icon, 'icon', nil, 'hook:OnEnter')
    end)
    macroPopupButton:HookScript('OnLeave', GameTooltip_Hide)
  end
end
