do
    local ORIGIN_COLOR =
    {
        [SCENE_MANAGER_MESSAGE_ORIGIN_PREGAME] = ZO_ColorDef:New(1, 0.6, 0.6),
        [SCENE_MANAGER_MESSAGE_ORIGIN_INGAME] = ZO_ColorDef:New(0.6, 1, 0.6),
        [SCENE_MANAGER_MESSAGE_ORIGIN_INTERNAL] = ZO_ColorDef:New(0.6, 0.6, 1),
    }

    function ZO_Scene_GetOriginColor()
        return ORIGIN_COLOR[ZO_REMOTE_SCENE_CHANGE_ORIGIN]
    end
end

----------
--Scene
----------

local g_loggingEnabled = false

-- Alias
SCENE_SHOWING = ZO_STATE.SHOWING
SCENE_SHOWN = ZO_STATE.SHOWN
SCENE_HIDING = ZO_STATE.HIDING
SCENE_HIDDEN = ZO_STATE.HIDDEN

ZO_Scene = ZO_InitializingCallbackObject:Subclass()

function ZO_Scene:Initialize(name, sceneManager)
    self.name = name
    self.state = SCENE_HIDDEN
    self.sceneManager = sceneManager
    self.fragments = {}
    self.restoresHUDSceneToggleUIMode = false
    self.restoresHUDSceneToggleGameMenu = false
    self.disallowEvaluateTransitionCompleteCount = 0

    sceneManager:Add(self)
end

function ZO_Scene:AddFragment(fragment)
    if not self:HasFragment(fragment) then
        table.insert(self.fragments, fragment)
        fragment:SetSceneManager(self.sceneManager)
        fragment:Refresh()
    end
end

function ZO_Scene:RemoveFragment(fragment)
    for i = 1, #self.fragments do
        if(self.fragments[i] == fragment) then
            table.remove(self.fragments, i)
            fragment:Refresh()
            break
        end
    end
end

function ZO_Scene:HasFragment(fragment)
    for i = 1, #self.fragments do
        if(self.fragments[i] == fragment) then
            return true
        end
    end
    return false
end

function ZO_Scene:GetFragments()
    return self.fragments
end

function ZO_Scene:FragmentIterator(filterFunctions)
    return ZO_FilteredNumericallyIndexedTableIterator(self.fragments, filterFunctions)
end

function ZO_Scene:AddTemporaryFragment(fragment)
    if not self:HasFragment(fragment) then
        if(not self.temporaryFragments) then
            self.temporaryFragments = {}
        end
        self.temporaryFragments[fragment] = true
        self:AddFragment(fragment)
        fragment:SetSceneManager(self.sceneManager)
        fragment:Refresh()
    end
end

function ZO_Scene:RemoveTemporaryFragment(fragment)
    if self.temporaryFragments and self.temporaryFragments[fragment] then
        self.temporaryFragments[fragment] = nil
        self:RemoveFragment(fragment)
        fragment:Refresh()
    end
end

function ZO_Scene:AddFragmentGroup(fragments)
    for _, fragment in pairs(fragments) do
        self:AddFragment(fragment)
    end
end

function ZO_Scene:RemoveFragmentGroup(fragments)
    for _, fragment in pairs(fragments) do
        self:RemoveFragment(fragment)
    end
end

function ZO_Scene:GetName()
    return self.name
end

function ZO_Scene:GetState()
    return self.state
end

function ZO_Scene:IsShowing()
    return self.state == SCENE_SHOWN or self.state == SCENE_SHOWING
end

function ZO_Scene:IsHiding()
    return self.state == SCENE_HIDING or self.state == SCENE_HIDDEN
end

function ZO_Scene:HasFragmentWithCategory(category)
    return self:GetFragmentWithCategory(category) ~= nil
end

function ZO_Scene:GetFragmentWithCategory(category)
    for _, fragment in ipairs(self.fragments) do
        local fragmentCategory = fragment:GetCategory()
        if(fragmentCategory == category) then
            return fragment
        end
    end
end

function ZO_Scene:SetState(newState)
    if self.state ~= newState then
        self:Log(string.format("Scene %s", newState))
        local oldState = self.state
        --Making a local for the scene name so it appears in the traceback in case of errors
        local name = self.name
        self.sceneManager:OnPreSceneStateChange(self, oldState, newState)
        self.state = newState
        if self.state == SCENE_SHOWING then
            self.wasShownInGamepadPreferredMode = IsInGamepadPreferredMode()
            self:UpdateFragmentsToCurrentSceneManager()
        end
        --We will DetermineIfTransitionIsComplete when calling RefreshFragments below. Allowing DetermineIfTransitionIsComplete before then will not have allowed the fragments to react the scene state change which
        --can cause the scene to finish the transition before some fragments even got a chance to try hiding or showing. The specific case that triggered this was removing a temporary fragment in the scene hiding callback.
        self:DisallowEvaluateTransitionComplete()
        self:FireCallbacks("StateChange", oldState, newState)
        self.sceneManager:OnSceneStateChange(self, oldState, newState)
        if self.state == SCENE_HIDDEN then
            self:RemoveTemporaryFragments()
        end
        self:AllowEvaluateTransitionComplete()
        local AS_A_RESULT_OF_SCENE_STATE_CHANGE = true
        self:RefreshFragments(AS_A_RESULT_OF_SCENE_STATE_CHANGE)
    end
end

function ZO_Scene:OnRemovedFromQueue()
    --Meant to be overriden
end

function ZO_Scene:RemoveTemporaryFragments()
    if(self.temporaryFragments) then
        for tempFragment in pairs(self.temporaryFragments) do
            self:RemoveFragment(tempFragment)
            tempFragment:Refresh()
        end
        self.temporaryFragments = nil
    end
end

function ZO_Scene:RefreshFragmentsHelper(asAResultOfSceneStateChange, ...)
    local NO_CUSTOM_SHOW_PARAMETER = nil
    local NO_CUSTOM_HIDE_PARAMETER = nil
    for i = 1, select("#", ...) do
        local fragment = select(i, ...)
        fragment:Refresh(NO_CUSTOM_SHOW_PARAMETER, NO_CUSTOM_HIDE_PARAMETER, asAResultOfSceneStateChange, self)
    end
end

function ZO_Scene:RefreshFragments(asAResultOfSceneStateChange)
    --wait until after we have refreshed all fragments to evaluate if we're done 
    self:DisallowEvaluateTransitionComplete()
    --Protect against fragments being added or removed during iteration by unpacking onto the stack
    self:RefreshFragmentsHelper(asAResultOfSceneStateChange, unpack(self.fragments))
    self:AllowEvaluateTransitionComplete()
    self:DetermineIfTransitionIsComplete()
end

function ZO_Scene:UpdateFragmentsToCurrentSceneManager()
    for _, fragment in ipairs(self.fragments) do
        fragment:SetSceneManager(self.sceneManager)
    end
end

function ZO_Scene:OnSceneFragmentStateChange(fragment, oldState, newState)
    self:DetermineIfTransitionIsComplete()
end

function ZO_Scene:HideAllHideOnSceneHiddenFragments(...)
    local allHiddenImmediately = true
    for i = 1, select("#", ...) do
        local fragment = select(i, ...)
        if fragment:GetHideOnSceneHidden() and not fragment:ComputeIfFragmentShouldShow() then
            fragment:Hide()
            if fragment:GetState() ~= SCENE_FRAGMENT_HIDDEN then
                allHiddenImmediately = false
            end
        end
    end
    return allHiddenImmediately
end

function ZO_Scene:IsTransitionComplete()
    local hasHideOnSceneHiddenFragments = false
    if self.state == SCENE_SHOWING or self.state == SCENE_HIDING then
        for _, fragment in ipairs(self.fragments) do
            local fragmentState = fragment:GetState()
            if self.state == SCENE_SHOWING then
                --If we waited for a fragment with a conditional to show before considering the scene shown we may wait forever,
                --because the conditional may not be true. So we just ignore them on show.
                if not fragment:HasConditional() and fragmentState ~= SCENE_FRAGMENT_SHOWN then
                    return false
                end
            elseif self.state == SCENE_HIDING then
                if fragmentState == SCENE_FRAGMENT_HIDING then
                    if fragment:GetHideOnSceneHidden() then
                        hasHideOnSceneHiddenFragments = true
                    else
                        return false
                    end
                end
            end
        end
    end

    if hasHideOnSceneHiddenFragments then
        --dont evaluate whether we should transition as a result of hiding a fragment here
        --since we're already evaluating that right now and will return the correct result
        self:DisallowEvaluateTransitionComplete()
        --Protect against fragments being added or removed during iteration by unpacking onto the stack
        local allHiddenImmediately = self:HideAllHideOnSceneHiddenFragments(unpack(self.fragments))
        self:AllowEvaluateTransitionComplete()
        return allHiddenImmediately
    end

    return true
end

function ZO_Scene:AllowEvaluateTransitionComplete()
    self.disallowEvaluateTransitionCompleteCount = self.disallowEvaluateTransitionCompleteCount - 1
end

function ZO_Scene:DisallowEvaluateTransitionComplete()
    self.disallowEvaluateTransitionCompleteCount = self.disallowEvaluateTransitionCompleteCount + 1
end

function ZO_Scene:DetermineIfTransitionIsComplete()
    if self.disallowEvaluateTransitionCompleteCount ~= 0 then
        return
    end

    local nextState = nil
    if self.state == SCENE_SHOWING then
        nextState = SCENE_SHOWN
    elseif self.state == SCENE_HIDING then
        nextState = SCENE_HIDDEN
    end

    if not nextState then
        return
    end

    if self:IsTransitionComplete() then
        self:OnTransitionComplete(nextState)
    end
end

function ZO_Scene:OnTransitionComplete(nextState)
    self:SetState(nextState)
end

function ZO_Scene:GetSceneGroup()
    return self.sceneGroup
end

function ZO_Scene:SetSceneGroup(sceneGroup)
    self.sceneGroup = sceneGroup
end

function ZO_Scene:SetHideSceneConfirmationCallback(callback)
    self.hideSceneConfirmationCallback = callback
end

function ZO_Scene:HasHideSceneConfirmation()
    return self.hideSceneConfirmationCallback ~= nil
end

function ZO_Scene:ConfirmHideScene(nextSceneName, push, nextSceneClearsSceneStack, numScenesNextScenePops, bypassHideSceneConfirmationReason)
    self.hideSceneConfirmationNextSceneName = nextSceneName
    self.hideSceneConfirmationPush = push
    self.hideSceneConfirmationNextSceneClearsSceneStack = nextSceneClearsSceneStack
    self.hideSceneConfirmationNumScenesNextScenePops = numScenesNextScenePops
    self.hideSceneConfirmationCallback(self, nextSceneName, bypassHideSceneConfirmationReason)
end

local function ClearHideSceneConfirmationState(self)
    self.hideSceneConfirmationNextSceneName = nil
    self.hideSceneConfirmationPush = nil
    self.hideSceneConfirmationNextSceneClearsSceneStack = nil
    self.hideSceneConfirmationNumScenesNextScenePops = nil
    self:UnregisterAllCallbacks("HideSceneConfirmationResult")
end

function ZO_Scene:AcceptHideScene()
    local nextSceneName = self.hideSceneConfirmationNextSceneName
    local push = self.hideSceneConfirmationPush
    local nextSceneClearsSceneStack = self.hideSceneConfirmationNextSceneClearsSceneStack
    local numScenesNextScenePops = self.hideSceneConfirmationNumScenesNextScenePops
    self:FireCallbacks("HideSceneConfirmationResult", true)
    ClearHideSceneConfirmationState(self)
    SCENE_MANAGER:Show(nextSceneName, push, nextSceneClearsSceneStack, numScenesNextScenePops, ZO_BHSCR_ALREADY_SEEN)
end

function ZO_Scene:RejectHideScene()
    self:FireCallbacks("HideSceneConfirmationResult", false)
    ClearHideSceneConfirmationState(self)
end

function ZO_Scene:SetInputPreferredMode(mode)
    self.inputPreferredMode = mode
end

function ZO_Scene:WasShownInGamepadPreferredMode()
    return self.wasShownInGamepadPreferredMode
end

function ZO_Scene:WasRequestedToShowInGamepadPreferredMode()
    return self.wasRequestedToShowInGamepadPreferredMode or self.inputPreferredMode == INPUT_PREFERRED_MODE_ALWAYS_GAMEPAD
end

function ZO_Scene:SetWasRequestedToShowInGamepadPreferredMode(gamepad)
    self.wasRequestedToShowInGamepadPreferredMode = gamepad
end

function ZO_Scene:IsRemoteScene()
    return false
end

function ZO_Scene:DoesSceneRestoreHUDSceneFromToggleUIMode()
    return self.restoresHUDSceneToggleUIMode
end

function ZO_Scene:DoesSceneRestoreHUDSceneFromToggleGameMenu()
    return self.restoresHUDSceneToggleGameMenu
end

function ZO_Scene:SetSceneRestoreHUDSceneToggleUIMode(restoreScene)
    self.restoresHUDSceneToggleUIMode = restoreScene
end

function ZO_Scene:SetSceneRestoreHUDSceneToggleGameMenu(restoreScene)
    self.restoresHUDSceneToggleGameMenu = restoreScene
end

function ZO_Scene:SetHandleGamepadPreferredModeChangedCallback(handleGamepadPreferredModeChangedCallback)
    self.handleGamepadPreferredModeChangedCallback = handleGamepadPreferredModeChangedCallback
end

function ZO_Scene:GetHandleGamepadPreferredModeChangedCallback()
    return self.handleGamepadPreferredModeChangedCallback
end

function ZO_Scene:Log(message)
    if WriteToInterfaceLog and g_loggingEnabled then
        WriteToInterfaceLog(string.format("%s - %s - %s", ZO_Scene_GetOriginColor():Colorize(GetString("SI_SCENEMANAGERMESSAGEORIGIN", ZO_REMOTE_SCENE_CHANGE_ORIGIN)), self.name, message))
    end
end

----------
--Remote Scene
----------

ZO_RemoteScene = ZO_Scene:Subclass()

function ZO_RemoteScene:New(...)
    return ZO_Scene.New(self, ...)
end

function ZO_RemoteScene:Initialize(name, sceneManager)
    ZO_Scene.Initialize(self, name, sceneManager)

    self.waitingOnRemoteFragmentTransition = false
    self.fragmentsFinishedTransition = false
end

function ZO_RemoteScene:SetState(newState)
    if self.state ~= newState then
        if newState == SCENE_HIDING or newState == SCENE_SHOWING then
            self.waitingOnRemoteFragmentTransition = true
            self.fragmentsFinishedTransition = false
        else
            self.waitingOnRemoteFragmentTransition = false
        end

        -- call parent SetState after we send out message because it could
        -- trigger another call to SetState and the events would be out of order
        ZO_Scene.SetState(self, newState)
    end
end

function ZO_RemoteScene:OnTransitionComplete(nextState)
    self.fragmentsFinishedTransition = true
    self:Log("Local Fragments Done Transitioning")
    SCENE_MANAGER:SendFragmentCompleteMessage()

    -- we are done, but some remote scene might not have reported that it is also done
    -- only set the state if both scenes have completed their transition
    if not self.waitingOnRemoteFragmentTransition then
        ZO_Scene.OnTransitionComplete(self, nextState)
    end
end

function ZO_RemoteScene:IsRemoteScene()
    return true
end

function ZO_RemoteScene:SetSequenceNumber(sequenceNumber)
    self.sequenceNumber = sequenceNumber
end

function ZO_RemoteScene:GetSequenceNumber()
    return self.sequenceNumber
end

function ZO_RemoteScene:AreFragmentsDoneTransitioning()
    return self.fragmentsFinishedTransition
end

function ZO_RemoteScene:OnRemoteSceneFinishedFragmentTransition(sequenceNumber)
    if self.waitingOnRemoteFragmentTransition then
        self:Log("Was Notified Remote Fragments Complete")
        if self:GetSequenceNumber() == sequenceNumber then
            self.waitingOnRemoteFragmentTransition = false
            self:DetermineIfTransitionIsComplete()
        else
            self:Log(string.format("Sequence Numbers did not match. Expected %d, got %d", self:GetSequenceNumber(), sequenceNumber))
        end
    end
end
