Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.General
Imports Nexamas.UI.Motion
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    Partial Public NotInheritable Class MASCalendarView

#Region "Calendar selection motion"

        Private _selectionMotionRunner As MASMotionTimelineRunner
        Private _selectionMotionFromDate As Nullable(Of Date)
        Private _selectionMotionToDate As Nullable(Of Date)
        Private _selectionMotionProgress As Single = 1.0F
        Private _hasSelectionMotion As Boolean

        Private Sub StartCalendarViewSelectionMotion(previousDate As Nullable(Of Date),
                                                     nextDate As Nullable(Of Date))
            StopCalendarViewSelectionMotion(resetState:=False)

            If Not previousDate.HasValue OrElse Not nextDate.HasValue OrElse previousDate.Value.Date = nextDate.Value.Date Then
                _hasSelectionMotion = False
                _selectionMotionProgress = 1.0F
                Return
            End If

            _selectionMotionFromDate = previousDate.Value.Date
            _selectionMotionToDate = nextDate.Value.Date
            _selectionMotionProgress = 0.0F
            _hasSelectionMotion = True

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

            _selectionMotionRunner = MASMotionSystem.CreateTimelineRunner(
                owner:=Me,
                consumerName:="MASCalendarView.Selection",
                plan:=plan,
                requestFrame:=AddressOf InvalidateVisual,
                applyFrame:=AddressOf ApplyCalendarViewSelectionMotionFrame,
                completed:=AddressOf CompleteCalendarViewSelectionMotionFrame)
            _selectionMotionRunner.Start()

            InvalidateVisual()
        End Sub

        Private Sub ApplyCalendarViewSelectionMotionFrame(frame As MASMotionFrame)
            If frame Is Nothing Then Return
            _selectionMotionProgress = MASMotionSystem.ClampProgress(frame.Value)
            _hasSelectionMotion = True
            InvalidateVisual()
        End Sub

        Private Sub CompleteCalendarViewSelectionMotionFrame(frame As MASMotionFrame)
            StopCalendarViewSelectionMotion(resetState:=True)
            InvalidateVisual()
        End Sub

        Private Sub StopCalendarViewSelectionMotion(resetState As Boolean)
            If _selectionMotionRunner IsNot Nothing Then
                _selectionMotionRunner.Dispose()
                _selectionMotionRunner = Nothing
            End If

            If resetState Then
                _selectionMotionFromDate = Nothing
                _selectionMotionToDate = Nothing
                _selectionMotionProgress = 1.0F
                _hasSelectionMotion = False
            End If
        End Sub

        Private Function ShouldSuppressCalendarViewSelectionFill(value As Date) As Boolean
            Return _hasSelectionMotion AndAlso _selectionMotionToDate.HasValue AndAlso _selectionMotionToDate.Value.Date = value.Date
        End Function

        Private Sub DrawCalendarViewSelectionMotionOverlay(canvas As SKCanvas,
                                                           dpi As Single,
                                                           fillColor As SKColor,
                                                           borderColor As SKColor,
                                                           radiusPx As Single)
            If canvas Is Nothing OrElse Not _hasSelectionMotion Then Return
            If Not _selectionMotionFromDate.HasValue OrElse Not _selectionMotionToDate.HasValue Then Return

            Dim fromRect As SKRect = ResolveCalendarViewDayRect(_selectionMotionFromDate.Value.Date)
            Dim toRect As SKRect = ResolveCalendarViewDayRect(_selectionMotionToDate.Value.Date)
            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.SnapRectHalf(LerpRect(fromRect, toRect, MASMotionSystem.ClampProgress(_selectionMotionProgress)))
            Dim radius As Single = Math.Max(4.0F * dpi, radiusPx)

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

            Using stroke As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Stroke,
                .StrokeWidth = Math.Max(1.0F, dpi),
                .Color = borderColor.WithAlpha(CalendarViewTokens.TodayStrokeAlpha)
            }
                canvas.DrawRoundRect(Inset(rect, 1.0F * dpi), Math.Max(2.0F * dpi, radius - 1.0F * dpi), Math.Max(2.0F * dpi, radius - 1.0F * dpi), stroke)
            End Using
        End Sub

        Private Function ResolveCalendarViewDayRect(value As Date) As SKRect
            For i As Integer = 0 To _cells.Count - 1
                Dim cell As CalendarDayCell = _cells(i)
                If cell.Value.Date = value.Date Then Return cell.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)
            StopCalendarViewSelectionMotion(resetState:=True)
            MyBase.Dispose(disposing)
        End Sub

#End Region

    End Class

End Namespace
