Option Strict On
Option Explicit On

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

Namespace Nexamas.UI.Rendering

    Partial Friend NotInheritable Class MASShadowPainter
        Friend Sub DrawShadow(canvas As SKCanvas,
                              r As SKRect,
                              contract As MASResolvedComponentContract,
                              dpi As Single,
                              surfaceTheme As IMASFoundationSurfaceTheme)

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

            contract.AssertConsumable()

            Dim shadowBase As SKColor = ResolveShadowBase(surfaceTheme)
            Dim spec As MASShadowSpec = MASShadowResolver.ResolveOuter(contract)

            DrawShadow(
                canvas:=canvas,
                r:=r,
                contract:=contract,
                dpi:=dpi,
                shadowBase:=shadowBase,
                spec:=spec)
        End Sub

        Friend Sub DrawShadow(canvas As SKCanvas,
                              r As SKRect,
                              contract As MASResolvedComponentContract,
                              dpi As Single,
                              shadowBase As SKColor,
                              spec As MASShadowSpec)

            If _disposed OrElse canvas Is Nothing Then Return
            If contract Is Nothing Then Throw New ArgumentNullException(NameOf(contract))
            If r.Width <= 1 OrElse r.Height <= 1 Then Return

            contract.AssertConsumable()

            dpi = PixelSnap.SafeDpi(dpi)

            Dim m As MASSurfaceInteractionRing.Metrics
            Dim baseRect As SKRect
            If Not MASVisualEffectGeometry.TryResolveBaseRect(r, dpi, m, baseRect) Then Return

            Dim radiusPx As Single =
                MASRadiusResolver.ResolveShadowPx(contract, baseRect, dpi)

            DrawShadow(
                canvas:=canvas,
                r:=baseRect,
                radiusPx:=radiusPx,
                dpi:=dpi,
                shadowBase:=shadowBase,
                spec:=spec)
        End Sub

        Friend Sub DrawLayeredShadow(canvas As SKCanvas,
                                     r As SKRect,
                                     contract As MASResolvedComponentContract,
                                     dpi As Single,
                                     surfaceTheme As IMASFoundationSurfaceTheme)

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

            contract.AssertConsumable()

            Dim shadowBase As SKColor = ResolveShadowBase(surfaceTheme)
            Dim spec As MASLayeredShadowSpec = MASShadowResolver.ResolveLayered(contract)

            DrawLayeredShadow(
                canvas:=canvas,
                r:=r,
                contract:=contract,
                dpi:=dpi,
                shadowBase:=shadowBase,
                spec:=spec)
        End Sub

        Friend Sub DrawLayeredShadow(canvas As SKCanvas,
                                     r As SKRect,
                                     contract As MASResolvedComponentContract,
                                     dpi As Single,
                                     shadowBase As SKColor,
                                     spec As MASLayeredShadowSpec)

            If _disposed OrElse canvas Is Nothing Then Return
            If contract Is Nothing Then Throw New ArgumentNullException(NameOf(contract))
            If r.Width <= 1 OrElse r.Height <= 1 Then Return

            contract.AssertConsumable()

            dpi = PixelSnap.SafeDpi(dpi)

            Dim m As MASSurfaceInteractionRing.Metrics
            Dim baseRect As SKRect
            If Not MASVisualEffectGeometry.TryResolveBaseRect(r, dpi, m, baseRect) Then Return

            Dim radiusPx As Single =
                MASRadiusResolver.ResolveShadowPx(contract, baseRect, dpi)

            DrawLayeredShadow(
                canvas:=canvas,
                r:=baseRect,
                radiusPx:=radiusPx,
                dpi:=dpi,
                shadowBase:=shadowBase,
                spec:=spec)
        End Sub

        Friend Sub DrawCardShadow(canvas As SKCanvas,
                                  r As SKRect,
                                  contract As MASResolvedComponentContract,
                                  dpi As Single,
                                  surfaceTheme As IMASFoundationSurfaceTheme,
                                  Optional secondary As Boolean = False)

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

            contract.AssertConsumable()

            Dim shadowBase As SKColor = ResolveShadowBase(surfaceTheme)
            Dim spec As MASCardShadowSpec =
                MASShadowResolver.ResolveCard(contract, secondary)

            DrawCardShadow(
                canvas:=canvas,
                r:=r,
                contract:=contract,
                dpi:=dpi,
                shadowBase:=shadowBase,
                spec:=spec)
        End Sub

        Friend Sub DrawCardShadow(canvas As SKCanvas,
                                  r As SKRect,
                                  contract As MASResolvedComponentContract,
                                  dpi As Single,
                                  shadowBase As SKColor,
                                  spec As MASCardShadowSpec)

            If _disposed OrElse canvas Is Nothing Then Return
            If contract Is Nothing Then Throw New ArgumentNullException(NameOf(contract))
            If r.Width <= 1 OrElse r.Height <= 1 Then Return

            contract.AssertConsumable()

            dpi = PixelSnap.SafeDpi(dpi)

            Dim m As MASSurfaceInteractionRing.Metrics
            Dim baseRect As SKRect
            If Not MASVisualEffectGeometry.TryResolveBaseRect(r, dpi, m, baseRect) Then Return

            Dim radiusPx As Single =
                MASRadiusResolver.ResolveShadowPx(contract, baseRect, dpi)

            DrawCardShadow(
                canvas:=canvas,
                r:=baseRect,
                radiusPx:=radiusPx,
                dpi:=dpi,
                shadowBase:=shadowBase,
                spec:=spec)
        End Sub

        Friend Sub DrawShadow(canvas As SKCanvas,
                              r As SKRect,
                              radiusPx As Single,
                              dpi As Single,
                              shadowBase As SKColor,
                              spec As MASShadowSpec)

            If _disposed OrElse canvas Is Nothing Then Return
            If r.Width <= 1 OrElse r.Height <= 1 Then Return

            dpi = PixelSnap.SafeDpi(dpi)
            radiusPx = PixelSnap.SafeRadius(radiusPx)

            If spec.Alpha = 0 OrElse spec.SigmaDip <= 0.001F Then Return

            Dim w As Integer = Math.Max(1, CInt(Math.Round(r.Width)))
            Dim h As Integer = Math.Max(1, CInt(Math.Round(r.Height)))
            Dim dpi100 As Integer = CInt(Math.Round(dpi * 100.0F))
            Dim rad100 As Integer = CInt(Math.Round(radiusPx * 100.0F))

            Dim sigma As Single = spec.SigmaDip * dpi
            Dim offY As Single = spec.OffsetYDip * dpi

            Dim sigma100 As Integer = CInt(Math.Round(sigma * 100.0F))
            Dim offY100 As Integer = CInt(Math.Round(offY * 100.0F))
            Dim alphaI As Integer = CInt(spec.Alpha)

            Dim baseCol As SKColor = CleanShadowColor(shadowBase, satMax:=0.14F)

            Dim need As Boolean =
                (_img Is Nothing) OrElse
                (w <> _lastW) OrElse
                (h <> _lastH) OrElse
                (dpi100 <> _lastDpi100) OrElse
                (rad100 <> _lastRadius100) OrElse
                (_lastBase <> baseCol) OrElse
                (_lastSigma100 <> sigma100) OrElse
                (_lastOffY100 <> offY100) OrElse
                (_lastAlpha <> alphaI)

            If need Then
                RebuildOuter(w, h, radiusPx, dpi, baseCol, spec)

                _lastW = w
                _lastH = h
                _lastDpi100 = dpi100
                _lastRadius100 = rad100
                _lastBase = baseCol
                _lastSigma100 = sigma100
                _lastOffY100 = offY100
                _lastAlpha = alphaI
            End If

            If _img IsNot Nothing Then
                canvas.DrawImage(_img, r.Left - _pad, r.Top - _pad + _offsetY)
            End If
        End Sub



        Friend Shared Function ComputeOuterShadowPadPx(dpi As Single,
                                                       spec As MASShadowSpec) As Single

            dpi = PixelSnap.SafeDpi(dpi)

            If spec.SigmaDip <= 0.001F OrElse spec.Alpha = 0 Then Return 0.0F

            Dim sigmaPx As Single = spec.SigmaDip * dpi
            Dim offYPx As Single = spec.OffsetYDip * dpi

            Dim pad As Single =
                Math.Max(
                    spec.PadMinPx,
                    CSng(Math.Ceiling(sigmaPx * spec.PadSigmaMul)) + 3.0F)

            pad += CSng(Math.Ceiling(Math.Max(0.0F, offYPx))) + 1.0F

            Return pad
        End Function

        Friend Shared Function ComputeOuterShadowPadPx(contract As MASResolvedComponentContract,
                                                       dpi As Single) As Single

            If contract Is Nothing Then Throw New ArgumentNullException(NameOf(contract))
            contract.AssertConsumable()

            Dim spec As MASShadowSpec = MASShadowResolver.ResolveOuter(contract)
            Return ComputeOuterShadowPadPx(dpi, spec)
        End Function

        Friend Sub DrawInnerHalo(canvas As SKCanvas,
                                 r As SKRect,
                                 radiusPx As Single,
                                 dpi As Single,
                                 shadowBase As SKColor,
                                 Optional alpha As Byte = 12,
                                 Optional sigmaDip As Single = 3.0F,
                                 Optional strokeDip As Single = 0.95F,
                                 Optional insetDip As Single = 1.15F)

            If _disposed OrElse canvas Is Nothing Then Return
            If r.Width <= 2 OrElse r.Height <= 2 Then Return

            dpi = PixelSnap.SafeDpi(dpi)
            radiusPx = PixelSnap.SafeRadius(radiusPx)

            Dim baseCol As SKColor = CleanShadowColor(shadowBase, satMax:=0.12F)

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

            Dim inset As Single =
                Math.Max(
                    SurfaceTokens.FxSnapStepPx,
                    insetDip * dpi)

            Dim rr As SKRect =
                PixelSnap.SnapRectHalf(PixelSnap.InsetAll(r, inset))

            If rr.Width <= 2 OrElse rr.Height <= 2 Then Return

            Dim rad As Single =
                CornerRadii.ResolveInsetRadiusPx(radiusPx, inset)

            _haloPaint.StrokeWidth = sw
            _haloPaint.BlendMode = SKBlendMode.SrcOver
            _haloPaint.Color = New SKColor(baseCol.Red, baseCol.Green, baseCol.Blue, alpha)
            _haloPaint.MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, sigmaDip * dpi)

            canvas.DrawRoundRect(rr, rad, rad, _haloPaint)

            _haloPaint.MaskFilter = Nothing
            _haloPaint.BlendMode = SKBlendMode.SrcOver
        End Sub

        Friend Sub DrawInnerHalo(canvas As SKCanvas,
                                 r As SKRect,
                                 contract As MASResolvedComponentContract,
                                 dpi As Single,
                                 surfaceTheme As IMASFoundationSurfaceTheme,
                                 Optional alpha As Byte = 12,
                                 Optional sigmaDip As Single = 3.0F,
                                 Optional strokeDip As Single = 0.95F,
                                 Optional insetDip As Single = 1.15F)

            If _disposed OrElse canvas Is Nothing Then Return
            If contract Is Nothing Then Throw New ArgumentNullException(NameOf(contract))
            If r.Width <= 2 OrElse r.Height <= 2 Then Return

            contract.AssertConsumable()

            dpi = PixelSnap.SafeDpi(dpi)

            Dim m As MASSurfaceInteractionRing.Metrics
            Dim baseRect As SKRect
            If Not MASVisualEffectGeometry.TryResolveBaseRect(r, dpi, m, baseRect) Then Return

            Dim radiusPx As Single =
                MASRadiusResolver.ResolveShadowPx(contract, baseRect, dpi)

            Dim shadowBase As SKColor = ResolveShadowBase(surfaceTheme)

            DrawInnerHalo(
                canvas:=canvas,
                r:=baseRect,
                radiusPx:=radiusPx,
                dpi:=dpi,
                shadowBase:=shadowBase,
                alpha:=alpha,
                sigmaDip:=sigmaDip,
                strokeDip:=strokeDip,
                insetDip:=insetDip)
        End Sub

        Friend Shared Function LayeredSpec_FromThemeIdle(t As IMASLayeredShadowTheme,
                                                         Optional padMinPx As Integer = 10,
                                                         Optional padSigmaMul As Single = 3.1F) As MASLayeredShadowSpec

            If t Is Nothing Then
                Return New MASLayeredShadowSpec With {
                    .SigmaA_Dip = 0.0F,
                    .AlphaA = 0,
                    .OffY_A_Dip = 0.0F,
                    .SigmaB_Dip = 0.0F,
                    .AlphaB = 0,
                    .OffY_B_Dip = 0.0F,
                    .PadMinPx = padMinPx,
                    .PadSigmaMul = padSigmaMul
                }
            End If

            Dim aSig As Single = t.SigmaDipIdle
            Dim aOff As Single = t.OffsetDipIdle
            Dim aAl As Byte = t.AlphaIdle

            Dim bSig As Single = Math.Max(1.0F, aSig * 0.42F)
            Dim bOff As Single = Math.Max(0.0F, aOff * 0.38F)

            Dim bAlI As Integer = CInt(Math.Round(aAl * 0.78F))
            If bAlI > 255 Then bAlI = 255

            Return New MASLayeredShadowSpec With {
                .SigmaA_Dip = aSig,
                .AlphaA = aAl,
                .OffY_A_Dip = aOff,
                .SigmaB_Dip = bSig,
                .AlphaB = CByte(bAlI),
                .OffY_B_Dip = bOff,
                .PadMinPx = padMinPx,
                .PadSigmaMul = padSigmaMul
            }
        End Function

        Friend Sub DrawLayeredShadow(canvas As SKCanvas,
                                     r As SKRect,
                                     radiusPx As Single,
                                     dpi As Single,
                                     shadowBase As SKColor,
                                     spec As MASLayeredShadowSpec)

            If _disposed OrElse canvas Is Nothing Then Return
            If r.Width <= 1 OrElse r.Height <= 1 Then Return

            dpi = PixelSnap.SafeDpi(dpi)
            radiusPx = PixelSnap.SafeRadius(radiusPx)

            If spec.AlphaA = 0 AndAlso spec.AlphaB = 0 Then Return
            If spec.SigmaA_Dip <= 0.001F AndAlso spec.SigmaB_Dip <= 0.001F Then Return

            shadowBase = CleanShadowColor(shadowBase, satMax:=0.13F)

            EnsureLayeredCache(r, radiusPx, dpi, shadowBase, spec)

            If _layerImg IsNot Nothing Then
                canvas.DrawImage(_layerImg, r.Left - _layerPad, r.Top - _layerPad + _layerOffsetY)
            End If
        End Sub





        Friend Shared Function ComputeLayeredShadowPadPx(dpi As Single,
                                                         spec As MASLayeredShadowSpec) As Single

            dpi = PixelSnap.SafeDpi(dpi)

            If spec.AlphaA = 0 AndAlso spec.AlphaB = 0 Then Return 0.0F
            If spec.SigmaA_Dip <= 0.001F AndAlso spec.SigmaB_Dip <= 0.001F Then Return 0.0F

            Dim sigA As Single = spec.SigmaA_Dip * dpi
            Dim sigB As Single = spec.SigmaB_Dip * dpi
            Dim offA As Single = spec.OffY_A_Dip * dpi
            Dim offB As Single = spec.OffY_B_Dip * dpi

            Dim maxSig As Single = Math.Max(sigA, sigB)
            Dim maxOff As Single = Math.Max(offA, offB)

            Dim pad As Single =
                Math.Max(
                    spec.PadMinPx,
                    CSng(Math.Ceiling(maxSig * spec.PadSigmaMul)) + 3.0F)

            pad += CSng(Math.Ceiling(Math.Max(0.0F, maxOff))) + 1.0F

            Return pad
        End Function

        Friend Shared Function ComputeLayeredShadowPadPx(contract As MASResolvedComponentContract,
                                                         dpi As Single) As Single

            If contract Is Nothing Then Throw New ArgumentNullException(NameOf(contract))
            contract.AssertConsumable()

            Dim spec As MASLayeredShadowSpec = MASShadowResolver.ResolveLayered(contract)
            Return ComputeLayeredShadowPadPx(dpi, spec)
        End Function

        Friend Sub DrawCardShadow(canvas As SKCanvas,
                                  r As SKRect,
                                  radiusPx As Single,
                                  dpi As Single,
                                  shadowBase As SKColor,
                                  spec As MASCardShadowSpec)

            If _disposed OrElse canvas Is Nothing Then Return
            If r.Width <= 1 OrElse r.Height <= 1 Then Return

            dpi = PixelSnap.SafeDpi(dpi)
            radiusPx = PixelSnap.SafeRadius(radiusPx)

            If spec.AlphaA = 0 AndAlso spec.AlphaB = 0 Then Return
            If spec.SigmaA_Dip <= 0.001F AndAlso spec.SigmaB_Dip <= 0.001F Then Return

            shadowBase = CleanShadowColor(shadowBase, satMax:=0.12F)

            EnsureCardCache(r, radiusPx, dpi, shadowBase, spec)

            If _cardImg IsNot Nothing Then
                canvas.DrawImage(_cardImg, r.Left - _cardPad, r.Top - _cardPad + _cardOffsetY)
            End If
        End Sub





        Friend Shared Function ComputeCardShadowPadPx(dpi As Single,
                                                      spec As MASCardShadowSpec) As Single

            dpi = PixelSnap.SafeDpi(dpi)

            If spec.AlphaA = 0 AndAlso spec.AlphaB = 0 Then Return 0.0F
            If spec.SigmaA_Dip <= 0.001F AndAlso spec.SigmaB_Dip <= 0.001F Then Return 0.0F

            Dim sigA As Single = spec.SigmaA_Dip * dpi
            Dim sigB As Single = spec.SigmaB_Dip * dpi
            Dim offA As Single = spec.OffY_A_Dip * dpi
            Dim offB As Single = spec.OffY_B_Dip * dpi

            Dim maxSig As Single = Math.Max(sigA, sigB)
            Dim maxOff As Single = Math.Max(offA, offB)

            Dim pad As Single =
                Math.Max(
                    spec.PadMinPx,
                    CSng(Math.Ceiling(maxSig * spec.PadSigmaMul)) + 3.0F)

            pad += CSng(Math.Ceiling(Math.Max(0.0F, maxOff))) + 1.0F

            Return pad
        End Function

        Friend Shared Function ComputeCardShadowPadPx(contract As MASResolvedComponentContract,
                                                      dpi As Single,
                                                      Optional secondary As Boolean = False) As Single

            If contract Is Nothing Then Throw New ArgumentNullException(NameOf(contract))
            contract.AssertConsumable()

            Dim spec As MASCardShadowSpec = MASShadowResolver.ResolveCard(contract, secondary)
            Return ComputeCardShadowPadPx(dpi, spec)
        End Function

    End Class

End Namespace
