Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.General

    Partial Friend NotInheritable Class MASSurfaceInteractionRing

#Region "Overlay rendering"

        Friend Shared Sub DrawOverlay(canvas As SKCanvas,
                                      baseRectPx As SKRect,
                                      contract As MASResolvedComponentContract,
                                      spec As MASVisualInteractionOverlaySpec,
                                      dpi As Single,
                                      Optional fillInsetDipOverride As Single = -1.0F,
                                      Optional ringInsetDipOverride As Single = -1.0F,
                                      Optional ringStrokeDipOverride As Single = -1.0F,
                                      Optional ringMinStrokePxOverride As Single = -1.0F)

            If contract Is Nothing Then
                Throw New ArgumentNullException(NameOf(contract))
            End If

            contract.AssertConsumable()

            Dim baseRadiusPx As Single =
                MASRadiusResolver.ResolveInteractionPx(
                    contract:=contract,
                    rPx:=baseRectPx,
                    dpi:=dpi)

            DrawOverlay(
                canvas:=canvas,
                baseRectPx:=baseRectPx,
                baseRadiusPx:=baseRadiusPx,
                spec:=spec,
                dpi:=dpi,
                fillInsetDipOverride:=fillInsetDipOverride,
                ringInsetDipOverride:=ringInsetDipOverride,
                ringStrokeDipOverride:=ringStrokeDipOverride,
                ringMinStrokePxOverride:=ringMinStrokePxOverride)

        End Sub

        Friend Shared Sub DrawOverlay(canvas As SKCanvas,
                                      baseRectPx As SKRect,
                                      baseRadiusPx As Single,
                                      spec As MASVisualInteractionOverlaySpec,
                                      dpi As Single,
                                      Optional fillInsetDipOverride As Single = -1.0F,
                                      Optional ringInsetDipOverride As Single = -1.0F,
                                      Optional ringStrokeDipOverride As Single = -1.0F,
                                      Optional ringMinStrokePxOverride As Single = -1.0F)

            If canvas Is Nothing Then Return
            If baseRectPx.Width <= 1.0F OrElse baseRectPx.Height <= 1.0F Then Return
            If Not spec.FillEnabled AndAlso Not spec.RingEnabled Then Return

            dpi = PixelSnap.SafeDpi(dpi)

            If Not PixelSnap.HasArea(baseRectPx) Then Return

            Dim m As Metrics =
                GetMetrics(
                    dpi:=dpi,
                    fillInsetDipOverride:=fillInsetDipOverride,
                    ringInsetDipOverride:=ringInsetDipOverride,
                    ringStrokeDipOverride:=ringStrokeDipOverride,
                    ringMinStrokePxOverride:=ringMinStrokePxOverride)

            Dim rBase As SKRect = baseRectPx

            Dim radBase As Single = PixelSnap.SafeRadius(baseRadiusPx)
            Dim maxRad As Single = Math.Max(0.0F, Math.Min(rBase.Width, rBase.Height) / 2.0F)

            If radBase > maxRad Then
                radBase = maxRad
            End If

            If spec.FillEnabled Then
                Dim rf As SKRect = FillRect(rBase, m)

                If spec.ExtraFillInsetPx > 0.0F Then
                    rf = PixelSnap.SnapRect(
                        PixelSnap.InsetAll(rf, spec.ExtraFillInsetPx),
                        m.SnapStepPx)
                End If

                If PixelSnap.HasArea(rf) Then
                    Dim totalFillInset As Single =
                        m.FillInsetPx + Math.Max(0.0F, spec.ExtraFillInsetPx)

                    Dim radF As Single =
                        CornerRadii.ResolveInsetRadiusPx(radBase, totalFillInset)

                    If IsPathOnlyShape(spec.Shape) Then
                        Using p As SKPath = BuildOpenShapeFillPath(rf, radF, spec.Shape)
                            Dim fillShader As SKShader = Nothing

                            Try
                                If spec.Shape = MASVisualInteractionOverlayShape.OpenSides Then
                                    fillShader = CreateOpenSidesFillShader(rf, spec.Fill, dpi)
                                End If

                                _pathFill.Shader = fillShader
                                _pathFill.Color = If(fillShader Is Nothing, spec.Fill, SKColors.White)
                                _pathFill.Style = SKPaintStyle.Fill
                                _pathFill.BlendMode = spec.FillBlendMode

                                canvas.DrawPath(p, _pathFill)

                            Finally
                                _pathFill.Shader = Nothing

                                If fillShader IsNot Nothing Then
                                    fillShader.Dispose()
                                End If
                            End Try
                        End Using
                    Else
                        _fill.Shader = Nothing
                        _fill.Color = spec.Fill
                        _fill.Style = SKPaintStyle.Fill
                        _fill.BlendMode = spec.FillBlendMode

                        Dim rrFill As SKRoundRect =
                            BuildOverlayRoundRect(rf, radF, spec.Shape)

                        canvas.DrawRoundRect(rrFill, _fill)
                    End If
                End If
            End If

            If spec.RingEnabled Then
                Dim rr As SKRect = RingRect(rBase, m)

                If spec.ExtraRingInsetPx > 0.0F Then
                    rr = PixelSnap.SnapRect(
                        PixelSnap.InsetAll(rr, spec.ExtraRingInsetPx),
                        m.SnapStepPx)
                End If

                If PixelSnap.HasArea(rr) Then
                    Dim totalRingInset As Single =
                        m.RingPathInsetPx + Math.Max(0.0F, spec.ExtraRingInsetPx)

                    Dim radR As Single =
                        CornerRadii.ResolveInsetRadiusPx(radBase, totalRingInset)

                    If IsPathOnlyShape(spec.Shape) Then
                        Using p As SKPath = BuildOpenShapeRingPath(rr, radR, spec.Shape)
                            _pathRing.Shader = Nothing
                            _pathRing.Style = SKPaintStyle.Stroke
                            _pathRing.StrokeWidth = m.RingStrokePx
                            _pathRing.Color = spec.Ring
                            _pathRing.BlendMode = SKBlendMode.SrcOver

                            canvas.DrawPath(p, _pathRing)
                        End Using
                    Else
                        _ring.Shader = Nothing
                        _ring.Style = SKPaintStyle.Stroke
                        _ring.StrokeWidth = m.RingStrokePx
                        _ring.Color = spec.Ring
                        _ring.BlendMode = SKBlendMode.SrcOver

                        Dim rrRing As SKRoundRect =
                            BuildOverlayRoundRect(rr, radR, spec.Shape)

                        canvas.DrawRoundRect(rrRing, _ring)
                    End If
                End If
            End If

        End Sub

#End Region

#Region "Idle rim"

        Friend Shared Sub DrawIdleRim(canvas As SKCanvas,
                                      baseR As SKRect,
                                      contract As MASResolvedComponentContract,
                                      rimColor As SKColor,
                                      rimAlpha As Byte,
                                      strokePx As Single,
                                      dpi As Single,
                                      Optional edgeInsetPx As Single = 0.0F,
                                      Optional shape As MASVisualInteractionOverlayShape = MASVisualInteractionOverlayShape.Closed)

            If contract Is Nothing Then
                Throw New ArgumentNullException(NameOf(contract))
            End If

            contract.AssertConsumable()

            Dim baseRadiusPx As Single =
                MASRadiusResolver.ResolveInteractionPx(
                    contract:=contract,
                    rPx:=baseR,
                    dpi:=dpi)

            DrawIdleRim(
                canvas:=canvas,
                baseR:=baseR,
                baseRadiusPx:=baseRadiusPx,
                rimColor:=rimColor,
                rimAlpha:=rimAlpha,
                strokePx:=strokePx,
                dpi:=dpi,
                edgeInsetPx:=edgeInsetPx,
                shape:=shape)

        End Sub

        Friend Shared Sub DrawIdleRim(canvas As SKCanvas,
                                      baseR As SKRect,
                                      baseRadiusPx As Single,
                                      rimColor As SKColor,
                                      rimAlpha As Byte,
                                      strokePx As Single,
                                      dpi As Single,
                                      Optional edgeInsetPx As Single = 0.0F,
                                      Optional shape As MASVisualInteractionOverlayShape = MASVisualInteractionOverlayShape.Closed)

            If canvas Is Nothing Then Return
            If rimAlpha = 0 OrElse rimColor.Alpha = 0 Then Return
            If baseR.Width <= 1.0F OrElse baseR.Height <= 1.0F Then Return

            dpi = PixelSnap.SafeDpi(dpi)

            Dim sw As Single = strokePx

            If Single.IsNaN(sw) OrElse
               Single.IsInfinity(sw) OrElse
               sw <= 0.0F Then

                Throw New InvalidOperationException("Idle rim stroke must be a valid positive value.")
            End If

            sw = Math.Max(SurfaceTokens.FxRingMinStrokePx, sw)

            If Single.IsNaN(edgeInsetPx) OrElse
               Single.IsInfinity(edgeInsetPx) OrElse
               edgeInsetPx < 0.0F Then

                Throw New InvalidOperationException("Idle rim edge inset is invalid.")
            End If

            Dim insetPath As Single = PixelSnap.PathInset(edgeInsetPx, sw)

            Dim rr As SKRect = baseR
            rr.Inflate(-insetPath, -insetPath)

            If Not PixelSnap.HasArea(rr) Then Return

            Dim rad As Single =
                CornerRadii.ResolveInsetRadiusPx(
                    PixelSnap.SafeRadius(baseRadiusPx),
                    insetPath)

            If IsPathOnlyShape(shape) Then
                Using p As SKPath = BuildOpenShapeRingPath(rr, rad, shape)
                    _pathRing.Shader = Nothing
                    _pathRing.Style = SKPaintStyle.Stroke
                    _pathRing.StrokeWidth = sw
                    _pathRing.Color = ColorMath.WithAlpha(rimColor, rimAlpha)
                    _pathRing.BlendMode = SKBlendMode.SrcOver

                    canvas.DrawPath(p, _pathRing)
                End Using
            Else
                _ring.Shader = Nothing
                _ring.Style = SKPaintStyle.Stroke
                _ring.StrokeWidth = sw
                _ring.Color = ColorMath.WithAlpha(rimColor, rimAlpha)
                _ring.BlendMode = SKBlendMode.SrcOver

                Dim rrShape As SKRoundRect =
                    BuildOverlayRoundRect(rr, rad, shape)

                canvas.DrawRoundRect(rrShape, _ring)
            End If

        End Sub

#End Region

#Region "Prebuilt round-rect rendering"

        Friend Shared Sub DrawOverlayRoundRects(canvas As SKCanvas,
                                                rrFill As SKRoundRect,
                                                rrRing As SKRoundRect,
                                                spec As MASVisualInteractionOverlaySpec,
                                                ringStrokePx As Single)

            If canvas Is Nothing Then Return
            If Not spec.FillEnabled AndAlso Not spec.RingEnabled Then Return

            If spec.FillEnabled AndAlso Not rrFill.Rect.IsEmpty Then
                _fill.Shader = Nothing
                _fill.BlendMode = spec.FillBlendMode
                _fill.Style = SKPaintStyle.Fill
                _fill.Color = spec.Fill

                canvas.DrawRoundRect(rrFill, _fill)
            End If

            If spec.RingEnabled AndAlso Not rrRing.Rect.IsEmpty Then
                Dim sw As Single = ringStrokePx

                If Single.IsNaN(sw) OrElse
                   Single.IsInfinity(sw) OrElse
                   sw <= 0.0F Then

                    Throw New InvalidOperationException("Round-rect ring stroke must be a valid positive value.")
                End If

                sw = Math.Max(SurfaceTokens.FxRingMinStrokePx, sw)

                _ring.Shader = Nothing
                _ring.BlendMode = SKBlendMode.SrcOver
                _ring.Style = SKPaintStyle.Stroke
                _ring.StrokeJoin = SKStrokeJoin.Round
                _ring.StrokeCap = SKStrokeCap.Round
                _ring.StrokeWidth = sw
                _ring.Color = spec.Ring

                canvas.DrawRoundRect(rrRing, _ring)
            End If

        End Sub

#End Region

#Region "Segmented overlay"

        Friend Shared Sub DrawSegmentOverlay(canvas As SKCanvas,
                                             baseRectPx As SKRect,
                                             baseRadiusPx As Single,
                                             m As Metrics,
                                             spec As MASVisualInteractionOverlaySpec,
                                             segmentIndex As Integer,
                                             segmentCount As Integer)

            If canvas Is Nothing Then Return
            If Not spec.FillEnabled AndAlso Not spec.RingEnabled Then Return
            If baseRectPx.Width <= 1.0F OrElse baseRectPx.Height <= 1.0F Then Return
            If segmentCount <= 0 Then Return

            Dim effectiveShape As MASVisualInteractionOverlayShape

            If segmentCount = 1 Then
                effectiveShape = MASVisualInteractionOverlayShape.Closed
            ElseIf segmentIndex = 0 Then
                effectiveShape = MASVisualInteractionOverlayShape.SegmentLeft
            ElseIf segmentIndex = segmentCount - 1 Then
                effectiveShape = MASVisualInteractionOverlayShape.SegmentRight
            Else
                effectiveShape = MASVisualInteractionOverlayShape.SegmentMiddle
            End If

            Dim localSpec As MASVisualInteractionOverlaySpec = spec
            localSpec.Shape = effectiveShape

            Dim rrFill As New SKRoundRect()

            If localSpec.FillEnabled Then
                Dim fillR As SKRect = FillRect(baseRectPx, m)

                If PixelSnap.HasArea(fillR, 1.0F) Then
                    Dim radFill As Single =
                        CornerRadii.ResolveInsetRadiusPx(
                            baseRadiusPx,
                            m.FillInsetPx)

                    rrFill = BuildOverlayRoundRect(fillR, radFill, effectiveShape)
                End If
            End If

            Dim rrRing As New SKRoundRect()

            If localSpec.RingEnabled Then
                Dim ringR As SKRect = RingRect(baseRectPx, m)

                If PixelSnap.HasArea(ringR, 1.0F) Then
                    Dim radRing As Single =
                        CornerRadii.ResolveInsetRadiusPx(
                            baseRadiusPx,
                            m.RingPathInsetPx)

                    rrRing = BuildOverlayRoundRect(ringR, radRing, effectiveShape)
                End If
            End If

            DrawOverlayRoundRects(
                canvas:=canvas,
                rrFill:=rrFill,
                rrRing:=rrRing,
                spec:=localSpec,
                ringStrokePx:=m.RingStrokePx)

        End Sub

#End Region

    End Class

End Namespace
