ZO_PlayerEmote_Manager = ZO_InitializingCallbackObject:Subclass()

function ZO_PlayerEmote_Manager:Initialize()
    local function MarkEmoteListDirty()
        self:MarkEmoteListDirty()
    end

    EVENT_MANAGER:RegisterForEvent("ZO_PlayerEmote_Manager", EVENT_PERSONALITY_CHANGED, MarkEmoteListDirty)
    EVENT_MANAGER:RegisterForEvent("ZO_PlayerEmote_Manager", EVENT_GAMEPAD_PREFERRED_MODE_CHANGED, MarkEmoteListDirty)
    EVENT_MANAGER:RegisterForEvent("ZO_PlayerEmote_Manager", EVENT_ADD_ON_LOADED, function(_, addOnName) self:OnAddOnLoaded(addOnName) end)
    ZO_COLLECTIBLE_DATA_MANAGER:RegisterCallback("OnCollectibleUpdated", function(...) self:OnCollectibleUpdated(...) end)
    ZO_COLLECTIBLE_DATA_MANAGER:RegisterCallback("OnCollectionUpdated", function(...) self:OnCollectionUpdated(...) end)

    self.emoteList = {}
    self.emoteCategories = {}
    self.emoteCategoryTypes = {}

    self:MarkEmoteListDirty()
end

function ZO_PlayerEmote_Manager:OnAddOnLoaded(addOnName)
    if addOnName == "ZO_Ingame" then
        self:RefreshEmoteSlashCommands()
        EVENT_MANAGER:UnregisterForEvent("ZO_PlayerEmote_Manager", EVENT_ADD_ON_LOADED)
    end
end

function ZO_PlayerEmote_Manager:OnCollectionUpdated(collectionUpdateType, collectiblesByNewUnlockState)
    if collectionUpdateType == ZO_COLLECTION_UPDATE_TYPE.REBUILD then
        self:MarkEmoteListDirty()
        self:RefreshEmoteSlashCommands()
    else
        for _, unlockStateTable in pairs(collectiblesByNewUnlockState) do
            for _, collectibleData in ipairs(unlockStateTable) do
                if collectibleData:IsCategoryType(COLLECTIBLE_CATEGORY_TYPE_EMOTE) or collectibleData:IsCategoryType(COLLECTIBLE_CATEGORY_TYPE_PERSONALITY) then
                    self:MarkEmoteListDirty()
                    self:RefreshEmoteSlashCommands()
                    return
                end
            end
        end
    end
end

function ZO_PlayerEmote_Manager:OnCollectibleUpdated(collectibleId)
    local collectibleData = ZO_COLLECTIBLE_DATA_MANAGER:GetCollectibleDataById(collectibleId)
    if collectibleData:IsCategoryType(COLLECTIBLE_CATEGORY_TYPE_EMOTE) or collectibleData:IsCategoryType(COLLECTIBLE_CATEGORY_TYPE_PERSONALITY) then
        self:MarkEmoteListDirty()
        self:RefreshEmoteSlashCommands()
    end
end

function ZO_PlayerEmote_Manager:AddOrRemoveEmoteSlashCommand(emoteIndex, add, ...)
    for i = 1, select("#", ...) do
        local cmd = select(i, ...)
        if SLASH_COMMANDS[cmd] == nil then
            if add then
                SLASH_COMMANDS[cmd] = function() PlayEmoteByIndex(emoteIndex) end
            end
        else
            if not add then
                SLASH_COMMANDS[cmd] = nil
            end
        end
    end
end

function ZO_PlayerEmote_Manager:RefreshEmoteSlashCommands()
    local numEmotes = GetNumEmotes()
    for emoteIndex = 1, numEmotes do
        local lockedByCollectibleId = GetEmoteCollectibleId(emoteIndex)
        local slashName = GetEmoteSlashNameByIndex(emoteIndex)
        if lockedByCollectibleId then
             -- Since this function is called at load time, not accessing the collectible data manager here can facilitate loading the data manager via coroutine
            local add = IsCollectibleUnlocked(lockedByCollectibleId)
            self:AddOrRemoveEmoteSlashCommand(emoteIndex, add, zo_strsplit(" ", slashName))
        else
            local ADD = true
            self:AddOrRemoveEmoteSlashCommand(emoteIndex, ADD, zo_strsplit(" ", slashName))
        end
    end
    self:FireCallbacks("EmoteSlashCommandsUpdated")
end

function ZO_PlayerEmote_Manager:CompareEmotes(emoteA, emoteB)
    if IsInGamepadPreferredMode() then
        self:CompareEmotesGamepad(emoteA, emoteB)
    else
        self:CompareEmotesKeyboard(emoteA, emoteB)
    end
end

function ZO_PlayerEmote_Manager:CompareEmotesGamepad(emoteA, emoteB)
    return self:GetEmoteItemInfo(emoteA).displayName < self:GetEmoteItemInfo(emoteB).displayName
end

function ZO_PlayerEmote_Manager:CompareEmotesKeyboard(emoteA, emoteB)
    return self:GetEmoteItemInfo(emoteA).emoteSlashName < self:GetEmoteItemInfo(emoteB).emoteSlashName
end

local function CompareCategories(categoryA, categoryB)
    return categoryA < categoryB
end

function ZO_PlayerEmote_Manager:GetEmoteItemInfo(emoteId)
    self:CleanEmoteList()
    return self.emoteList[emoteId]
end

function ZO_PlayerEmote_Manager:MarkEmoteListDirty()
    self.listDirty = true
    self:FireCallbacks("EmoteListUpdated")
end

function ZO_PlayerEmote_Manager:CleanEmoteList()
    if self.listDirty then
        self:BuildEmoteList()
    end
end

function ZO_PlayerEmote_Manager:BuildEmoteList()
    self.listDirty = false

    ZO_ClearTable(self.emoteList)
    ZO_ClearTable(self.emoteCategories)
    ZO_ClearNumericallyIndexedTable(self.emoteCategoryTypes)

    for emoteIndex = 1, GetNumEmotes() do
        local lockedByCollectibleId = GetEmoteCollectibleId(emoteIndex)
        local collectibleData  = lockedByCollectibleId and ZO_COLLECTIBLE_DATA_MANAGER:GetCollectibleDataById(lockedByCollectibleId)
        if not collectibleData or collectibleData:IsUnlocked() then
            local emoteSlashName, emoteCategory, emoteId, displayName, showInGamepadUI = GetEmoteInfo(emoteIndex)
            if emoteSlashName ~= "" then
                if not self.emoteCategories[emoteCategory] then
                    self.emoteCategories[emoteCategory] = {}
                    table.insert(self.emoteCategoryTypes, emoteCategory)
                end

                local isOverriddenByPersonality = IsPlayerEmoteOverridden(emoteId)
                self.emoteList[emoteId] =   {
                                                emoteCategory = emoteCategory,
                                                emoteId = emoteId,
                                                emoteIndex = emoteIndex,
                                                displayName = displayName,
                                                emoteSlashName = emoteSlashName,
                                                showInGamepadUI = showInGamepadUI,
                                                isOverriddenByPersonality = isOverriddenByPersonality,
                                            }

                table.insert(self.emoteCategories[emoteCategory], emoteId)

                if isOverriddenByPersonality then
                    if not self.emoteCategories[EMOTE_CATEGORY_PERSONALITY_OVERRIDE] then
                        self.emoteCategories[EMOTE_CATEGORY_PERSONALITY_OVERRIDE] = {}
                        table.insert(self.emoteCategoryTypes, EMOTE_CATEGORY_PERSONALITY_OVERRIDE)
                    end

                    table.insert(self.emoteCategories[EMOTE_CATEGORY_PERSONALITY_OVERRIDE], emoteId)
                end

                if collectibleData then
                    if not self.emoteCategories[EMOTE_CATEGORY_COLLECTED] then
                        self.emoteCategories[EMOTE_CATEGORY_COLLECTED] = {}
                        table.insert(self.emoteCategoryTypes, EMOTE_CATEGORY_COLLECTED)
                    end

                    table.insert(self.emoteCategories[EMOTE_CATEGORY_COLLECTED], emoteId)
                end
            end
        end
    end

    local emoteSortFunction
    if IsInGamepadPreferredMode() then
        emoteSortFunction = function(...) return self:CompareEmotesGamepad(...) end
    else
        emoteSortFunction = function(...) return self:CompareEmotesKeyboard(...) end
    end

    for _, emoteList in pairs(self.emoteCategories) do
        table.sort(emoteList, emoteSortFunction)
    end

    table.sort(self.emoteCategoryTypes, CompareCategories)
end

function ZO_PlayerEmote_Manager:GetEmoteListForType(emoteType, optFilterFunction)
    self:CleanEmoteList()

    local emoteCategory = self.emoteCategories[emoteType]
    if not optFilterFunction then
        return emoteCategory
    else
        local filteredEmoteCategory = {}
        for _, emoteId in ipairs(emoteCategory) do
            if optFilterFunction(self.emoteList[emoteId]) then
                table.insert(filteredEmoteCategory, emoteId)
            end
        end

        return filteredEmoteCategory
    end    
end

function ZO_PlayerEmote_Manager:GetEmoteCategories()
    self:CleanEmoteList()

    return self.emoteCategoryTypes
end

do
    local SHARED_EMOTE_EMPTY_SLOT_ICON_PATH = "EsoUI/Art/Quickslots/quickslot_emptySlot.dds"
    function ZO_PlayerEmote_Manager:GetSharedEmoteIconForCategory(category)
        return GetSharedEmoteIconForCategory(category) or SHARED_EMOTE_EMPTY_SLOT_ICON_PATH
    end

    function ZO_PlayerEmote_Manager:GetSharedPersonalityEmoteIconForCategory(category)
        return GetSharedPersonalityEmoteIconForCategory(category) or SHARED_EMOTE_EMPTY_SLOT_ICON_PATH
    end
end

-- Globals
-------------------------------------------------------------
PLAYER_EMOTE_MANAGER = ZO_PlayerEmote_Manager:New()