Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.General
Imports System.Collections.Generic
Imports Nexamas.UI.Motion
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    Partial Public NotInheritable Class MASAgendaView

#Region "Agenda event selection motion"

        Private _agendaSelectionRunner As MASMotionTimelineRunner
        Private _agendaSelectionFromIndex As Integer = -1
        Private _agendaSelectionToIndex As Integer = -1
        Private _agendaSelectionProgress As Single = 1.0F
        Private _hasAgendaSelectionMotion As Boolean

        Private Sub StartAgendaEventSelectionMotion(previousIndex As Integer,
                                                    nextIndex As Integer)
            StopAgendaEventSelectionMotion(resetState:=False)

            If _viewMode <> MASAgendaViewMode.Day OrElse previousIndex < 0 OrElse nextIndex < 0 OrElse previousIndex = nextIndex Then
                _hasAgendaSelectionMotion = False
                _agendaSelectionProgress = 1.0F
                Return
            End If

            _agendaSelectionFromIndex = previousIndex
            _agendaSelectionToIndex = nextIndex
            _agendaSelectionProgress = 0.0F
            _hasAgendaSelectionMotion = True

            Dim plan As MASMotionTransitionPlan = MASMotionSystem.CreateTransitionPlan(MASMotionTokenId.RowSelectionMove, 0.0F, 1.0F)
            If Not plan.ShouldAnimate Then
                CompleteAgendaEventSelectionMotionFrame(Nothing)
                Return
            End If

            _agendaSelectionRunner = MASMotionSystem.CreateTimelineRunner(
                owner:=Me,
                consumerName:="MASAgendaView.EventSelection",
                plan:=plan,
                requestFrame:=AddressOf InvalidateVisual,
                applyFrame:=AddressOf ApplyAgendaEventSelectionMotionFrame,
                completed:=AddressOf CompleteAgendaEventSelectionMotionFrame)
            _agendaSelectionRunner.Start()

            InvalidateVisual()
        End Sub

        Private Sub ApplyAgendaEventSelectionMotionFrame(frame As MASMotionFrame)
            If frame Is Nothing Then Return
            _agendaSelectionProgress = MASMotionSystem.ClampProgress(frame.Value)
            _hasAgendaSelectionMotion = True
            InvalidateVisual()
        End Sub

        Private Sub CompleteAgendaEventSelectionMotionFrame(frame As MASMotionFrame)
            StopAgendaEventSelectionMotion(resetState:=True)
            InvalidateVisual()
        End Sub

        Private Sub StopAgendaEventSelectionMotion(resetState As Boolean)
            If _agendaSelectionRunner IsNot Nothing Then
                _agendaSelectionRunner.Dispose()
                _agendaSelectionRunner = Nothing
            End If

            If resetState Then
                _agendaSelectionFromIndex = -1
                _agendaSelectionToIndex = -1
                _agendaSelectionProgress = 1.0F
                _hasAgendaSelectionMotion = False
            End If
        End Sub

        Private Function IsAgendaEventSelectionMotionActive() As Boolean
            Return _viewMode = MASAgendaViewMode.Day AndAlso _hasAgendaSelectionMotion AndAlso _agendaSelectionRunner IsNot Nothing AndAlso _agendaSelectionRunner.IsRunning
        End Function

        Private Function ShouldSuppressAgendaEventSelectionFill(eventIndex As Integer) As Boolean
            Return IsAgendaEventSelectionMotionActive() AndAlso eventIndex = _agendaSelectionToIndex
        End Function

        Private Sub DrawAgendaEventSelectionMotionOverlay(canvas As SKCanvas,
                                                         ctx As MASThemeContext,
                                                         slots As List(Of AgendaEventLayoutSlot),
                                                         dpi As Single,
                                                         plan As MASAgendaViewLayoutPlan)
            If canvas Is Nothing OrElse ctx Is Nothing OrElse slots Is Nothing OrElse plan Is Nothing Then Return
            If Not IsAgendaEventSelectionMotionActive() Then Return

            Dim fromRect As SKRect = ResolveAgendaSelectionMotionRect(slots, _agendaSelectionFromIndex)
            Dim toRect As SKRect = ResolveAgendaSelectionMotionRect(slots, _agendaSelectionToIndex)
            If fromRect.Width <= 1.0F OrElse fromRect.Height <= 1.0F OrElse toRect.Width <= 1.0F OrElse toRect.Height <= 1.0F Then Return

            Dim rect As SKRect = PixelSnap.SnapRect(LerpRect(fromRect, toRect, MASMotionSystem.ClampProgress(_agendaSelectionProgress)), Math.Max(0.64F, dpi))
            Dim radius As Single = Math.Max(4.0F * dpi, plan.EventRadiusPx)
            Dim accent As SKColor = MASAgendaViewPolicy.ResolveAccentColor(ctx)

            Using fill As New SKPaint With {
                .IsAntialias = True,
                .IsDither = True,
                .Style = SKPaintStyle.Fill,
                .Color = accent.WithAlpha(AgendaViewTokens.EventSelectedAlpha),
                .BlendMode = SKBlendMode.SrcOver
            }
                canvas.DrawRoundRect(rect, radius, radius, fill)
            End Using
        End Sub

        Private Shared Function ResolveAgendaSelectionMotionRect(slots As List(Of AgendaEventLayoutSlot),
                                                                 eventIndex As Integer) As SKRect
            If slots Is Nothing OrElse eventIndex < 0 Then Return SKRect.Empty
            For Each slot As AgendaEventLayoutSlot In slots
                If slot IsNot Nothing AndAlso slot.EventIndex = eventIndex Then Return slot.Rect
            Next
            Return SKRect.Empty
        End Function

        Private Shared Function LerpRect(fromRect As SKRect,
                                         toRect As SKRect,
                                         progress As Single) As SKRect
            Dim p As Single = MASMotionSystem.ClampProgress(progress)
            Return New SKRect(
                fromRect.Left + ((toRect.Left - fromRect.Left) * p),
                fromRect.Top + ((toRect.Top - fromRect.Top) * p),
                fromRect.Right + ((toRect.Right - fromRect.Right) * p),
                fromRect.Bottom + ((toRect.Bottom - fromRect.Bottom) * p))
        End Function

#End Region

#Region "Dispose"

        Protected Overrides Sub Dispose(disposing As Boolean)
            If disposing Then StopAgendaEventSelectionMotion(resetState:=True)
            MyBase.Dispose(disposing)
        End Sub

#End Region

    End Class

End Namespace
