Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.General
Imports Nexamas.UI.Scroll.Theming
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Scroll

    Friend NotInheritable Class ScrollbarPainter
        Implements IDisposable

        Private ReadOnly _contract As MASResolvedComponentContract
        Private ReadOnly _scope As MASContractRenderScope
        Private ReadOnly _metrics As ScrollMetrics
        Private _ctx As MASThemeContext

        Private _disposed As Boolean

        Friend Sub New(metrics As ScrollMetrics,
               ctx As MASThemeContext,
               contract As MASResolvedComponentContract)

            If metrics Is Nothing Then Throw New ArgumentNullException(NameOf(metrics))
            If ctx Is Nothing Then Throw New ArgumentNullException(NameOf(ctx))
            If contract Is Nothing Then Throw New ArgumentNullException(NameOf(contract))

            _metrics = metrics
            _ctx = ctx

            _contract = contract

            _scope =
        MASArchitectureRuntime.CreateRenderScope(
            NameOf(ScrollbarPainter),
            _contract,
            MASRenderLayer.Surface,
            MASRenderLayer.Hover,
            MASRenderLayer.Pressed)
        End Sub
        Friend Sub UpdateThemeContext(ctx As MASThemeContext)
            If _disposed Then Return
            If ctx Is Nothing Then Return
            If Object.ReferenceEquals(_ctx, ctx) Then Return

            _ctx = ctx
        End Sub
        Friend Sub Paint(canvas As SKCanvas,
                         dpi As Single,
                         geo As ScrollbarLayout.Geometry,
                         state As ScrollbarController.State,
                         isHover As Boolean)

            If _disposed Then Return
            If canvas Is Nothing Then Return
            If geo.TrackRect.IsEmpty OrElse geo.ThumbRect.IsEmpty Then Return

            _scope.RequireAnyLayer(
    MASRenderLayer.Surface,
    MASRenderLayer.Hover,
    MASRenderLayer.Pressed)

            dpi = PixelSnap.SafeDpi(dpi)

            Dim t As IMASAppTheme = _ctx.Theme
            If t Is Nothing Then Return

            Dim st As IMASScrollTheme = MASAppThemeContracts.Families(t).Scroll
            If st Is Nothing Then Return

            Dim thickness As Single = geo.Thickness
            If thickness <= 0 Then Return

            Dim channelTop As SKColor = st.Channel.Top
            Dim channelBottom As SKColor = st.Channel.Bottom

            Dim featherEdge As SKColor = st.Channel.RimEdgeRgb.WithAlpha(st.Metrics.ChannelRimAlpha)
            Dim featherZero As SKColor = st.Channel.RimEdgeRgb.WithAlpha(0)

            Dim capTop As SKColor = st.Cap.Top
            Dim capBot As SKColor = st.Cap.Bottom

            Dim thumbTop As SKColor = st.Thumb.Top
            Dim thumbBottom As SKColor = st.Thumb.Bottom
            Dim thumbRim As SKColor = st.Thumb.RimRgb.WithAlpha(st.Metrics.ThumbRimAlpha)

            Dim thumbInterState As MASInteractionState =
                ResolveThumbState(state, isHover)

            Dim thumbOverlay As MASVisualInteractionOverlaySpec =
                MASInteractionResolver.Build(
                    contract:=_contract,
                    state:=thumbInterState,
                    theme:=t)

            ApplyThumbInteractionColors(
                overlay:=thumbOverlay,
                interactionTheme:=MASAppThemeContracts.Foundation(t).Interaction,
                state:=thumbInterState)

            Dim upState As MASInteractionState =
                PartState(ScrollbarController.Part.UpArrow, state, isHover)

            Dim dnState As MASInteractionState =
                PartState(ScrollbarController.Part.DownArrow, state, isHover)

            Dim upR As SKRect = PixelSnap.SnapRectHalf(geo.UpRect)
            Dim trR As SKRect = PixelSnap.SnapRectHalf(geo.TrackRect)
            Dim dnR As SKRect = PixelSnap.SnapRectHalf(geo.DownRect)
            Dim thR As SKRect = PixelSnap.SnapRectHalf(geo.ThumbRect)

            Dim channel As SKRect = Union3(upR, trR, dnR)
            If channel.IsEmpty Then Return

            Dim channelR As Single =
                PixelSnap.SafeRadius(
                    MASRadiusResolver.ResolveSurfacePx(
                        contract:=_contract,
                        rPx:=channel,
                        dpi:=dpi))

            Dim thumbR As Single =
                PixelSnap.SafeRadius(
                    MASRadiusResolver.ResolveInteractionPx(
                        contract:=_contract,
                        rPx:=thR,
                        dpi:=dpi))

            Dim hairW As Single =
                Math.Max(SurfaceTokens.FxRingMinStrokePx, thickness * 0.06F)

            Dim capExtend As Single =
                Math.Max(2.0F * dpi, thickness * 0.26F)

            Dim upCap As New SKRect(upR.Left, upR.Top, upR.Right, upR.Bottom + capExtend)
            Dim dnCap As New SKRect(dnR.Left, dnR.Top - capExtend, dnR.Right, dnR.Bottom)

            upCap = PixelSnap.SnapRectHalf(upCap)
            dnCap = PixelSnap.SnapRectHalf(dnCap)

            Using chPath As SKPath =
                BuildChannelPathWithConcaveJoins(channel, channelR, upCap.Bottom, dnCap.Top, 0.0F, 0.0F)

                Using shd As SKShader =
                    SKShader.CreateLinearGradient(
                        New SKPoint(channel.Left, channel.Top),
                        New SKPoint(channel.Left, channel.Bottom),
                        New SKColor() {channelTop, channelBottom},
                        New Single() {0.0F, 1.0F},
                        SKShaderTileMode.Clamp)

                    Using fillPaint As New SKPaint With {
                        .IsAntialias = True,
                        .Style = SKPaintStyle.Fill,
                        .Shader = shd
                    }
                        canvas.DrawPath(chPath, fillPaint)
                    End Using
                End Using

                DrawFeatheredRim(canvas, channel, chPath, dpi, thickness, featherEdge, featherZero)

                Dim saveCaps As Integer = canvas.Save()
                canvas.ClipPath(chPath, SKClipOperation.Intersect, True)

                DrawArcCapConcave(canvas, upCap, True, dpi, thickness, capTop, capBot)
                DrawArcCapConcave(canvas, dnCap, False, dpi, thickness, capTop, capBot)

                Dim glyphBase As SKColor = thumbRim.WithAlpha(185)

                DrawArrowGlyph(canvas, upR, True, dpi, upState, glyphBase)
                DrawArrowGlyph(canvas, dnR, False, dpi, dnState, glyphBase)

                canvas.RestoreToCount(saveCaps)
            End Using

            Using shd2 As SKShader =
                SKShader.CreateLinearGradient(
                    New SKPoint(thR.Left, thR.Top),
                    New SKPoint(thR.Left, thR.Bottom),
                    New SKColor() {thumbTop, thumbBottom},
                    New Single() {0.0F, 1.0F},
                    SKShaderTileMode.Clamp)

                Using thumbFill As New SKPaint With {
                    .IsAntialias = True,
                    .Style = SKPaintStyle.Fill,
                    .Shader = shd2
                }
                    canvas.DrawRoundRect(thR, thumbR, thumbR, thumbFill)
                End Using
            End Using

            If thumbOverlay.FillEnabled OrElse thumbOverlay.RingEnabled Then
                MASSurfaceInteractionRing.DrawOverlay(
                    canvas:=canvas,
                    baseRectPx:=thR,
                    baseRadiusPx:=thumbR,
                    spec:=thumbOverlay,
                    dpi:=dpi,
                    ringStrokeDipOverride:=hairW / dpi,
                    ringMinStrokePxOverride:=hairW)
            End If

            Using strokePaint As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Stroke,
                .Color = thumbRim,
                .StrokeWidth = hairW
            }
                canvas.DrawRoundRect(thR, thumbR, thumbR, strokePaint)
            End Using

        End Sub

        Private Shared Function ResolveThumbState(s As ScrollbarController.State,
                                                  isHover As Boolean) As MASInteractionState

            If s.Dragging OrElse s.DownPart = ScrollbarController.Part.Thumb Then
                Return MASInteractionState.Pressed
            End If

            If isHover AndAlso s.HoverPart = ScrollbarController.Part.Thumb Then
                Return MASInteractionState.Hovered
            End If

            Return MASInteractionState.None

        End Function

        Private Shared Function PartState(part As ScrollbarController.Part,
                                          s As ScrollbarController.State,
                                          isHover As Boolean) As MASInteractionState

            If s.DownPart = part Then Return MASInteractionState.Pressed
            If Not isHover Then Return MASInteractionState.None
            If s.HoverPart = part Then Return MASInteractionState.Hovered

            Return MASInteractionState.None

        End Function

        Private Shared Sub DrawArrowGlyph(canvas As SKCanvas,
                                          r As SKRect,
                                          isUp As Boolean,
                                          dpi As Single,
                                          st As MASInteractionState,
                                          baseInk As SKColor)

            If canvas Is Nothing Then Return
            If r.IsEmpty Then Return

            Dim pad As Single = Math.Max(2.4F * dpi, r.Width * 0.26F)
            Dim rr As SKRect = r
            rr.Inflate(-pad, -pad)

            If rr.IsEmpty Then Return

            Dim cx As Single = PixelSnap.SnapHalf(rr.MidX)
            Dim cy As Single = PixelSnap.SnapHalf(rr.MidY)
            Dim w As Single = rr.Width * 0.58F
            Dim h As Single = rr.Height * 0.42F

            Dim ink As SKColor = baseInk

            If st = MASInteractionState.Hovered Then
                ink = ColorMath.Mix(ink, SKColors.White, 0.18F).WithAlpha(235)
            ElseIf st = MASInteractionState.Pressed Then
                ink = ColorMath.Mix(ink, SKColors.Black, 0.08F).WithAlpha(245)
                cy += Math.Max(0.6F * dpi, 0.8F)
            End If

            Dim outline As SKColor =
                ColorMath.Mix(ink, SKColors.Black, 0.35F).WithAlpha(50)

            Dim sw As Single =
                Math.Max(SurfaceTokens.FxRingMinStrokePx, 1.25F * dpi)

            Using path As New SKPath()
                If isUp Then
                    path.MoveTo(cx, cy - h)
                    path.LineTo(cx - w, cy + h)
                    path.LineTo(cx + w, cy + h)
                Else
                    path.MoveTo(cx, cy + h)
                    path.LineTo(cx - w, cy - h)
                    path.LineTo(cx + w, cy - h)
                End If

                path.Close()

                Using pS As New SKPaint With {
                    .IsAntialias = True,
                    .Style = SKPaintStyle.Stroke,
                    .StrokeWidth = sw,
                    .StrokeJoin = SKStrokeJoin.Round,
                    .StrokeCap = SKStrokeCap.Round,
                    .Color = outline
                }
                    canvas.DrawPath(path, pS)
                End Using

                Using pF As New SKPaint With {
                    .IsAntialias = True,
                    .Style = SKPaintStyle.Fill,
                    .Color = ink
                }
                    canvas.DrawPath(path, pF)
                End Using
            End Using

        End Sub

        Private Shared Sub DrawFeatheredRim(canvas As SKCanvas,
                                            channel As SKRect,
                                            chPath As SKPath,
                                            dpi As Single,
                                            thickness As Single,
                                            edge As SKColor,
                                            zero As SKColor)

            Dim w As Single = Math.Max(1.5F * dpi, thickness * 0.12F)
            Dim pad As Single = Math.Max(0.75F * dpi, thickness * 0.05F)

            Dim save As Integer = canvas.Save()
            canvas.ClipPath(chPath, SKClipOperation.Intersect, True)

            Dim leftBand As New SKRect(channel.Left + pad, channel.Top + pad, channel.Left + pad + w, channel.Bottom - pad)

            Using pL As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill}
                Using shd As SKShader =
                    SKShader.CreateLinearGradient(
                        New SKPoint(leftBand.Left, leftBand.Top),
                        New SKPoint(leftBand.Right, leftBand.Top),
                        New SKColor() {edge, zero},
                        New Single() {0.0F, 1.0F},
                        SKShaderTileMode.Clamp)

                    pL.Shader = shd
                    canvas.DrawRect(leftBand, pL)
                End Using
            End Using

            Dim rightBand As New SKRect(channel.Right - pad - w, channel.Top + pad, channel.Right - pad, channel.Bottom - pad)

            Using pR As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill}
                Using shd As SKShader =
                    SKShader.CreateLinearGradient(
                        New SKPoint(rightBand.Left, rightBand.Top),
                        New SKPoint(rightBand.Right, rightBand.Top),
                        New SKColor() {zero, edge},
                        New Single() {0.0F, 1.0F},
                        SKShaderTileMode.Clamp)

                    pR.Shader = shd
                    canvas.DrawRect(rightBand, pR)
                End Using
            End Using

            Dim topBand As New SKRect(channel.Left + pad, channel.Top + pad, channel.Right - pad, channel.Top + pad + w)

            Using pT As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill}
                Using shd As SKShader =
                    SKShader.CreateLinearGradient(
                        New SKPoint(topBand.Left, topBand.Top),
                        New SKPoint(topBand.Left, topBand.Bottom),
                        New SKColor() {edge, zero},
                        New Single() {0.0F, 1.0F},
                        SKShaderTileMode.Clamp)

                    pT.Shader = shd
                    canvas.DrawRect(topBand, pT)
                End Using
            End Using

            Dim botBand As New SKRect(channel.Left + pad, channel.Bottom - pad - w, channel.Right - pad, channel.Bottom - pad)

            Using pB As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill}
                Using shd As SKShader =
                    SKShader.CreateLinearGradient(
                        New SKPoint(botBand.Left, botBand.Top),
                        New SKPoint(botBand.Left, botBand.Bottom),
                        New SKColor() {zero, edge},
                        New Single() {0.0F, 1.0F},
                        SKShaderTileMode.Clamp)

                    pB.Shader = shd
                    canvas.DrawRect(botBand, pB)
                End Using
            End Using

            canvas.RestoreToCount(save)

        End Sub

        Private Shared Sub DrawArcCapConcave(canvas As SKCanvas,
                                             capRect As SKRect,
                                             isTop As Boolean,
                                             dpi As Single,
                                             thickness As Single,
                                             topA As SKColor,
                                             botB As SKColor)

            If capRect.IsEmpty Then Return

            Dim arcDepth As Single = Math.Min(capRect.Height * 0.58F, thickness * 0.5F)
            arcDepth = Math.Max(arcDepth, 2.0F * dpi)

            Dim cx As Single = (capRect.Left + capRect.Right) * 0.5F

            Using path As New SKPath()
                If isTop Then
                    path.MoveTo(capRect.Left, capRect.Top)
                    path.LineTo(capRect.Right, capRect.Top)
                    path.LineTo(capRect.Right, capRect.Bottom)
                    path.QuadTo(cx, capRect.Bottom - arcDepth, capRect.Left, capRect.Bottom)
                Else
                    path.MoveTo(capRect.Left, capRect.Bottom)
                    path.LineTo(capRect.Right, capRect.Bottom)
                    path.LineTo(capRect.Right, capRect.Top)
                    path.QuadTo(cx, capRect.Top + arcDepth, capRect.Left, capRect.Top)
                End If

                path.Close()

                Using p As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill}
                    Using shd As SKShader =
                        SKShader.CreateLinearGradient(
                            New SKPoint(capRect.Left, capRect.Top),
                            New SKPoint(capRect.Left, capRect.Bottom),
                            New SKColor() {topA, botB},
                            New Single() {0.0F, 1.0F},
                            SKShaderTileMode.Clamp)

                        p.Shader = shd
                        canvas.DrawPath(path, p)
                    End Using
                End Using
            End Using

        End Sub

        Private Shared Function BuildChannelPathWithConcaveJoins(channel As SKRect,
                                                                 radius As Single,
                                                                 yJoinTop As Single,
                                                                 yJoinBot As Single,
                                                                 pinchDepth As Single,
                                                                 pinchSpan As Single) As SKPath

            Dim l As Single = channel.Left
            Dim r As Single = channel.Right
            Dim t As Single = channel.Top
            Dim b As Single = channel.Bottom
            Dim rr As Single = Math.Max(1.0F, radius)

            yJoinTop = Math.Max(t + rr, Math.Min(b - rr, yJoinTop))
            yJoinBot = Math.Max(t + rr, Math.Min(b - rr, yJoinBot))

            Dim s As Single = pinchSpan
            Dim d As Single = pinchDepth

            Dim p As New SKPath()

            p.MoveTo(l + rr, t)
            p.LineTo(r - rr, t)
            p.QuadTo(r, t, r, t + rr)

            p.LineTo(r, yJoinTop - s)
            p.QuadTo(r, yJoinTop, r - d, yJoinTop)
            p.QuadTo(r, yJoinTop, r, yJoinTop + s)

            p.LineTo(r, yJoinBot - s)
            p.QuadTo(r, yJoinBot, r - d, yJoinBot)
            p.QuadTo(r, yJoinBot, r, yJoinBot + s)

            p.LineTo(r, b - rr)
            p.QuadTo(r, b, r - rr, b)

            p.LineTo(l + rr, b)
            p.QuadTo(l, b, l, b - rr)

            p.LineTo(l, yJoinBot + s)
            p.QuadTo(l, yJoinBot, l + d, yJoinBot)
            p.QuadTo(l, yJoinBot, l, yJoinBot - s)

            p.LineTo(l, yJoinTop + s)
            p.QuadTo(l, yJoinTop, l + d, yJoinTop)
            p.QuadTo(l, yJoinTop, l, yJoinTop - s)

            p.LineTo(l, t + rr)
            p.QuadTo(l, t, l + rr, t)

            p.Close()
            Return p

        End Function

        Private Shared Function Union3(a As SKRect, b As SKRect, c As SKRect) As SKRect

            Dim left As Single = Math.Min(a.Left, Math.Min(b.Left, c.Left))
            Dim top As Single = Math.Min(a.Top, Math.Min(b.Top, c.Top))
            Dim right As Single = Math.Max(a.Right, Math.Max(b.Right, c.Right))
            Dim bottom As Single = Math.Max(a.Bottom, Math.Max(b.Bottom, c.Bottom))

            If right <= left OrElse bottom <= top Then
                Return SKRect.Empty
            End If

            Return PixelSnap.SnapRectHalf(New SKRect(left, top, right, bottom))

        End Function

        Private Shared Sub ApplyThumbInteractionColors(ByRef overlay As MASVisualInteractionOverlaySpec,
                                                       interactionTheme As IMASInteractionTheme,
                                                       state As MASInteractionState)

            If interactionTheme Is Nothing Then Return

            Dim interactionStateTheme As MASInteractionThemeState =
                ResolveScrollMASInteractionThemeState(interactionTheme, state)

            If overlay.FillEnabled AndAlso interactionStateTheme.Fill.Enabled Then
                overlay.Fill = interactionStateTheme.Fill.Color.WithAlpha(overlay.Fill.Alpha)
            End If

            If overlay.RingEnabled AndAlso interactionStateTheme.Ring.Enabled Then
                overlay.Ring = interactionStateTheme.Ring.Color.WithAlpha(overlay.Ring.Alpha)
            End If

        End Sub

        Private Shared Function ResolveScrollMASInteractionThemeState(interactionTheme As IMASInteractionTheme,
                                                                   state As MASInteractionState) As MASInteractionThemeState

            If interactionTheme Is Nothing Then
                Return MASInteractionThemeState.None
            End If

            Select Case state
                Case MASInteractionState.Hovered
                    Return interactionTheme.Hover

                Case MASInteractionState.Pressed
                    Return interactionTheme.Pressed

                Case MASInteractionState.Selected
                    Return interactionTheme.Selected

                Case MASInteractionState.Focused
                    Return interactionTheme.Focused

                Case Else
                    Return MASInteractionThemeState.None
            End Select

        End Function

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

    End Class

End Namespace