Public Class MyPageRight
    Inherits AdornerDecorator
    Public PageUuid As Integer = GetUuid()

    '“返回顶部” 按钮检测的滚动区域
    Public Property PanScroll As String '需要在 Loaded 之后才能获取到控件，所以不能用 Binding 直接绑定（#6664）
        Get
            Return GetValue(PanScrollProperty)
        End Get
        Set(value As String)
            SetValue(PanScrollProperty, value)
        End Set
    End Property
    Private Shared ReadOnly PanScrollProperty =
        DependencyProperty.Register("PanScroll", GetType(String), GetType(MyPageRight), New PropertyMetadata(Nothing))

    '当前状态
    Public Enum PageStates
        Empty '默认状态，页面全空
        LoaderWait '加载环初始等待
        LoaderEnter '加载环进入动画
        LoaderStayForce '加载环正常显示（强制等待）
        LoaderStay '加载环正常显示
        LoaderExit '加载环退出动画
        ContentEnter '内容进入动画
        ContentStay '内容正常显示
        ContentExit '刷新导致的全部退出动画，或页面内容退出（子页面更改）导致的全部退出动画
        PageExit '切换页面导致的全部退出动画
    End Enum
    Private _PageState As PageStates = PageStates.Empty
    Public Property PageState As PageStates
        Get
            Return _PageState
        End Get
        Set(value As PageStates)
            If _PageState = value Then Return
            _PageState = value
            If ModeDebug Then Log("[UI] 页面状态切换为 " & GetStringFromEnum(value))
        End Set
    End Property

#Region "加载器"

    Private PageLoader As LoaderBase
    Private PageLoaderInputInvoke
    Private PageLoaderUi As MyLoading
    Private PanLoader As FrameworkElement
    Private PanContent As FrameworkElement
    Private PanAlways As FrameworkElement
    Private PageLoaderAutoRun As Boolean

    '初始化
    ''' <summary>
    ''' 表明页面存在需要在后台执行的加载器。
    ''' </summary>
    ''' <param name="LoaderUi">MyLoading 控件。</param>
    ''' <param name="PanLoader">MyLoading 控件对应的卡片。</param>
    ''' <param name="PanContent">加载结束后出现的内容容器。</param>
    ''' <param name="PanAlways">无论是否在加载总是要显示的容器。可以为 Nothing。</param>
    ''' <param name="RealLoader">在工作线程执行的加载器。</param>
    ''' <param name="FinishedInvoke">当加载器执行完成，在 UI 线程触发的 UI 初始化事件。</param>
    Public Sub PageLoaderInit(LoaderUi As MyLoading, PanLoader As FrameworkElement, PanContent As FrameworkElement, PanAlways As FrameworkElement,
                              RealLoader As LoaderBase,
                              Optional FinishedInvoke As Action(Of LoaderBase) = Nothing, Optional InputInvoke As Func(Of Object) = Nothing,
                              Optional AutoRun As Boolean = True)
        '初始化参数
        Me.PanLoader = PanLoader
        Me.PanContent = PanContent
        Me.PanAlways = PanAlways
        Me.PageLoader = RealLoader
        Me.PageLoaderUi = LoaderUi
        Me.PageLoaderInputInvoke = InputInvoke
        Me.PageLoaderAutoRun = AutoRun
        '添加结束 Invoke
        If FinishedInvoke IsNot Nothing Then
            AddHandler RealLoader.PreviewFinish,
            Sub()
                Do While PageState = MyPageRight.PageStates.PageExit OrElse PageState = MyPageRight.PageStates.ContentExit
                    Thread.Sleep(10) '不在退出动画时执行 UI 线程操作，避免退出动画被重置
                Loop
                RunInUiWait(Sub() FinishedInvoke(RealLoader))
                Thread.Sleep(20) '由于大量初始化控件会导致掉帧，延迟触发 State 改变事件
            End Sub
        End If
        AddHandler RealLoader.OnStateChangedUi, Sub(Loader As LoaderBase, NewState As LoadState, OldState As LoadState) RunInUi(Sub() PageLoaderState(Loader, NewState, OldState))
        '隐藏 UI
        PanLoader.Visibility = Visibility.Collapsed
        PanContent.Visibility = Visibility.Collapsed
        If PanAlways IsNot Nothing Then PanAlways.Visibility = Visibility.Collapsed
        '初次运行加载器
        If PageLoaderAutoRun Then
            If PageLoader.GetType.Name.StartsWithF("LoaderTask") Then
                PageLoader.Start(CType(PageLoader, Object).StartGetInput(Nothing, PageLoaderInputInvoke))
            Else
                Dim Input = Nothing
                If PageLoaderInputInvoke IsNot Nothing Then Input = PageLoaderInputInvoke()
                PageLoader.Start(Input)
            End If
        End If
        If PageLoader.State = LoadState.Finished AndAlso FinishedInvoke IsNot Nothing Then
            RunInUiWait(Sub() FinishedInvoke(RealLoader)) '加载器已提前完成，直接触发事件
        End If
        '设置加载环
        PageLoaderUi.State = RealLoader
        AddHandler PageLoaderUi.Click, Sub() If RealLoader.State = LoadState.Failed Then PageLoaderRestart() '点击重试事件
    End Sub
    '重试
    Public Sub PageLoaderRestart(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = True) '由外部调用的重试
        If Not PageLoaderAutoRun Then Return
        If PageLoader.GetType.Name.StartsWithF("LoaderTask") Then
            PageLoader.Start(CType(PageLoader, Object).StartGetInput(Input, PageLoaderInputInvoke), IsForceRestart:=IsForceRestart)
        Else
            If Input Is Nothing AndAlso PageLoaderInputInvoke IsNot Nothing Then Input = PageLoaderInputInvoke()
            PageLoader.Start(Input, IsForceRestart:=IsForceRestart)
        End If
    End Sub

#End Region

#Region "事件"

    '外部触发的事件
    ''' <summary>
    ''' 需要切换到当前页面，并且原本的 Loaded 事件已执行完成。
    ''' 需要根据加载器状态，从 Empty 切换到 ContentEnter、LoaderWait、LoaderEnter。
    ''' </summary>
    Public Sub PageOnEnter()
        If ModeDebug Then Log("[UI] 已触发 PageOnEnter")
        RaiseEvent PageEnter()
        Select Case PageState
            Case PageStates.Empty
                If PageLoader Is Nothing OrElse PageLoader.State = LoadState.Finished OrElse PageLoader.State = LoadState.Waiting OrElse PageLoader.State = LoadState.Aborted Then
                    '如果加载器在进入页面时不启动（例如联机），那么在此时就会有 State = Waiting
                    PageState = PageStates.ContentEnter
                    TriggerEnterAnimation(PanAlways, If(PanContent, Child))
                ElseIf PageLoader.State = LoadState.Loading Then
                    PageState = PageStates.LoaderWait
                    AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 200), "PageRight PageChange " & PageUuid)
                Else 'PageLoader.State = LoadState.Failed
                    PageState = PageStates.LoaderEnter
                    TriggerEnterAnimation(PanAlways, PanLoader)
                End If
            Case PageStates.ContentExit
                '和上面的一样，但是不管 PanAlways
                If PageLoader Is Nothing OrElse PageLoader.State = LoadState.Finished OrElse PageLoader.State = LoadState.Waiting OrElse PageLoader.State = LoadState.Aborted Then
                    PageState = PageStates.ContentEnter
                    TriggerEnterAnimation(If(PanContent, Child))
                ElseIf PageLoader.State = LoadState.Loading Then
                    PageState = PageStates.LoaderWait
                    AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 200), "PageRight PageChange " & PageUuid)
                Else 'PageLoader.State = LoadState.Failed
                    PageState = PageStates.LoaderEnter
                    TriggerEnterAnimation(PanLoader)
                End If
            Case PageStates.ContentEnter
                '重复调用 PageOnEnter，直接忽略
            Case Else
                Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnEnter 事件。")
        End Select
    End Sub
    Public Event PageEnter()
    ''' <summary>
    ''' 需要切换到其他页面。
    ''' 需要立即切换至 PageExit 或 Empty。
    ''' </summary>
    Public Sub PageOnExit()
        If ModeDebug Then Log("[UI] 已触发 PageOnExit")
        RaiseEvent PageExit()
        Select Case PageState
            Case PageStates.ContentEnter, PageStates.ContentStay
                PageState = PageStates.PageExit
                TriggerExitAnimation(PanAlways, If(PanContent, Child))
            Case PageStates.LoaderEnter, PageStates.LoaderStayForce, PageStates.LoaderStay
                PageState = PageStates.PageExit
                TriggerExitAnimation(PanAlways, PanLoader)
            Case PageStates.LoaderWait
                PageState = PageStates.PageExit
                TriggerExitAnimation(PanAlways)
            Case PageStates.LoaderExit, PageStates.ContentExit
                PageState = PageStates.PageExit
                If PanAlways IsNot Nothing Then TriggerExitAnimation(PanAlways, If(PanContent, Child))
            Case PageStates.PageExit, PageStates.Empty
        End Select
    End Sub
    Public Event PageExit()
    ''' <summary>
    ''' 即将切换到其他页面，需要强制完成页面状态清理。
    ''' 需要立即切换至 Empty。
    ''' </summary>
    Public Sub PageOnForceExit()
        If PageState = PageStates.Empty Then Return
        If ModeDebug Then Log("[UI] 已触发 PageOnForceExit")
        PageState = PageStates.Empty
        AniStop("PageRight PageChange " & PageUuid)
        '由于动画会被强制中止，所以需要手动进行隐藏
        If PageLoader Is Nothing Then
            Child.Visibility = Visibility.Collapsed
        Else
            PanContent.Visibility = Visibility.Collapsed
            PanLoader.Visibility = Visibility.Collapsed
            If PanAlways IsNot Nothing Then PanAlways.Visibility = Visibility.Collapsed
        End If
    End Sub
    ''' <summary>
    ''' PanContent 中的子页面改变，需要让当前内容退出，再显示新的内容。
    ''' 需要在 PageEnter 事件确认要显示的子页面有哪些。
    ''' </summary>
    Public Sub PageOnContentExit()
        If ModeDebug Then Log("[UI] 已触发 PageOnContentExit")
        If PageLoader IsNot Nothing AndAlso PageLoader.State = LoadState.Loading Then
            'Loading 的加载器可能触发进一步变化，难以预测会触发子页面的动画还是加载器完成的动画
            Throw New Exception("在调用 PageOnContentExit 时，加载器不能为 Loading 状态")
        End If
        Select Case PageState
            Case PageStates.ContentEnter, PageStates.ContentStay
                PageState = PageStates.ContentExit
                TriggerExitAnimation(PanContent)
            Case PageStates.LoaderExit
                PageState = PageStates.ContentExit
            Case PageStates.LoaderEnter, PageStates.LoaderStayForce, PageStates.LoaderStay
                PageState = PageStates.ContentExit
                TriggerExitAnimation(PanLoader)
            Case PageStates.LoaderWait
                PageOnEnter()
            Case PageStates.Empty
                '可能是在离开页面后，其他后台线程调用了本函数，此时不应触发 PageOnEnter()
                '例如：在加入联机房间后，切换到其他页面，然后联机房间被解散
                If IsLoaded Then PageOnEnter()
        End Select
    End Sub

    '内部触发的事件
    ''' <summary>
    ''' 逐个进入动画已执行完成。
    ''' 需要根据目前状态，从 ContentEnter 切换到 ContentStay，或从 LoaderEnter 切换到 LoaderStayForce。
    ''' </summary>
    Private Sub PageOnEnterAnimationFinished()
        If ModeDebug Then Log("[UI] 已触发 PageOnEnterAnimationFinished")
        Select Case PageState
            Case PageStates.ContentEnter
                PageState = PageStates.ContentStay
            Case PageStates.LoaderEnter
                PageState = PageStates.LoaderStayForce
                AniStart(AaCode(AddressOf PageOnLoaderStayFinished, 400), "PageRight PageChange " & PageUuid)
            Case Else
                Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnEnterAnimationFinished 事件。")
        End Select
    End Sub
    ''' <summary>
    ''' 逐个退出动画已执行完成。
    ''' 需要根据目前状态，从 AllExit 切换到 Empty，或从 LoaderExit 切换到 ContentEnter，或从 ContentExit 重新触发 PageOnEnter。
    ''' </summary>
    Private Sub PageOnExitAnimationFinished()
        If ModeDebug Then Log("[UI] 已触发 PageOnExitAnimationFinished")
        Select Case PageState
            Case PageStates.PageExit
                PageState = PageStates.Empty
            Case PageStates.ContentExit
                PageOnEnter()
            Case PageStates.LoaderExit
                PageState = PageStates.ContentEnter
                TriggerEnterAnimation(PanContent)
            Case Else
                Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnExitAnimationFinished 事件。")
        End Select
    End Sub
    ''' <summary>
    ''' 加载环进入等待已结束。
    ''' 需要从 LoaderWait 切换到 LoaderEnter。
    ''' </summary>
    Private Sub PageOnLoaderWaitFinished()
        If ModeDebug Then Log("[UI] 已触发 PageOnLoaderWaitFinished")
        Select Case PageState
            Case PageStates.LoaderWait
                PageState = PageStates.LoaderEnter
                If PanAlways IsNot Nothing AndAlso PanAlways.Visibility = Visibility.Collapsed Then
                    TriggerEnterAnimation(PanAlways, PanLoader)
                Else
                    TriggerEnterAnimation(PanLoader)
                End If
            Case Else
                Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnLoaderWaitFinished 事件。")
        End Select
    End Sub
    ''' <summary>
    ''' 加载环展示等待已结束。
    ''' 需要从 LoaderStayForce 切换到 LoaderStay 或 LoaderExit。
    ''' </summary>
    Private Sub PageOnLoaderStayFinished()
        If ModeDebug Then Log("[UI] 已触发 PageOnLoaderStayFinished")
        Select Case PageState
            Case PageStates.LoaderStayForce
                If PageLoader.State = LoadState.Finished Then
                    PageState = PageStates.LoaderExit
                    TriggerExitAnimation(PanLoader)
                Else
                    PageState = PageStates.LoaderStay
                End If
            Case Else
                Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnLoaderWaitFinished 事件。")
        End Select
    End Sub

    ''' <summary>
    ''' 全局加载状态已改变。
    ''' </summary>
    Private Sub PageLoaderState(sender As Object, NewState As LoadState, OldState As LoadState)
        Select Case NewState
            Case LoadState.Failed, LoadState.Loading
                If OldState = LoadState.Failed OrElse OldState = LoadState.Loading Then Return
                If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Start/Refresh)")
                '（重新）开始运行
                '需要从部分状态切换到 ReloadExit
                Select Case PageState
                    Case PageStates.ContentEnter, PageStates.ContentStay
                        PageState = PageStates.ContentExit
                        TriggerExitAnimation(PanContent)
                    Case PageStates.LoaderExit
                        PageState = PageStates.ContentExit
                End Select
            Case LoadState.Finished, LoadState.Aborted, LoadState.Waiting
                If Not (OldState = LoadState.Failed OrElse OldState = LoadState.Loading) Then Return
                If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Stop/Abort)")
                '运行结束
                '需要从 LoaderWait 切换到 ContentEnter，或从 LoaderStay 切换到 LoaderExit
                Select Case PageState
                    Case PageStates.LoaderWait
                        PageState = PageStates.ContentEnter
                        If PanAlways IsNot Nothing AndAlso PanAlways.Visibility = Visibility.Collapsed Then
                            TriggerEnterAnimation(PanAlways, PanContent)
                        Else
                            TriggerEnterAnimation(PanContent)
                        End If
                    Case PageStates.LoaderStay
                        PageState = PageStates.LoaderExit
                        TriggerExitAnimation(PanLoader)
                End Select
        End Select
    End Sub

#End Region

#Region "动画"

    '逐个进入动画
    Public Sub TriggerEnterAnimation(ParamArray Elements As FrameworkElement())
        Dim RealElements = Elements.Where(Function(e) e IsNot Nothing)
        For Each Element In RealElements
            Element.Visibility = Visibility.Visible '页面均处于默认的隐藏状态
        Next
        Dim AniList As New List(Of AniData)
        Dim Delay As Integer = 0
        '基础动画
        For Each Element In RealElements
            For Each Control As FrameworkElement In GetAllAnimControls(Element, True)
                '还原被隐藏的卡片的消失动画
                Control.IsHitTestVisible = True
                If Control.RenderTransform IsNot Nothing AndAlso TypeOf Control.RenderTransform Is TranslateTransform Then Control.RenderTransform = Nothing
            Next
            For Each Control As FrameworkElement In GetAllAnimControls(Element)
                If TypeOf Control Is MyExtraTextButton Then
                    CType(Control, MyExtraTextButton).Show = True
                Else
                    Control.Opacity = 0
                    Control.RenderTransform = New TranslateTransform(0, -16)
                    AniList.Add(AaOpacity(Control, 1, 100, Delay, New AniEaseOutFluent(AniEasePower.Weak)))
                    AniList.Add(AaTranslateY(Control, 5, 250, Delay, New AniEaseOutFluent))
                    AniList.Add(AaTranslateY(Control, 11, 350, Delay, New AniEaseOutBack))
                    Delay += 25
                End If
            Next
        Next
        '滚动条动画
        Dim Scroll As MyScrollBar = GetFirstScrollViewer(RealElements)
        If Scroll IsNot Nothing Then
            If TypeOf Scroll.RenderTransform IsNot TranslateTransform Then Scroll.RenderTransform = New TranslateTransform(10, 0)
            AniList.Add(AaTranslateX(Scroll, -CType(Scroll.RenderTransform, TranslateTransform).X, 350, 0, New AniEaseOutFluent))
        End If
        '结束
        AniList.Add(AaCode(Sub() PageOnEnterAnimationFinished(),, True))
        AniStart(AniList, "PageRight PageChange " & PageUuid)
    End Sub

    '逐个退出动画
    Public Sub TriggerExitAnimation(ParamArray Elements As FrameworkElement())
        Dim RealElements = Elements.Where(Function(e) e IsNot Nothing)
        Dim AniList As New List(Of AniData)
        Dim Delay As Integer = 0
        For Each Element In RealElements
            For Each Control As FrameworkElement In GetAllAnimControls(Element)
                If TypeOf Control Is MyExtraTextButton Then
                    CType(Control, MyExtraTextButton).Show = False
                Else
                    Control.IsHitTestVisible = False
                    AniList.Add(AaOpacity(Control, -1, 70, Delay))
                    AniList.Add(AaTranslateY(Control, -6, 70, Delay))
                    Delay += 15
                End If
            Next
        Next
        '滚动条动画
        Dim Scroll As MyScrollBar = GetFirstScrollViewer(RealElements)
        If Scroll IsNot Nothing Then
            If TypeOf Scroll.RenderTransform IsNot TranslateTransform Then Scroll.RenderTransform = New TranslateTransform
            AniList.Add(AaTranslateX(Scroll, 10 - CType(Scroll.RenderTransform, TranslateTransform).X, 90, 0, New AniEaseInFluent))
        End If
        '结束
        AniList.Add(AaCode(
        Sub()
            For Each Element In RealElements
                Element.Visibility = Visibility.Collapsed
            Next
            PageOnExitAnimationFinished()
        End Sub,, True))
        AniStart(AniList, "PageRight PageChange " & PageUuid)
    End Sub

    ''' <summary>
    ''' 禁用页面切换动画的控件列表。
    ''' </summary>
    Public DisabledPageAnimControls As New List(Of FrameworkElement)
    ''' <summary>
    ''' 遍历获取所有需要生成动画的控件。
    ''' </summary>
    Friend Function GetAllAnimControls(Element As FrameworkElement, Optional IgnoreInvisibility As Boolean = False) As IEnumerable(Of FrameworkElement)
        Dim AllControls As New List(Of FrameworkElement)
        _GetAllAnimControls(Element, AllControls, IgnoreInvisibility)
        Return AllControls.Except(DisabledPageAnimControls)
    End Function
    Private Sub _GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean)
        If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Return
        If TypeOf Element Is MyCard OrElse TypeOf Element Is MyHint OrElse TypeOf Element Is MyExtraTextButton OrElse TypeOf Element Is TextBlock OrElse TypeOf Element Is MyTextButton Then
            AllControls.Add(Element)
        ElseIf TypeOf Element Is ContentControl Then
            Dim Content = CType(Element, ContentControl).Content
            If Content IsNot Nothing AndAlso TypeOf Content Is FrameworkElement Then _GetAllAnimControls(Content, AllControls, IgnoreInvisibility)
        ElseIf TypeOf Element Is Panel Then
            For Each Element2 In CType(Element, Panel).Children
                If TypeOf Element2 Is FrameworkElement Then _GetAllAnimControls(Element2, AllControls, IgnoreInvisibility)
            Next
        End If
    End Sub

    '查找列表中的第一个滚动条
    Private Function GetFirstScrollViewer(Elements As IEnumerable(Of FrameworkElement)) As MyScrollBar
        Dim Viewer As MyScrollViewer = Nothing
        For Each Element In Elements
            If TypeOf Element Is MyScrollViewer Then
                Viewer = Element
                GoTo FindViewer
            End If
            For Each Control In LogicalTreeHelper.GetChildren(Element)
                If TypeOf Control Is MyScrollViewer Then
                    Viewer = Control
                    GoTo FindViewer
                End If
            Next
        Next
        Return Nothing
FindViewer:
        If Viewer.ComputedVerticalScrollBarVisibility <> Visibility.Visible Then Return Nothing
        Return Viewer.ScrollBar
    End Function

#End Region

End Class
