' SSOT-V2 Signature: This class/file follows the SSOT-V2 architecture.
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 MASAcrylicEffectPainter
        Implements IDisposable

#Region "Fields"

        Private _disposed As Boolean

        Private ReadOnly _hairline As New SKPaint With {
            .IsAntialias = True,
            .IsDither = True,
            .Style = SKPaintStyle.Stroke
        }

        Private ReadOnly _fill As New SKPaint With {
            .IsAntialias = True,
            .IsDither = False,
            .Style = SKPaintStyle.Fill
        }

        Private ReadOnly _shine As New SKPaint With {
            .IsAntialias = True,
            .IsDither = False,
            .Style = SKPaintStyle.Fill
        }

        Private ReadOnly _strokeOuter As New SKPaint With {
            .IsAntialias = True,
            .IsDither = False,
            .Style = SKPaintStyle.Stroke
        }

        Private ReadOnly _strokeInner As New SKPaint With {
            .IsAntialias = True,
            .IsDither = False,
            .Style = SKPaintStyle.Stroke
        }

        Private ReadOnly _glow As New SKPaint With {
            .IsAntialias = True,
            .IsDither = True,
            .Style = SKPaintStyle.Fill
        }

        Private _lastW As Integer = -1
        Private _lastH As Integer = -1
        Private _lastDpi100 As Integer = -1

        Private _lastTop As SKColor = Nothing
        Private _lastMid As SKColor = Nothing
        Private _lastBot As SKColor = Nothing
        Private _lastWash As SKColor = Nothing

        Private _bodyShader As SKShader = Nothing
        Private _shineShader As SKShader = Nothing

        Private _lastFam As CommonEnums.SurfaceFamily = CType(-1, CommonEnums.SurfaceFamily)

        Private _grainBmp As SKBitmap
        Private _grainShader As SKShader
        Private _grainLastDpi100 As Integer = -1
        Private _grainLastTilePx100 As Integer = -1

#End Region

#Region "Dispose"

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

            SafeDisposePaint(_fill)
            SafeDisposePaint(_shine)
            SafeDisposePaint(_strokeOuter)
            SafeDisposePaint(_strokeInner)
            SafeDisposePaint(_glow)
            SafeDisposePaint(_hairline)

            MASVisualPrimitivesPainter.DisposeShader(_bodyShader)
            MASVisualPrimitivesPainter.DisposeShader(_shineShader)
            MASVisualPrimitivesPainter.DisposeShader(_grainShader)
            SafeDisposeBitmap(_grainBmp)
        End Sub

#End Region

#Region "Contract entry points"

        Friend Sub DrawCoolAcrylicBody(canvas As SKCanvas,
                                                    ctx As MASThemeContext,
                                                    r As SKRect,
                                                    dpi As Single,
                                                    contract As MASResolvedComponentContract)

            DrawCoolAcrylicCore_NoShadow(
                canvas:=canvas,
                ctx:=ctx,
                r:=r,
                dpi:=dpi,
                contract:=contract,
                drawRims:=True)

        End Sub

        Friend Sub DrawCoolAcrylicBody_NoShadow(canvas As SKCanvas,
                                                ctx As MASThemeContext,
                                                r As SKRect,
                                                dpi As Single,
                                                contract As MASResolvedComponentContract)

            DrawCoolAcrylicCore_NoShadow(
                canvas:=canvas,
                ctx:=ctx,
                r:=r,
                dpi:=dpi,
                contract:=contract,
                drawRims:=False)

        End Sub

#End Region

#Region "Low-level acrylic entry points"

        Friend Sub DrawCoolAcrylicBody(canvas As SKCanvas,
                                                    baseRect As SKRect,
                                                    radius As Single,
                                                    dpi As Single,
                                                    fam As CommonEnums.SurfaceFamily,
                                                    topCol As SKColor,
                                                    midCol As SKColor,
                                                    botCol As SKColor,
                                                    wash As SKColor,
                                                    borderOuter As SKColor,
                                                    borderInner As SKColor)

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

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

            Dim intensity As MASVisualEffectIntensityProfiles.AcrylicSurfaceIntensityProfile =
                MASVisualEffectIntensityProfiles.ResolveAcrylic(fam)

            EnsureCoolAcrylicShaders(baseRect, dpi, fam, topCol, midCol, botCol, wash)

            MASVisualPrimitivesPainter.WithTranslatedLocalRect(canvas, baseRect,
                Sub(lr As SKRect)

                    _fill.Shader = _bodyShader
                    _fill.BlendMode = SKBlendMode.SrcOver
                    canvas.DrawRoundRect(lr, radius, radius, _fill)
                    _fill.Shader = Nothing

                    If intensity.UseBodyGlow AndAlso intensity.BodyGlowStrength > 0.0F Then
                        DrawCoolAcrylicBodyGlow(canvas, lr, radius, intensity)
                    End If

                    If intensity.UseTopSheen AndAlso intensity.SheenStrength > 0.0F Then
                        DrawCoolAcrylicTopSheen(canvas, lr, radius, dpi, intensity)
                    End If

                    DrawCoolAcrylicTopEdgeMicroHighlight(canvas, lr, radius, dpi, midCol:=midCol, intensity:=intensity)
                    DrawCoolAcrylicInnerBottomVignette(canvas, lr, radius, botCol:=botCol, intensity:=intensity)

                    DrawCoolAcrylicBorderHierarchy(
                        canvas:=canvas,
                        baseRect:=lr,
                        radius:=radius,
                        dpi:=dpi,
                        borderOuter:=borderOuter,
                        borderInner:=borderInner,
                        outerStrength:=intensity.OuterBorderStrength,
                        innerStrength:=intensity.InnerBorderStrength)

                End Sub)

        End Sub

        Friend Sub DrawCoolAcrylicBody_NoShadow(canvas As SKCanvas,
                                                baseRect As SKRect,
                                                radius As Single,
                                                dpi As Single,
                                                fam As CommonEnums.SurfaceFamily,
                                                topCol As SKColor,
                                                midCol As SKColor,
                                                botCol As SKColor,
                                                wash As SKColor)

            DrawCoolAcrylicBody(
                canvas:=canvas,
                baseRect:=baseRect,
                radius:=radius,
                dpi:=dpi,
                fam:=fam,
                topCol:=topCol,
                midCol:=midCol,
                botCol:=botCol,
                wash:=wash,
                borderOuter:=SKColors.Transparent,
                borderInner:=SKColors.Transparent)

        End Sub

#End Region

#Region "Contract acrylic core"

        Private Sub DrawCoolAcrylicCore_NoShadow(canvas As SKCanvas,
                                                 ctx As MASThemeContext,
                                                 r As SKRect,
                                                 dpi As Single,
                                                 contract As MASResolvedComponentContract,
                                                 drawRims As Boolean)

            If _disposed Then Return
            If canvas Is Nothing OrElse ctx 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.ResolveSurfacePx(contract, baseRect, dpi)

            radiusPx = PixelSnap.SafeRadius(radiusPx)

            Dim pal As MASAcrylicEffectPaletteResolver.AcrylicPalette
            If Not MASAcrylicEffectPaletteResolver.TryResolveThemePalette(ctx, contract, pal) Then Return

            Dim fam As CommonEnums.SurfaceFamily = contract.SurfaceFamily

            Dim tuning As MASAcrylicEffectTuningProfiles.AcrylicTuning =
                ResolveCoolAcrylicTuning(fam)

            Dim fx As MASAcrylicEffectTuningProfiles.FensterFxTuning =
                ResolveCoolAcrylicFxTuning(fam)

            Dim topCol As SKColor = pal.Top
            Dim midCol As SKColor = pal.Mid
            Dim botCol As SKColor = pal.Bot
            Dim wash As SKColor = ResolveCoolAcrylicWash(fam, pal.Wash)
            Dim borderOuter As SKColor = pal.BorderOuter
            Dim borderInner As SKColor = pal.BorderInner

            Dim intensity As MASVisualEffectIntensityProfiles.AcrylicSurfaceIntensityProfile =
                MASVisualEffectIntensityProfiles.ResolveAcrylic(fam)

            DrawCoolAcrylicBody(
                canvas:=canvas,
                baseRect:=baseRect,
                radius:=radiusPx,
                dpi:=dpi,
                fam:=fam,
                topCol:=topCol,
                midCol:=midCol,
                botCol:=botCol,
                wash:=wash,
                borderOuter:=borderOuter,
                borderInner:=If(drawRims, borderInner, SKColors.Transparent))

            If fam = CommonEnums.SurfaceFamily.Menu Then
                DrawCoolAcrylicTopEdgeHighlight(canvas, baseRect, radiusPx, dpi, midCol)
            End If

            If tuning.GlossA > 0 AndAlso intensity.UseTopSheen AndAlso intensity.SheenStrength > 0.0F Then
                DrawCoolAcrylicTopGlossBand(
                    canvas:=canvas,
                    r:=baseRect,
                    radiusPx:=radiusPx,
                    dpi:=dpi,
                    midCol:=midCol,
                    wash:=wash,
                    topGlossAlpha:=ResolveTopGlossAlpha(tuning.GlossA, intensity),
                    intensity:=intensity)
            End If

            If fx.EdgeFadeAlpha > 0 AndAlso fx.EdgeFadeSizeDip > 0 Then
                DrawCoolAcrylicEdgeFade(
                    canvas:=canvas,
                    r:=baseRect,
                    radiusPx:=radiusPx,
                    dpi:=dpi,
                    midCol:=midCol,
                    wash:=wash,
                    edgeFadeAlpha:=fx.EdgeFadeAlpha,
                    edgeFadeSizeDip:=fx.EdgeFadeSizeDip,
                    edgeFadeTopBoost:=fx.EdgeFadeTopBoost)
            End If

            If tuning.GrainA > 0 AndAlso fx.GrainTileDip > 0 Then
                DrawCoolAcrylicGrain(
                    canvas:=canvas,
                    r:=baseRect,
                    radiusPx:=radiusPx,
                    dpi:=dpi,
                    grainAlpha:=tuning.GrainA,
                    grainTileDip:=fx.GrainTileDip)
            End If

            If tuning.ShadeBotA > 0 Then
                DrawCoolAcrylicBottomInnerShade(
                    canvas:=canvas,
                    r:=baseRect,
                    radiusPx:=radiusPx,
                    dpi:=dpi,
                    midCol:=midCol,
                    innerShadeBottomAlpha:=tuning.ShadeBotA,
                    intensity:=intensity)
            End If

            If drawRims Then
                Dim sw As Single = Math.Max(1.0F, fx.StrokeDip * dpi)

                If sw >= 0.75F Then
                    DrawCoolAcrylicRims(
                        canvas:=canvas,
                        r:=baseRect,
                        radiusPx:=radiusPx,
                        dpi:=dpi,
                        midCol:=midCol,
                        borderOuter:=borderOuter,
                        borderInner:=borderInner,
                        strokeW:=sw,
                        innerRimAlpha:=tuning.RimA,
                        intensity:=intensity)
                End If
            End If

        End Sub

#End Region

#Region "Core acrylic shader pipeline"

        Private Sub EnsureCoolAcrylicShaders(baseRect As SKRect,
                                             dpi As Single,
                                             fam As CommonEnums.SurfaceFamily,
                                             topCol As SKColor,
                                             midCol As SKColor,
                                             botCol As SKColor,
                                             wash As SKColor)

            Dim sizeKey As MASVisualPrimitivesPainter.SizeDpiKey =
                MASVisualPrimitivesPainter.BuildSizeDpiKey(baseRect, dpi)

            Dim intensity As MASVisualEffectIntensityProfiles.AcrylicSurfaceIntensityProfile =
                MASVisualEffectIntensityProfiles.ResolveAcrylic(fam)

            Dim recipe As AcrylicFamilyRecipe = ResolveAcrylicFamilyRecipe(fam)

            Dim top1 As SKColor = ResolveTop1Color(topCol, midCol, wash, intensity, recipe)
            Dim top2 As SKColor = ResolveTop2Color(topCol, midCol, intensity, recipe)
            Dim mid As SKColor = midCol
            Dim bot2 As SKColor = ResolveBot2Color(midCol, botCol, intensity, recipe)
            Dim bot1 As SKColor = ResolveBot1Color(botCol, midCol, intensity, recipe)

            Dim colorKey As MASVisualPrimitivesPainter.AcrylicColorKey =
                MASVisualPrimitivesPainter.BuildAcrylicColorKey(top1, mid, bot1, wash)

            Dim lastSizeKey As New MASVisualPrimitivesPainter.SizeDpiKey With {
                .W = _lastW,
                .H = _lastH,
                .Dpi100 = _lastDpi100
            }

            Dim lastColorKey As New MASVisualPrimitivesPainter.AcrylicColorKey With {
                .Top = _lastTop,
                .Mid = _lastMid,
                .Bot = _lastBot,
                .Wash = _lastWash
            }

            Dim need As Boolean =
                (_bodyShader Is Nothing) OrElse
                (_shineShader Is Nothing) OrElse
                (_lastFam <> fam) OrElse
                (Not MASVisualPrimitivesPainter.SizeDpiKeyEquals(sizeKey, lastSizeKey)) OrElse
                (Not MASVisualPrimitivesPainter.AcrylicColorKeyEquals(colorKey, lastColorKey))

            If Not need Then Return

            MASVisualPrimitivesPainter.DisposeShader(_bodyShader)
            MASVisualPrimitivesPainter.DisposeShader(_shineShader)

            _bodyShader = SKShader.CreateLinearGradient(
                New SKPoint(0.0F, 0.0F),
                New SKPoint(0.0F, CSng(sizeKey.H)),
                New SKColor() {top1, top2, mid, bot2, bot1},
                recipe.BodyStops,
                SKShaderTileMode.Clamp)

            Dim shineInset As Single = SurfaceTokens.AcrylicShineInsetPx * dpi
            Dim shineH As Single =
                CSng(sizeKey.H) *
                SurfaceTokens.AcrylicShineHeightRatio *
                intensity.SheenHeightMul

            Dim shineRect As New SKRect(
                shineInset,
                shineInset,
                CSng(sizeKey.W) - shineInset,
                shineH)

            Dim shineHiBase As SKColor = ColorMath.Mix(wash, midCol, recipe.ShineHiWashMix)
            Dim shineMidBase As SKColor = ColorMath.Mix(midCol, wash, recipe.ShineMidWashMix)

            Dim hiAlpha As Byte =
                If(intensity.UseTopSheen,
                   CByte(Math.Max(0, Math.Min(255, CInt(Math.Round(recipe.ShineHiBaseA + recipe.ShineHiMulA * intensity.SheenStrength))))),
                   CByte(0))

            Dim midAlpha As Byte =
                If(intensity.UseTopSheen,
                   CByte(Math.Max(0, Math.Min(255, CInt(Math.Round(recipe.ShineMidBaseA + recipe.ShineMidMulA * intensity.SheenStrength))))),
                   CByte(0))

            _shineShader = SKShader.CreateLinearGradient(
                New SKPoint(shineRect.Left, shineRect.Top),
                New SKPoint(shineRect.Left, shineRect.Bottom),
                New SKColor() {
                    ColorMath.WithAlpha(shineHiBase, hiAlpha),
                    ColorMath.WithAlpha(shineMidBase, midAlpha),
                    Values.Color.Core.Transparent
                },
                recipe.ShineStops,
                SKShaderTileMode.Clamp)

            _lastW = sizeKey.W
            _lastH = sizeKey.H
            _lastDpi100 = sizeKey.Dpi100
            _lastFam = fam

            _lastTop = colorKey.Top
            _lastMid = colorKey.Mid
            _lastBot = colorKey.Bot
            _lastWash = colorKey.Wash

        End Sub

#End Region

End Class

End Namespace
