Option Strict On
Option Explicit On

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

Namespace Nexamas.UI.Components

    Partial Friend NotInheritable Class MASDatePickerCalendarView

#Region "Calendar selection motion"

        Private _calendarSelectionRunner As MASMotionTimelineRunner
        Private _calendarSelectionFromDate As Nullable(Of Date)
        Private _calendarSelectionToDate As Nullable(Of Date)
        Private _calendarSelectionProgress As Single = 1.0F
        Private _hasCalendarSelectionMotion As Boolean

        Private Sub StartCalendarSelectionMotion(previousDate As Nullable(Of Date),
                                                 nextDate As Nullable(Of Date))
            StopCalendarSelectionMotion(resetState:=False)

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

            _calendarSelectionFromDate = previousDate.Value.Date
            _calendarSelectionToDate = nextDate.Value.Date
            _calendarSelectionProgress = 0.0F
            _hasCalendarSelectionMotion = True

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

            _calendarSelectionRunner = MASMotionSystem.CreateTimelineRunner(
                owner:=Me,
                consumerName:="MASDatePickerCalendarView.Selection",
                plan:=plan,
                requestFrame:=AddressOf InvalidateVisual,
                applyFrame:=AddressOf ApplyCalendarSelectionMotionFrame,
                completed:=AddressOf CompleteCalendarSelectionMotionFrame)
            _calendarSelectionRunner.Start()

            InvalidateVisual()
        End Sub

        Private Sub ApplyCalendarSelectionMotionFrame(frame As MASMotionFrame)
            If frame Is Nothing Then Return
            _calendarSelectionProgress = MASMotionSystem.ClampProgress(frame.Value)
            _hasCalendarSelectionMotion = True
            InvalidateVisual()
        End Sub

        Private Sub CompleteCalendarSelectionMotionFrame(frame As MASMotionFrame)
            StopCalendarSelectionMotion(resetState:=True)
            InvalidateVisual()
        End Sub

        Private Sub StopCalendarSelectionMotion(resetState As Boolean)
            If _calendarSelectionRunner IsNot Nothing Then
                _calendarSelectionRunner.Dispose()
                _calendarSelectionRunner = Nothing
            End If

            If resetState Then
                _calendarSelectionFromDate = Nothing
                _calendarSelectionToDate = Nothing
                _calendarSelectionProgress = 1.0F
                _hasCalendarSelectionMotion = False
            End If
        End Sub

        Private Function ShouldSuppressCalendarSelectionFill(value As Date) As Boolean
            Return _hasCalendarSelectionMotion AndAlso _calendarSelectionToDate.HasValue AndAlso _calendarSelectionToDate.Value.Date = value.Date
        End Function

        Private Sub DrawCalendarSelectionMotionOverlay(canvas As SKCanvas,
                                                       dpi As Single,
                                                       fillColor As SKColor,
                                                       borderColor As SKColor)
            If canvas Is Nothing OrElse Not _hasCalendarSelectionMotion Then Return
            If Not _calendarSelectionFromDate.HasValue OrElse Not _calendarSelectionToDate.HasValue Then Return

            Dim fromRect As SKRect = ResolveCalendarDayRect(_calendarSelectionFromDate.Value.Date)
            Dim toRect As SKRect = ResolveCalendarDayRect(_calendarSelectionToDate.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 progress As Single = MASMotionSystem.ClampProgress(_calendarSelectionProgress)
            Dim rect As SKRect = PixelSnap.SnapRectHalf(LerpRect(fromRect, toRect, progress))
            Dim radius As Single = Math.Max(4.0F, If(_lastLayoutPlan Is Nothing, DatePickerTokens.CalendarSelectedRadiusDip * dpi, _lastLayoutPlan.SelectedRadiusPx))

            Using fill As New SKPaint With {
                .IsAntialias = True,
                .IsDither = True,
                .Style = SKPaintStyle.Fill,
                .Color = fillColor.WithAlpha(DatePickerTokens.CalendarSelectedFillAlpha),
                .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(105)
            }
                canvas.DrawRoundRect(Inset(rect, 1.0F * dpi), radius, radius, stroke)
            End Using
        End Sub

        Private Function ResolveCalendarDayRect(value As Date) As SKRect
            For i As Integer = 0 To _lastDayDates.Length - 1
                If _lastDayDates(i).Date = value.Date Then Return _lastDayCells(i)
            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)
            StopCalendarSelectionMotion(resetState:=True)
            MyBase.Dispose(disposing)
        End Sub

#End Region

    End Class

End Namespace
