Option Strict On
Option Explicit On

Imports System

Namespace Nexamas.UI.Motion

    ''' <summary>
    ''' Internal scheduler-backed runner for one MASMotionTimeline.
    ''' </summary>
    ''' <remarks>
    ''' The runner is the official runtime execution layer between a deterministic
    ''' MASMotionTimeline and a visual consumer. It owns no timer, thread, renderer,
    ''' or public API. It registers with MASMotionFrameClock, which is advanced only
    ''' from MASSkiaFrameScheduler after a rendered frame completes.
    ''' </remarks>
    Friend NotInheritable Class MASMotionTimelineRunner
        Implements IDisposable

        Private ReadOnly _owner As Object
        Private ReadOnly _consumerName As String
        Private ReadOnly _timeline As MASMotionTimeline
        Private ReadOnly _requestFrame As Action
        Private ReadOnly _applyFrame As Action(Of MASMotionFrame)
        Private ReadOnly _completed As Action(Of MASMotionFrame)
        Private ReadOnly _motionFrameClock As MASMotionFrameClock

        Private _started As Boolean
        Private _finished As Boolean
        Private _disposed As Boolean

        Friend Sub New(owner As Object,
                       consumerName As String,
                       plan As MASMotionTransitionPlan,
                       requestFrame As Action,
                       applyFrame As Action(Of MASMotionFrame),
                       Optional completed As Action(Of MASMotionFrame) = Nothing,
                       Optional motionFrameClock As MASMotionFrameClock = Nothing)
            If owner Is Nothing Then Throw New ArgumentNullException(NameOf(owner))
            If plan Is Nothing Then Throw New ArgumentNullException(NameOf(plan))
            If requestFrame Is Nothing Then Throw New ArgumentNullException(NameOf(requestFrame))
            If applyFrame Is Nothing Then Throw New ArgumentNullException(NameOf(applyFrame))

            _owner = owner
            _consumerName = If(consumerName, String.Empty)
            _timeline = MASMotionSystem.CreateTimeline(plan)
            _requestFrame = requestFrame
            _applyFrame = applyFrame
            _completed = completed
            _motionFrameClock = If(motionFrameClock, MASMotionSystem.ResolveFrameClockForOwner(owner))
        End Sub

        Friend ReadOnly Property IsRunning As Boolean
            Get
                Return _started AndAlso Not _finished AndAlso Not _disposed
            End Get
        End Property

        Friend ReadOnly Property Plan As MASMotionTransitionPlan
            Get
                Return _timeline.Plan
            End Get
        End Property

        Friend Sub Start()
            If _disposed OrElse _finished OrElse _started Then Return

            _started = True
            Nexamas.UI.Performance.MASPerformanceSystem.RecordMotionTimelineRunnerStarted()

            Dim initialFrame As MASMotionFrame = _timeline.CurrentFrame()
            ApplyFrameSafe(initialFrame)
            If _finished Then Return

            If Not _timeline.Plan.ShouldAnimate Then
                CompleteNow()
                Return
            End If

            If _motionFrameClock Is Nothing Then
                CompleteNow()
                Return
            End If

            _motionFrameClock.Register(
                owner:=_owner,
                consumerName:=_consumerName,
                requestFrame:=_requestFrame,
                advanceFrame:=AddressOf AdvanceFrame)
        End Sub

        Friend Sub CompleteNow()
            If _disposed OrElse _finished Then Return

            Dim frame As MASMotionFrame = _timeline.Complete()
            _finished = True
            If _motionFrameClock IsNot Nothing Then _motionFrameClock.Unregister(_owner)
            ApplyFrameSafe(frame)
            Nexamas.UI.Performance.MASPerformanceSystem.RecordMotionTimelineRunnerCompleted()
            CompleteSafe(frame)
            RequestFrameSafe()
        End Sub

        Friend Sub Cancel()
            If _disposed OrElse _finished Then Return

            Dim frame As MASMotionFrame = _timeline.Cancel()
            _finished = True
            If _motionFrameClock IsNot Nothing Then _motionFrameClock.Unregister(_owner)
            ApplyFrameSafe(frame)
            RequestFrameSafe()
        End Sub

        Private Function AdvanceFrame(deltaMs As Integer) As Boolean
            If _disposed OrElse _finished Then Return False

            Dim frame As MASMotionFrame = _timeline.Advance(deltaMs)
            ApplyFrameSafe(frame)
            If _finished Then Return False

            If frame.IsComplete Then
                _finished = True
                If _motionFrameClock IsNot Nothing Then _motionFrameClock.Unregister(_owner)
                Nexamas.UI.Performance.MASPerformanceSystem.RecordMotionTimelineRunnerCompleted()
                CompleteSafe(frame)
                RequestFrameSafe()
                Return False
            End If

            Return True
        End Function

        Private Sub ApplyFrameSafe(frame As MASMotionFrame)
            Try
                _applyFrame.Invoke(frame)
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtException1)
                _finished = True
            End Try
        End Sub

        Private Sub CompleteSafe(frame As MASMotionFrame)
            If _completed Is Nothing Then Return

            Try
                _completed.Invoke(frame)
            Catch masCaughtException2 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtException2)
            End Try
        End Sub

        Private Sub RequestFrameSafe()
            Try
                _requestFrame.Invoke()
            Catch masCaughtException3 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtException3)
            End Try
        End Sub

        Public Sub Dispose() Implements IDisposable.Dispose
            If _disposed Then Return
            _disposed = True
            _finished = True
            If _motionFrameClock IsNot Nothing Then _motionFrameClock.Unregister(_owner)
        End Sub

    End Class

End Namespace
