Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.Scroll
Imports Nexamas.UI.Theming
Imports SkiaSharp

Namespace Nexamas.UI.SkiaScroll

    Friend NotInheritable Class SkiaScrollHost
        Implements IDisposable

        Private ReadOnly _model As ScrollModel
        Private ReadOnly _metrics As ScrollMetrics
        Private ReadOnly _layout As ScrollbarLayout
        Private ReadOnly _controller As ScrollbarController
        Private ReadOnly _painter As ScrollbarPainter

        Private _hostRect As SKRect = SKRect.Empty
        Private _contentHeight As Single
        Private _viewportHeight As Single
        Private _disposed As Boolean
        Friend Sub UpdateThemeContext(ctx As MASThemeContext)
            _painter.UpdateThemeContext(ctx)
        End Sub
        Friend Sub New(metrics As ScrollMetrics,
                       layout As ScrollbarLayout,
                       painter As ScrollbarPainter)

            If metrics Is Nothing Then Throw New ArgumentNullException(NameOf(metrics))
            If layout Is Nothing Then Throw New ArgumentNullException(NameOf(layout))
            If painter Is Nothing Then Throw New ArgumentNullException(NameOf(painter))

            _model = New ScrollModel()
            _metrics = metrics
            _layout = layout
            _controller = New ScrollbarController(_model, _metrics, _layout)
            _painter = painter
        End Sub

        Friend ReadOnly Property Offset As Single
            Get
                Return _model.Position
            End Get
        End Property

        Friend Sub Update(hostRect As SKRect, contentHeight As Single, viewportHeight As Single)
            If _disposed Then Return

            _hostRect = hostRect
            _contentHeight = Math.Max(0.0F, contentHeight)
            _viewportHeight = Math.Max(0.0F, viewportHeight)

            If _hostRect.IsEmpty OrElse _viewportHeight <= 0.0F OrElse _contentHeight <= _viewportHeight Then
                _model.Update(0, 0)
                _controller.Update(_hostRect, _contentHeight, _viewportHeight)
                Return
            End If

            Dim contentPx As Integer = Math.Max(0, CInt(Math.Ceiling(_contentHeight)))
            Dim viewportPx As Integer = Math.Max(0, CInt(Math.Ceiling(_viewportHeight)))

            _model.Update(contentPx, viewportPx)
            _controller.Update(_hostRect, _contentHeight, _viewportHeight)
        End Sub

        Friend Function Tick() As Boolean
            If _disposed Then Return False
            Return _controller.Tick()
        End Function

        Friend ReadOnly Property RequiresPump As Boolean
            Get
                If _disposed Then Return False
                Return _controller.RequiresPump
            End Get
        End Property

        Friend Sub Paint(canvas As SKCanvas, dpi As Single, isHover As Boolean)
            If _disposed Then Return
            If canvas Is Nothing Then Return
            If _hostRect.IsEmpty Then Return
            If _viewportHeight <= 0.0F OrElse _contentHeight <= _viewportHeight Then Return

            If dpi <= 0.0F OrElse Single.IsNaN(dpi) OrElse Single.IsInfinity(dpi) Then
                dpi = 1.0F
            End If

            Dim geo As ScrollbarLayout.Geometry =
                _layout.Compute(_hostRect, _contentHeight, _viewportHeight, _model)

            If geo.TrackRect.IsEmpty OrElse geo.ThumbRect.IsEmpty Then Return

            Dim ctrlState As ScrollbarController.State = _controller.GetState()
            _painter.Paint(canvas, dpi, geo, ctrlState, isHover)
        End Sub

        Friend Sub GetVisibleRange(rowH As Single,
                                   totalItems As Integer,
                                   ByRef firstIndex As Integer,
                                   ByRef rowsToDraw As Integer,
                                   ByRef startY As Single,
                                   contentTop As Single)

            firstIndex = 0
            rowsToDraw = 0
            startY = contentTop

            If _disposed Then Return
            If rowH <= 0.0F OrElse totalItems <= 0 OrElse _viewportHeight <= 0.0F Then Return

            Dim scrollOffset As Single = _model.Position

            firstIndex = CInt(Math.Floor(scrollOffset / rowH))
            If firstIndex < 0 Then firstIndex = 0
            If firstIndex > totalItems - 1 Then firstIndex = totalItems - 1

            Dim yOffset As Single = scrollOffset - (firstIndex * rowH)
            startY = contentTop - yOffset

            rowsToDraw = CInt(Math.Ceiling(_viewportHeight / rowH)) + 2
            If rowsToDraw < 0 Then rowsToDraw = 0
        End Sub

        Friend Function HitTestIndex(pt As SKPoint,
                                     contentRect As SKRect,
                                     rowH As Single,
                                     totalItems As Integer) As Integer

            If _disposed Then Return -1
            If totalItems <= 0 OrElse rowH <= 0.0F Then Return -1
            If Not contentRect.Contains(pt) Then Return -1

            Dim scrollOffset As Single = _model.Position
            Dim yInContent As Single = (pt.Y - contentRect.Top) + scrollOffset
            Dim idx As Integer = CInt(Math.Floor(yInContent / rowH))

            If idx < 0 OrElse idx >= totalItems Then Return -1
            Return idx
        End Function

        Friend Function MouseMove(pt As SKPoint) As Boolean
            If _disposed Then Return False
            Return _controller.MouseMove(pt)
        End Function

        Friend Function MouseDown(pt As SKPoint, rowStepPx As Single, pageFactor As Single) As Boolean
            If _disposed Then Return False
            Return _controller.MouseDown(pt, rowStepPx, pageFactor)
        End Function

        Friend Function MouseUp() As Boolean
            If _disposed Then Return False
            Return _controller.MouseUp()
        End Function

        Friend Function MouseLeave() As Boolean
            If _disposed Then Return False
            Return _controller.MouseLeave()
        End Function

        Friend Function CanMouseWheel(delta As Integer, stepPx As Single) As Boolean
            If _disposed Then Return False
            Return _controller.CanMouseWheel(delta, stepPx)
        End Function

        Friend Function MouseWheel(delta As Integer, stepPx As Single) As Boolean
            If _disposed Then Return False
            Return _controller.MouseWheel(delta, stepPx)
        End Function

        Friend Function EnsureVisible(top As Single, bottom As Single) As Boolean
            If _disposed Then Return False
            Return _controller.EnsureVisible(top, bottom)
        End Function

        Friend Function ScrollToOffset(offsetPx As Single) As Boolean
            If _disposed Then Return False

            Dim oldPos As Integer = _model.Position

            If Single.IsNaN(offsetPx) OrElse Single.IsInfinity(offsetPx) OrElse offsetPx <= 0.0F Then
                _model.Position = 0
                Return (_model.Position <> oldPos)
            End If

            Dim safeOffset As Double = Math.Min(CDbl(Integer.MaxValue), Math.Round(CDbl(offsetPx)))
            _model.Position = CInt(safeOffset)

            Return (_model.Position <> oldPos)
        End Function

        Public Sub Dispose() Implements IDisposable.Dispose
            If _disposed Then Return
            _disposed = True
        End Sub

    End Class

End Namespace