GAMEPAD_SELECTOR_STRIDE = 3
GAMEPAD_SELECTOR_IGNORE_POSITION = -1

--
-- Character Creation Generic Selector
--

ZO_CharacterCreateSelector_Gamepad = ZO_Object:Subclass()

function ZO_CharacterCreateSelector_Gamepad:New(control)
    local object = ZO_Object.New(self)
    object:Initialize(control)
    return object
end

function ZO_CharacterCreateSelector_Gamepad:Initialize(control)
    control.sliderObject = self
    self.control = control
    self.activeButtonControls = {}
    self.selectionName = control:GetNamedChild("SelectionName")
    self.highlightControl = CreateControlFromVirtual("$(parent)Highlight", control, "ZO_CharacterCreateSelectorHighlight_Gamepad")
    self.selectedControl = CreateControlFromVirtual("$(parent)Selected", control, "ZO_CharacterCreateSelectorSelected_Gamepad")
    self.showKeybind = true
    self.stride = GAMEPAD_SELECTOR_STRIDE
    self.focusEnabled = false
    self.currentHighlight = GAMEPAD_SELECTOR_IGNORE_POSITION -- we start off with no highlight because we have no controls to highlight yet
    self.lastHighlight = 1
end

function ZO_CharacterCreateSelector_Gamepad:SetActiveButtonControls(activeButtonControls)
    self.activeButtonControls = activeButtonControls
end

function ZO_CharacterCreateSelector_Gamepad:GetNumActiveButtons()
    return #self.activeButtonControls
end

function ZO_CharacterCreateSelector_Gamepad:SetHighlight(index)
    self.currentHighlight = index

    local control = self:GetCurrentButton()
    self.buttonHighlighted = control ~= nil

    local highlight = self.highlightControl
    if self.buttonHighlighted then
        highlight:SetParent(control)
        highlight:SetAnchor(CENTER, control, CENTER, 0, 0)

        local bannerText = self:GetBannerText(control)
        self.selectionName:SetText(bannerText)
    end

    highlight:SetHidden(not self.buttonHighlighted)

    local buttonInfo = self:GetButtonInfo(index)
    self:OnButtonHighlighted(buttonInfo)
end

function ZO_CharacterCreateSelector_Gamepad:GetFocusIndex()
    return self.control.sliderObject.info.index
end

function ZO_CharacterCreateSelector_Gamepad:GetSelectedIndex()
    return self.selectedPosition
end

function ZO_CharacterCreateSelector_Gamepad:UpdateButtons()
    local selectedControl = self.selectedControl
    local bannerText = nil
    for _, info in ipairs(self:GetSelectionInfo()) do
        local button = self:GetButton(info.gamepadPosition)
        if button == nil then
            -- Ignore
        elseif button:GetState() == BSTATE_PRESSED then
            -- If the button is selected
            if bannerText == nil then
                bannerText = self:GetBannerText(button)
            end

            selectedControl:SetParent(button)
            selectedControl:SetAnchor(CENTER, control, CENTER, 0, 0)
            selectedControl:SetHidden(false)
            self.selectedPosition = info.gamepadPosition
        elseif info.gamepadPosition == self.currentHighlight and self.buttonHighlighted then
            -- If we have the button highlighted (in focus)
            bannerText = self:GetBannerText(button)
        else
            -- Unselected and unhighlighted
        end
    end

    self.selectionName:SetText(bannerText)
end

function ZO_CharacterCreateSelector_Gamepad:EnableFocus(enabled)
    local nameControl = self.control:GetNamedChild("Name")
    if nameControl then
        local fontColor
        local nameFont
        if enabled then
            fontColor = ZO_SELECTED_TEXT
            nameFont = "ZoFontGamepad42"
        else
            fontColor = ZO_DISABLED_TEXT
            nameFont = "ZoFontGamepad34"
        end

        nameControl:SetColor(fontColor:UnpackRGB())
        nameControl:SetFont(nameFont)
        self.selectionName:SetColor(fontColor:UnpackRGB())
    end

    self.focusEnabled = enabled

    if enabled then
        -- if we are enabling then we need to make sure the highlight is initialized/reenabled
        local highlightIndex = self.currentHighlight
        if highlightIndex == GAMEPAD_SELECTOR_IGNORE_POSITION then
            highlightIndex = self.lastHighlight
        end
        self:SetHighlight(highlightIndex)
    else
        -- if we are disabling then we need to make sure the highlight is disabled
        -- and we need to set our selection text to the text of the selected control instead of the highlight
        self.buttonHighlighted = false
        local button = self:GetButton(self.selectedPosition)
        if button then
            local bannerText = self:GetBannerText(button)
            self.selectionName:SetText(bannerText)
        end

        if self.currentHighlight ~= GAMEPAD_SELECTOR_IGNORE_POSITION then
            self.lastHighlight = self.currentHighlight
        end

        self:SetHighlight(GAMEPAD_SELECTOR_IGNORE_POSITION)
    end
end

function ZO_CharacterCreateSelector_Gamepad:OnPrimaryButtonPressed()
    local control = self:GetCurrentButton()
    if control then
        ZO_CharacterCreate_Gamepad_OnSelectorPressed(control)
    else
        local name = self.control and self.control:GetName() or "Unspecified"
        local numRadioButtons = self:GetNumActiveButtons()
        local errorString = string.format("ZO_CharacterCreateSelector_Gamepad:OnPrimaryButtonPressed, controlName =  %s, currentHighlight = %d, lastHighlight = %d, totalButtonCount = %d", name, self.currentHighlight, self.lastHighlight, numRadioButtons)
        internalassert(false, errorString)
    end
end

function ZO_CharacterCreateSelector_Gamepad:GetButton(gamepadPosition)
    if gamepadPosition == GAMEPAD_SELECTOR_IGNORE_POSITION then
        return nil
    end

    return self.activeButtonControls[gamepadPosition]
end

function ZO_CharacterCreateSelector_Gamepad:GetCurrentButton()
    return self:GetButton(self.currentHighlight)
end

function ZO_CharacterCreateSelector_Gamepad:GetButtonInfo(gamepadPosition)
    local buttonControl = self:GetButton(gamepadPosition)
    return buttonControl and buttonControl.selectorData
end

function ZO_CharacterCreateSelector_Gamepad:MoveNext()
    local count =  self:GetNumActiveButtons() - self.currentHighlight
    local newHighlight = self.currentHighlight

    while count > 0 do
        newHighlight = math.min(newHighlight + 1, self:GetNumActiveButtons())

        if self.currentHighlight % self.stride ~= 0 and self:IsValidButtonAtIndex(newHighlight) then
            self:SetHighlight(newHighlight)
            PlaySound(SOUNDS.HOR_LIST_ITEM_SELECTED)
            break
        end
        count = count - 1
    end
end

function ZO_CharacterCreateSelector_Gamepad:MovePrevious()
    local count = self.currentHighlight - 1
    local newHighlight = self.currentHighlight

    while count > 0 do
        newHighlight = math.max(newHighlight - 1, 1)
        
        if self.currentHighlight % self.stride ~= 1 and self:IsValidButtonAtIndex(newHighlight) then
            self:SetHighlight(newHighlight)
            PlaySound(SOUNDS.HOR_LIST_ITEM_SELECTED)
            break
        end
        count = count - 1
    end
end

function ZO_CharacterCreateSelector_Gamepad:GetNearestValidIndex(index, minIndex, maxIndex)
    if not self:IsValidButtonAtIndex(index) then
        index = maxIndex

        while index > minIndex and not self:IsValidButtonAtIndex(index) do
            index = index - 1
        end
    end
    return index
end

function ZO_CharacterCreateSelector_Gamepad:IsValidButtonAtIndex(buttonIndex)
    local button = self:GetButton(buttonIndex)
    return button ~= nil
end

function ZO_CharacterCreateSelector_Gamepad:ProcessUpDownMove(move)
    local currentHighlight = self.currentHighlight
    local newHighlight = currentHighlight

    -- Button layout is similar to
    -- 1 2 3
    -- or
    -- 1 2 3
    --   4
    -- or
    -- 1 2 3
    -- 4 5

    local maxHighlightIndex = self:GetMaxHighlightIndex()
    local lastRowLeftIndex = zo_ceil(maxHighlightIndex / self.stride) * self.stride - self.stride + 1

    if move == MOVEMENT_CONTROLLER_MOVE_NEXT then
        -- Moving Down
        if currentHighlight >= lastRowLeftIndex then
            -- If we are in the bottom row, there's no moving down
            return nil
        else
            -- Move straight down, but make sure we move down to a valid button
            local targetHighlight = currentHighlight + self.stride
            local leftmostHighlight = targetHighlight - (targetHighlight - 1) % self.stride
            local rightmostHighlight = targetHighlight
            newHighlight = self:GetNearestValidIndex(targetHighlight, leftmostHighlight, rightmostHighlight, move)
        end
    elseif move == MOVEMENT_CONTROLLER_MOVE_PREVIOUS then
        -- Moving Up
        if currentHighlight <= self.stride then
            -- If we are in the top row, there's no moving up
            return nil
        else
            -- Move straight up
            newHighlight = currentHighlight - self.stride
            if currentHighlight == maxHighlightIndex and maxHighlightIndex % self.stride == 1 then
                -- If the button is being centered in the final rows
                -- straight up needs to be offset to the right to match the same column
                newHighlight = newHighlight + zo_floor(self.stride / 2)
            end
        end
    else
        return nil
    end

    if self:IsValidButtonAtIndex(newHighlight) then
        if move == MOVEMENT_CONTROLLER_MOVE_NEXT then
            PlaySound(SOUNDS.GAMEPAD_MENU_DOWN)
        elseif move == MOVEMENT_CONTROLLER_MOVE_PREVIOUS then
            PlaySound(SOUNDS.GAMEPAD_MENU_UP)
        end

        return newHighlight
    end

    return nil
end

function ZO_CharacterCreateSelector_Gamepad:FocusUpdate(move)
    -- Up/down movement
    local newHighlight = self:ProcessUpDownMove(move)

    if newHighlight then
        self:SetHighlight(newHighlight)
        return true
    end

    return false
end

function ZO_CharacterCreateSelector_Gamepad:GetColumnFromIndex(index)
    local maxIndex = self:GetMaxHighlightIndex()
    -- If we have a set of buttons, such that there will be one button on the last row
    -- it will be centered in the middle instead of being all the way on the left
    if maxIndex % self.stride == 1 then
        if index == maxIndex then
            local centerColumnIndex = zo_ceil(self.stride / 2)
            return centerColumnIndex
        end
    end

    return (index - 1) % self.stride + 1
end

function ZO_CharacterCreateSelector_Gamepad:GetHighlightColumn()
    return self:GetColumnFromIndex(self.currentHighlight)
end

function ZO_CharacterCreateSelector_Gamepad:GetMaxHighlightIndex()
    return #self:GetSelectionInfo()
end

function ZO_CharacterCreateSelector_Gamepad:GetBannerText(control)
    -- To be overridden
end

function ZO_CharacterCreateSelector_Gamepad:SetHighlightIndexByColumn(index, moveDown)
    -- To be overridden
end

function ZO_CharacterCreateSelector_Gamepad:GetInformationTooltipStrings(buttonInfo)
    -- Optional override
    local title = ""
    local description = ""
    return title, description
end

function ZO_CharacterCreateSelector_Gamepad:OnButtonHighlighted(buttonInfo)
    if buttonInfo then
        local title, description = self:GetInformationTooltipStrings(buttonInfo)
        if title ~= "" or description ~= "" then
            GAMEPAD_CHARACTER_CREATE_MANAGER:ShowInformationTooltip(title, description)
            GAMEPAD_BUCKET_MANAGER:NarrateCurrentBucket()
            return
        end
    end

    GAMEPAD_CHARACTER_CREATE_MANAGER:HideInformationTooltip()
    GAMEPAD_BUCKET_MANAGER:NarrateCurrentBucket()
end

--
-- Character Creation Alliance
--

ZO_CharacterCreateAllianceSelector_Gamepad = ZO_CharacterCreateSelector_Gamepad:Subclass()

function ZO_CharacterCreateAllianceSelector_Gamepad:New(control)
    return ZO_CharacterCreateSelector_Gamepad.New(self, control)
end

function ZO_CharacterCreateAllianceSelector_Gamepad:GetSelectionInfo(control)
    return GAMEPAD_CHARACTER_CREATE_MANAGER.characterData:GetAllianceInfo()
end

function ZO_CharacterCreateAllianceSelector_Gamepad:GetBannerText(control)
    return zo_strformat(SI_ALLIANCE_NAME, GetAllianceName(control.defId))
end

function ZO_CharacterCreateAllianceSelector_Gamepad:SetHighlightIndexByColumn(index, moveDown)
    self:SetHighlight(index)
end

function ZO_CharacterCreateAllianceSelector_Gamepad:EnableFocus(enabled)
    if enabled then
        GAMEPAD_CHARACTER_CREATE_MANAGER:ShowLoreInfo(GAMEPAD_BUCKET_CUSTOM_CONTROL_ALLIANCE)
    else
        GAMEPAD_CHARACTER_CREATE_MANAGER:HideLoreInfo()
    end

    ZO_CharacterCreateSelector_Gamepad.EnableFocus(self, enabled)
end

function ZO_CharacterCreateAllianceSelector_Gamepad:GetNarrationText()
    local narrations = {}
    table.insert(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(GetString(SI_CREATE_CHARACTER_ALLIANCE_LABEL)))

    local isSelected = self.selectedPosition == self.currentHighlight
    local currentButton = self:GetCurrentButton()
    if currentButton then
        table.insert(narrations, ZO_FormatRadioButtonNarrationText(self:GetBannerText(currentButton), isSelected))
    end

    local buttonInfo = self:GetButtonInfo(self.currentHighlight)
    if buttonInfo then
        if isSelected then
            table.insert(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(buttonInfo.lore))
        end
    end
    return narrations
end

--
-- Character Creation Race
--

ZO_CharacterCreateRaceSelector_Gamepad = ZO_CharacterCreateSelector_Gamepad:Subclass()

function ZO_CharacterCreateRaceSelector_Gamepad:New(...)
    return ZO_CharacterCreateSelector_Gamepad.New(self, ...)
end

function ZO_CharacterCreateRaceSelector_Gamepad:Initialize(control)
    ZO_CharacterCreateSelector_Gamepad.Initialize(self, control)

    control.preSelectedOffsetAdditionalPadding = -20
end

function ZO_CharacterCreateRaceSelector_Gamepad:IsValidButtonAtIndex(buttonIndex)
    if ZO_CharacterCreateSelector_Gamepad.IsValidButtonAtIndex(self, buttonIndex) then
        local button = self:GetButton(buttonIndex)
        return self:IsValidRaceForAlliance(button.alliance)
    else
        return false
    end
end

function ZO_CharacterCreateRaceSelector_Gamepad:IsValidRaceForAlliance(alliance)
    if alliance == 0 then
        -- If there is no alliance restriction, then it's fine
        return true
    end

    local characterMode = ZO_CHARACTERCREATE_MANAGER:GetCharacterMode()
    local selectedAlliance = CharacterCreateGetAlliance(characterMode)

    return alliance == selectedAlliance or CanPlayAnyRaceAsAnyAlliance()
end

function ZO_CharacterCreateRaceSelector_Gamepad:GetSelectionInfo(control)
    return GAMEPAD_CHARACTER_CREATE_MANAGER.characterData:GetRaceInfo()
end

function ZO_CharacterCreateRaceSelector_Gamepad:GetBannerText(control)
    local characterMode = ZO_CHARACTERCREATE_MANAGER:GetCharacterMode()
    local currentGender = CharacterCreateGetGender(characterMode)

    return zo_strformat(SI_RACE_NAME, GetRaceName(currentGender, control.defId))
end

function ZO_CharacterCreateRaceSelector_Gamepad:GetMaxHighlightIndex()
    return self.control.numButtons
end

function ZO_CharacterCreateRaceSelector_Gamepad:SetHighlightIndexByColumn(index, moveDown)
    if not moveDown then
        local currentIndex = self:GetMaxHighlightIndex()
        while currentIndex > 0 do
            if self:GetColumnFromIndex(currentIndex) == index and self:IsValidButtonAtIndex(currentIndex) then
                self:SetHighlight(currentIndex)
                return
            end
            currentIndex = currentIndex - 1
        end
    end

    self:SetHighlight(index)
end

function ZO_CharacterCreateRaceSelector_Gamepad:EnableFocus(enabled)
    if enabled then
        GAMEPAD_CHARACTER_CREATE_MANAGER:ShowLoreInfo(GAMEPAD_BUCKET_CUSTOM_CONTROL_RACE)
    else
        GAMEPAD_CHARACTER_CREATE_MANAGER:HideLoreInfo()
    end

    ZO_CharacterCreateSelector_Gamepad.EnableFocus(self, enabled)
end

function ZO_CharacterCreateRaceSelector_Gamepad:GetInformationTooltipStrings(buttonInfo)
    local title = ""
    local description = ""

    if buttonInfo and not buttonInfo.isSelectable then
        local buttonPosition = buttonInfo.gamepadPosition
        local buttonControl = self:GetButton(buttonPosition)
        if internalassert(buttonControl, "missing control for button") then
            local restrictionReason, restrictingCollectible = GetRaceRestrictionReason(buttonControl.defId)
            description = ZO_CHARACTERCREATE_MANAGER.GetOptionRestrictionString(restrictionReason, restrictingCollectible)
            if description ~= "" then
                if restrictingCollectible ~= 0 and IsCollectiblePurchasable(restrictingCollectible) then
                    description = string.format("%s\n\n%s", description, GetString(SI_CHARACTER_CREATE_RESTRICTION_COLLECTIBLE_PURCHASABLE))
                end

                title = self:GetBannerText(buttonControl)
            end
        end
    end

    return title, description
end

function ZO_CharacterCreateRaceSelector_Gamepad:GetNarrationText()
    local narrations = {}
    table.insert(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(GetString(SI_CREATE_CHARACTER_RACE_LABEL)))

    local isSelected = self.selectedPosition == self.currentHighlight
    local currentButton = self:GetCurrentButton()
    if currentButton then
        table.insert(narrations, ZO_FormatRadioButtonNarrationText(self:GetBannerText(currentButton), isSelected))
    end

    local buttonInfo = self:GetButtonInfo(self.currentHighlight)
    if buttonInfo then
        local tooltipTitle, tooltipDescription = self:GetInformationTooltipStrings(buttonInfo)
        table.insert(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(tooltipTitle))
        table.insert(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(tooltipDescription))
        if isSelected then
            table.insert(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(buttonInfo.lore))
        end
    end
    return narrations
end

--
-- Character Creation Class
--

ZO_CharacterCreateClassSelector_Gamepad = ZO_CharacterCreateSelector_Gamepad:Subclass()

function ZO_CharacterCreateClassSelector_Gamepad:New(...)
    return ZO_CharacterCreateSelector_Gamepad.New(self, ...)
end

function ZO_CharacterCreateClassSelector_Gamepad:GetSelectionInfo(control)
    return GAMEPAD_CHARACTER_CREATE_MANAGER.characterData:GetClassInfo()
end

function ZO_CharacterCreateClassSelector_Gamepad:GetBannerText(control)
    local characterMode = ZO_CHARACTERCREATE_MANAGER:GetCharacterMode()
    local currentGender = CharacterCreateGetGender(characterMode)

    return zo_strformat(SI_CLASS_NAME, GetClassName(currentGender, control.defId))
end

function ZO_CharacterCreateClassSelector_Gamepad:SetHighlightIndexByColumn(index, moveDown)
    self:SetHighlight(index)
end

function ZO_CharacterCreateClassSelector_Gamepad:EnableFocus(enabled)
    if enabled then
        GAMEPAD_CHARACTER_CREATE_MANAGER:ShowLoreInfo(GAMEPAD_BUCKET_CUSTOM_CONTROL_CLASS)
    else
        GAMEPAD_CHARACTER_CREATE_MANAGER:HideLoreInfo()
    end

    ZO_CharacterCreateSelector_Gamepad.EnableFocus(self, enabled)
end

function ZO_CharacterCreateClassSelector_Gamepad:GetInformationTooltipStrings(buttonInfo)
    local title = ""
    local description = ""

    if buttonInfo and not buttonInfo.isSelectable then
        local buttonPosition = buttonInfo.gamepadPosition
        local buttonControl = self:GetButton(buttonPosition)
        if internalassert(buttonControl, "missing control for button") then
            local restrictionReason, restrictingCollectible = GetClassRestrictionReason(buttonControl.defId)
            description = ZO_CHARACTERCREATE_MANAGER.GetOptionRestrictionString(restrictionReason, restrictingCollectible)
            if description ~= "" then
                if restrictingCollectible ~= 0 and IsCollectiblePurchasable(restrictingCollectible) then
                    description = string.format("%s\n\n%s", description, GetString(SI_CHARACTER_CREATE_RESTRICTION_COLLECTIBLE_PURCHASABLE))
                end

                title = self:GetBannerText(buttonControl)
            end
        end
    end

    return title, description
end

function ZO_CharacterCreateClassSelector_Gamepad:GetNarrationText()
    local narrations = {}
    table.insert(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(GetString(SI_CREATE_CHARACTER_GAMEPAD_CLASS_LABEL)))

    local isSelected = self.selectedPosition == self.currentHighlight
    local currentButton = self:GetCurrentButton()
    if currentButton then
        table.insert(narrations, ZO_FormatRadioButtonNarrationText(self:GetBannerText(currentButton), isSelected))
    end

    local buttonInfo = self:GetButtonInfo(self.currentHighlight)
    if buttonInfo then
        local tooltipTitle, tooltipDescription = self:GetInformationTooltipStrings(buttonInfo)
        table.insert(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(tooltipTitle))
        table.insert(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(tooltipDescription))
        if isSelected then
            table.insert(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(buttonInfo.lore))
        end
    end
    return narrations
end