--[=[
    This file has the functions to get player information
    Dumping them here, make the code of the main file smaller
--]=]



if (not LIB_OPEN_RAID_CAN_LOAD) then
	return
end

local openRaidLib = LibStub:GetLibrary("LibOpenRaid-1.0")

local CONST_TALENT_VERSION_CLASSIC = 1
local CONST_TALENT_VERSION_LEGION = 4
local CONST_TALENT_VERSION_DRAGONFLIGHT = 5

local CONST_BTALENT_VERSION_COVENANTS = 9

local isTimewalkWoW = function()
    local _, _, _, buildInfo = GetBuildInfo()
    if (buildInfo < 40000) then
        return true
    end
end

local IsDragonflight = function()
	return select(4, GetBuildInfo()) >= 100000
end

local IsShadowlands = function()
    local versionString, revision, launchDate, gameVersion = GetBuildInfo()
    if (gameVersion >= 90000 and gameVersion < 100000) then
        return true
    end
end

--information about the player character to send, each expansion has its own system and data can be different
--it's always a number
function openRaidLib.UnitInfoManager.GetPlayerInfo1()
    if (IsShadowlands()) then
        --return the renown level within the player covenant
        local renown = C_CovenantSanctumUI.GetRenownLevel() or 1
        return renown
    end

    return 0
end

--information about the player character to send, each expansion has its own system and data can be different
--it's always a number
function openRaidLib.UnitInfoManager.GetPlayerInfo2()
    if (IsShadowlands()) then
        --return which covenant the player picked
        local covenant = C_Covenants.GetActiveCovenantID() or 0
        return covenant
    end

    return 0
end

--default player class-spec talent system
function openRaidLib.GetTalentVersion()
    local _, _, _, buildInfo = GetBuildInfo()

    if (buildInfo >= 1 and buildInfo <= 40000) then --vanilla tbc wotlk cataclysm
        return CONST_TALENT_VERSION_CLASSIC
    end

    if (buildInfo >= 70000 and buildInfo <= 100000) then --legion bfa shadowlands
        return CONST_TALENT_VERSION_LEGION
    end

    if (buildInfo >= 100000 and buildInfo <= 200000) then --dragonflight
        return CONST_TALENT_VERSION_DRAGONFLIGHT
    end
end

--secondary talent tree, can be a legendary weapon talent tree, covenant talent tree, etc...
function openRaidLib.GetBorrowedTalentVersion()
    if (IsShadowlands()) then
        return CONST_BTALENT_VERSION_COVENANTS
    end
end

local getDragonflightTalentsAsIndexTable = function()
    local allTalents = {}
    local configId = C_ClassTalents.GetActiveConfigID()
    if (not configId) then
        return allTalents
    end

    local configInfo = C_Traits.GetConfigInfo(configId)

    for treeIndex, treeId in ipairs(configInfo.treeIDs) do
        local treeNodes = C_Traits.GetTreeNodes(treeId)

        for nodeIdIndex, treeNodeID in ipairs(treeNodes) do
            local traitNodeInfo = C_Traits.GetNodeInfo(configId, treeNodeID)

            if (traitNodeInfo) then
                local activeEntry = traitNodeInfo.activeEntry
                if (activeEntry) then
                    local entryId = activeEntry.entryID
                    local rank = activeEntry.rank
                    if (rank > 0) then
                        --get the entry info
                        local traitEntryInfo = C_Traits.GetEntryInfo(configId, entryId)
                        local definitionId = traitEntryInfo.definitionID

                        --definition info
                        local traitDefinitionInfo = C_Traits.GetDefinitionInfo(definitionId)
                        local spellId = traitDefinitionInfo.overriddenSpellID or traitDefinitionInfo.spellID
                        local spellName, _, spellTexture = GetSpellInfo(spellId)
                        if (spellName) then
                            allTalents[#allTalents+1] = spellId
                        end
                    end
                end
            end
        end
    end

    return allTalents
end

--creates two tables, one with indexed talents and another with pairs values ([talentId] = true)
function openRaidLib.UnitInfoManager.GetPlayerTalentsAsPairsTable()
    local talentsPairs = {}
    local talentVersion = openRaidLib.GetTalentVersion()

    if (talentVersion == CONST_TALENT_VERSION_DRAGONFLIGHT) then
        local allTalents = getDragonflightTalentsAsIndexTable()
        for i = 1, #allTalents do
            local spellId = allTalents[i]
            talentsPairs[spellId] = true
        end

    elseif (talentVersion == CONST_TALENT_VERSION_LEGION) then
        for i = 1, 7 do
            for o = 1, 3 do
                local talentId, _, _, selected = GetTalentInfo(i, o, 1)
                if (selected) then
                    talentsPairs[talentId] = true
                    break
                end
            end
        end
    end

    return talentsPairs
end

function openRaidLib.UnitInfoManager.GetPlayerTalents()
    local talents = {}
    local talentVersion = openRaidLib.GetTalentVersion()

    if (talentVersion == CONST_TALENT_VERSION_DRAGONFLIGHT) then
        talents = getDragonflightTalentsAsIndexTable()

    elseif (talentVersion == CONST_TALENT_VERSION_LEGION) then
        talents = {0, 0, 0, 0, 0, 0, 0}
        for talentTier = 1, 7 do
            for talentColumn = 1, 3 do
                local talentId, name, texture, selected, available = GetTalentInfo(talentTier, talentColumn, 1)
                if (selected) then
                    talents[talentTier] = talentId
                    break
                end
            end
        end
    end

    return talents
end

function openRaidLib.UnitInfoManager.GetPlayerPvPTalents()
    if (IsDragonflight()) then
        return {}
    end

    local talentsPvP = {0, 0, 0}
    local talentList = C_SpecializationInfo.GetAllSelectedPvpTalentIDs()
    for talentIndex, talentId in ipairs(talentList) do
        local doesExists = GetPvpTalentInfoByID(talentId)
        if (doesExists) then
            talentsPvP[talentIndex] = talentId
        end
    end
    return talentsPvP
end

--return the current specId of the player
function openRaidLib.GetPlayerSpecId()
    if (isTimewalkWoW()) then
        return 0
    end

    local spec = GetSpecialization()
    if (spec) then
        local specId = GetSpecializationInfo(spec)
        if (specId and specId > 0) then
            return specId
        end
    end
end

--borrowed talent tree from shadowlands
function openRaidLib.UnitInfoManager.GetPlayerConduits()
    local conduits = {}
    local soulbindID = C_Soulbinds.GetActiveSoulbindID()

    if (soulbindID ~= 0) then
        local soulbindData = C_Soulbinds.GetSoulbindData(soulbindID)
        if (soulbindData ~= 0) then
            local tree = soulbindData.tree
            local nodes = tree.nodes

            table.sort(nodes, function(t1, t2) return t1.row < t2.row end)
            local C_Soulbinds_GetConduitCollectionData = C_Soulbinds.GetConduitCollectionData
            for nodeId, nodeInfo in ipairs(nodes) do
                --check if the node is a conduit placed by the player
                
                if (nodeInfo.state == Enum.SoulbindNodeState.Selected)  then
                    local conduitId = nodeInfo.conduitID
                    local conduitRank = nodeInfo.conduitRank
                    
                    if (conduitId and conduitRank) then
                        --have spell id when it's a default conduit from the game
                        local spellId = nodeInfo.spellID
                        --have conduit id when is a conduid placed by the player
                        local conduitId  = nodeInfo.conduitID
                        
                        if (spellId == 0) then
                            --is player conduit
                            spellId = C_Soulbinds.GetConduitSpellID(nodeInfo.conduitID, nodeInfo.conduitRank)
                            conduits[#conduits+1] = spellId
                            local collectionData = C_Soulbinds_GetConduitCollectionData(conduitId)
                            conduits[#conduits+1] = collectionData and collectionData.conduitItemLevel or 0         
                        else
                            --is default conduit
                            conduits[#conduits+1] = spellId
                            conduits[#conduits+1] = 0
                        end
                    end
                end
            end
        end
    end

    return conduits
end

function openRaidLib.UnitInfoManager.GetPlayerBorrowedTalents()
    local borrowedTalentVersion = openRaidLib.GetBorrowedTalentVersion()

    if (borrowedTalentVersion == CONST_BTALENT_VERSION_COVENANTS) then
        return openRaidLib.UnitInfoManager.GetPlayerConduits()
    end

    return {}
end


function openRaidLib.GearManager.GetPlayerItemLevel()
    if (_G.GetAverageItemLevel) then
        local _, itemLevel = GetAverageItemLevel()
        itemLevel = floor(itemLevel)
        return itemLevel
    else
        return 0
    end
end

--return an integer between zero and one hundret indicating the player gear durability
function openRaidLib.GearManager.GetPlayerGearDurability()
    local durabilityTotalPercent, totalItems = 0, 0
    for i = INVSLOT_FIRST_EQUIPPED, INVSLOT_LAST_EQUIPPED do
        local durability, maxDurability = GetInventoryItemDurability(i)
        if (durability and maxDurability) then
            local itemDurability = durability / maxDurability * 100
            durabilityTotalPercent = durabilityTotalPercent + itemDurability
            totalItems = totalItems + 1
        end
    end

    if (totalItems == 0) then
        return 100
    end

    return floor(durabilityTotalPercent / totalItems)
end

function openRaidLib.GearManager.GetPlayerWeaponEnchant()
    local weaponEnchant = 0
    local _, _, _, mainHandEnchantId, _, _, _, offHandEnchantId = GetWeaponEnchantInfo()
    if (LIB_OPEN_RAID_WEAPON_ENCHANT_IDS[mainHandEnchantId]) then
        weaponEnchant = 1

    elseif(LIB_OPEN_RAID_WEAPON_ENCHANT_IDS[offHandEnchantId]) then
        weaponEnchant = 1
    end
    return weaponEnchant
end

function openRaidLib.GearManager.GetPlayerGemsAndEnchantInfo()
    --hold equipmentSlotId of equipment with a gem socket but it's empty
    local slotsWithoutGems = {}
    --hold equipmentSlotId of equipments without an enchant
    local slotsWithoutEnchant = {}

    for equipmentSlotId = 1, 17 do
        local itemLink = GetInventoryItemLink("player", equipmentSlotId)
        if (itemLink) then
            --get the information from the item
            local _, itemId, enchantId, gemId1, gemId2, gemId3, gemId4, suffixId, uniqueId, levelOfTheItem, specId, upgradeInfo, instanceDifficultyId, numBonusIds, restLink = strsplit(":", itemLink)
            local gemsIds = {gemId1, gemId2, gemId3, gemId4}

            --enchant
                --check if the slot can receive enchat and if the equipment has an enchant
                local enchantAttribute = LIB_OPEN_RAID_ENCHANT_SLOTS[equipmentSlotId]
                if (enchantAttribute) then --this slot can receive an enchat

                    --check if this slot is relevant for the class, some slots can have enchants only for Agility which won't matter for Priests as an example
                    --if the value is an integer it points to an attribute (int, dex, str), otherwise it's true (boolean)
                    local slotIsRelevant = true
                    if (type(enchantAttribute) == "number") then
                        if (specMainAttribute ~= enchantAttribute) then
                            slotIsRelevant = false
                        end
                    end

                    if (slotIsRelevant) then
                        --does the slot has any enchant?
                        if (not enchantId or enchantId == "0" or enchantId == "") then
                            slotsWithoutEnchant[#slotsWithoutEnchant+1] = equipmentSlotId
                        else
                            --convert to integer
                            local enchantIdInt = tonumber(enchantId)
                            if (enchantIdInt) then
                                --does the enchant is relevent for the character?
                                if (not LIB_OPEN_RAID_ENCHANT_IDS[enchantIdInt]) then
                                    slotsWithoutEnchant[#slotsWithoutEnchant+1] = equipmentSlotId
                                end
                            else
                                --the enchat has an invalid id
                                slotsWithoutEnchant[#slotsWithoutEnchant+1] = equipmentSlotId
                            end
                        end
                    end
                end

            --gems
                local itemStatsTable = {}
                --fill the table above with information about the item
                GetItemStats(itemLink, itemStatsTable)

                --check if the item has a socket
                if (itemStatsTable.EMPTY_SOCKET_PRISMATIC) then
                    --check if the socket is empty
                    for i = 1, itemStatsTable.EMPTY_SOCKET_PRISMATIC do
                        local gemId = tonumber(gemsIds[i])
                        if (not gemId or gemId == 0) then
                            slotsWithoutGems[#slotsWithoutGems+1] = equipmentSlotId

                        --check if the gem is not a valid gem (deprecated gem)
                        elseif (not LIB_OPEN_RAID_GEM_IDS[gemId]) then
                            slotsWithoutGems[#slotsWithoutGems+1] = equipmentSlotId
                        end
                    end
                end
        end
    end

    return slotsWithoutGems, slotsWithoutEnchant
end

local playerHasPetOfNpcId = function(npcId)
    if (UnitExists("pet") and UnitHealth("pet") >= 1) then
        local guid = UnitGUID("pet")
        if (guid) then
            local split = {strsplit("-", guid)}
            local playerPetNpcId = tonumber(split[6])
            if (playerPetNpcId) then
                if (npcId == playerPetNpcId) then
                    return true
                end
            end
        end
    end
end

local addCooldownToTable = function(cooldowns, cooldownsHash, cooldownSpellId, timeNow)
    cooldowns[#cooldowns+1] = cooldownSpellId
    local timeLeft, charges, startTimeOffset, duration = openRaidLib.CooldownManager.GetPlayerCooldownStatus(cooldownSpellId)
    cooldowns[#cooldowns+1] = timeLeft
    cooldowns[#cooldowns+1] = charges
    cooldowns[#cooldowns+1] = startTimeOffset
    cooldowns[#cooldowns+1] = duration
    cooldownsHash[cooldownSpellId] = {timeLeft, charges, startTimeOffset, duration, timeNow}
end

local canAddCooldown = function(cooldownInfo)
        local needPetNpcId = cooldownInfo.pet
    if (needPetNpcId) then
        if (not playerHasPetOfNpcId(needPetNpcId)) then
            return false
        end
    end
    return true
end

--build a list with the local player cooldowns
--called only from SendAllPlayerCooldowns()
function openRaidLib.CooldownManager.GetPlayerCooldownList()
    --get the player specId
    local specId = openRaidLib.GetPlayerSpecId()
    if (specId) then
        --get the cooldowns for the specialization
        local playerCooldowns = LIB_OPEN_RAID_COOLDOWNS_BY_SPEC[specId]
        if (not playerCooldowns) then
            openRaidLib.DiagnosticError("CooldownManager|GetPlayerCooldownList|can't find player cooldowns for specId:", specId)
            return {}, {}
        end

        local cooldowns = {} --table to ship on comm
        local cooldownsHash = {} --table with [spellId] = cooldownInfo
        local talentsHash = openRaidLib.UnitInfoManager.GetPlayerTalentsAsPairsTable()
        local timeNow = GetTime()

        for cooldownSpellId, cooldownType in pairs(playerCooldowns) do
            --get all the information about this cooldow
            local cooldownInfo = LIB_OPEN_RAID_COOLDOWNS_INFO[cooldownSpellId]
            if (cooldownInfo) then
                --does this cooldown is based on a talent?
                local talentId = cooldownInfo.talent

                --check if the player has a talent which makes this cooldown unavailable
                local ignoredByTalentId = cooldownInfo.ignoredIfTalent
                local isIgnoredByTalentId = false
                if (ignoredByTalentId) then
                    if (talentsHash[ignoredByTalentId]) then
                        isIgnoredByTalentId = true
                    end
                end

                if (not isIgnoredByTalentId) then
                    if (talentId) then
                        --check if the player has the talent selected
                        if (talentsHash[talentId]) then
                            if (canAddCooldown(cooldownInfo)) then
                                addCooldownToTable(cooldowns, cooldownsHash, cooldownSpellId, timeNow)
                            end
                        end
                    else
                        if (canAddCooldown(cooldownInfo)) then
                            addCooldownToTable(cooldowns, cooldownsHash, cooldownSpellId, timeNow)
                        end
                    end
                end
            end
        end
        return cooldowns, cooldownsHash
    else
        return {}, {}
    end
end

--check if a player cooldown is ready or if is in cooldown
--@spellId: the spellId to check for cooldown
function openRaidLib.CooldownManager.GetPlayerCooldownStatus(spellId)
    --check if is a charge spell
    local cooldownInfo = LIB_OPEN_RAID_COOLDOWNS_INFO[spellId]
    if (cooldownInfo) then
        if (cooldownInfo.charges and cooldownInfo.charges > 1) then
            local chargesAvailable, chargesTotal, start, duration = GetSpellCharges(spellId)

            if (chargesAvailable == chargesTotal) then
                return 0, chargesTotal, 0, 0 --all charges are ready to use
            else
                --return the time to the next charge
                local timeLeft = start + duration - GetTime()
                local startTimeOffset = start - GetTime()
                return ceil(timeLeft), chargesAvailable, startTimeOffset, duration --time left, charges, startTime
            end

        else
            local start, duration = GetSpellCooldown(spellId)
            if (start == 0) then --cooldown is ready
                return 0, 1, 0, 0 --time left, charges, startTime
            else
                local timeLeft = start + duration - GetTime()
                local startTimeOffset = start - GetTime()
                return ceil(timeLeft), 0, ceil(startTimeOffset), duration --time left, charges, startTime, duration
            end
        end
    else
        return openRaidLib.DiagnosticError("CooldownManager|GetPlayerCooldownStatus()|cooldownInfo not found|", spellId)
    end
end

--which is the main attribute of each spec
--1 Intellect
--2 Agility
--3 Strenth
openRaidLib.specAttribute = {
	["DEMONHUNTER"] = {
		[577] = 2,
		[581] = 2,
	},
	["DEATHKNIGHT"] = {
		[250] = 3,
		[251] = 3,
		[252] = 3,
	},
	["WARRIOR"] = {
		[71] = 3,
		[72] = 3,
		[73] = 3,
	},
	["MAGE"] = {
		[62] = 1,
		[63] = 1,
		[64] = 1,
	},
	["ROGUE"] = {
		[259] = 2,
		[260] = 2,
		[261] = 2,
	},
	["DRUID"] = {
		[102] = 1,
		[103] = 2,
		[104] = 2,
		[105] = 1,
	},
	["HUNTER"] = {
		[253] = 2,
		[254] = 2,
		[255] = 2,
	},
	["SHAMAN"] = {
		[262] = 1,
		[263] = 2,
		[264] = 1,
	},
	["PRIEST"] = {
		[256] = 1,
		[257] = 1,
		[258] = 1,
	},
	["WARLOCK"] = {
		[265] = 1,
		[266] = 1,
		[267] =1 ,
	},
	["PALADIN"] = {
		[65] = 1,
		[66] = 3,
		[70] = 3,
	},
	["MONK"] = {
		[268] = 2,
		[269] = 2,
		[270] = 1,
    },
    ["EVOKER"] = {
        [1467] = 1, --Devastation
        [1468] = 1, --Preservation
    },
}