Option Strict On
Option Explicit On

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

Namespace Nexamas.UI.Components

    Friend NotInheritable Class BackgroundEffectsPainter
        Implements IDisposable

#Region "Public"

        Friend Property Palette As MASBackgroundPalette = MASBackgroundPalette.Default


#End Region

#Region "Cache"

        Private _lastTileW As Integer = -1
        Private _lastTileH As Integer = -1
        Private _lastDpi100 As Integer = -1

        Private _lastNoiseFreq As Single = Single.NaN
        Private _lastNoiseOct As Integer = -1

        Private _noiseShader As SKShader = Nothing

#End Region

#Region "Reusable Paints"

        Private ReadOnly _tintPaint As New SKPaint With {
            .IsAntialias = True,
            .IsDither = True
        }

        Private ReadOnly _grainPaint As New SKPaint With {
            .IsAntialias = True,
            .IsDither = True
        }

#End Region

        Private _disposed As Boolean

        Friend Sub Render(canvas As SKCanvas,
                          ctx As MASThemeContext,
                          rect As SKRect,
                          dpi As Single)

            If _disposed Then Return
            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If rect.Width <= 1.0F OrElse rect.Height <= 1.0F Then Return

            dpi = PixelSnap.SafeDpi(dpi)

            Dim bg As IMASBackgroundTheme = BackgroundThemeSupport.ResolveBackgroundTheme(ctx)
            Dim pal As BackgroundThemeSupport.BgPalette = BackgroundThemeSupport.GetPalette(ctx, Palette)

            Dim tintStrength As Single = BackgroundThemeSupport.Clamp01(bg.OverlayTintStrength)
            If tintStrength > 0.0001F Then
                DrawTint(canvas, rect, pal, bg, tintStrength)
            End If

            If bg.OverlayGrainAlpha > 0 Then
                DrawGrain(canvas, rect, dpi, pal, bg)
            End If
        End Sub

        Private Sub DrawTint(canvas As SKCanvas,
                             rect As SKRect,
                             pal As BackgroundThemeSupport.BgPalette,
                             bg As IMASBackgroundTheme,
                             tintStrength As Single)

            Dim alpha As Byte =
                CByte(Math.Max(0, Math.Min(255, CInt(255.0F * tintStrength))))

            Dim mixVal As Single = BackgroundThemeSupport.Clamp01(bg.OverlayTintMix)

            Dim tintCol As SKColor =
                ColorMath.Mix(pal.BaseBg, pal.ColdTint, mixVal).WithAlpha(alpha)

            _tintPaint.Color = tintCol
            _tintPaint.BlendMode = SKBlendMode.SrcOver
            _tintPaint.Shader = Nothing

            canvas.DrawRect(rect, _tintPaint)
        End Sub

        Private Sub DrawGrain(canvas As SKCanvas,
                              rect As SKRect,
                              dpi As Single,
                              pal As BackgroundThemeSupport.BgPalette,
                              bg As IMASBackgroundTheme)

            Dim dpi100 As Integer = CInt(Math.Round(dpi * 100.0F))

            Dim scaleMin As Single = BackgroundEffectsTokens.GrainScaleMin
            Dim baseDip As Single = BackgroundEffectsTokens.GrainBaseTileDip
            Dim baseTileDip As Single = baseDip * Math.Max(scaleMin, bg.OverlayGrainScale)

            Dim tilePx As Integer = CInt(Math.Round(baseTileDip * dpi))
            tilePx = Math.Max(BackgroundEffectsTokens.GrainTileMinPx,
                  Math.Min(BackgroundEffectsTokens.GrainTileMaxPx, tilePx))

            Dim tw As Integer = tilePx
            Dim th As Integer = tilePx

            Dim nf As Single = bg.OverlayNoiseFrequency
            Dim no As Integer = Math.Max(1, bg.OverlayNoiseOctaves)

            If _noiseShader Is Nothing OrElse
               tw <> _lastTileW OrElse
               th <> _lastTileH OrElse
               dpi100 <> _lastDpi100 OrElse
               Not BackgroundThemeSupport.NearlyEqual(nf, _lastNoiseFreq, BackgroundTokens.CacheEpsFloat) OrElse
               no <> _lastNoiseOct Then

                RebuildNoiseShader(tw, th, nf, no)

                _lastTileW = tw
                _lastTileH = th
                _lastDpi100 = dpi100
                _lastNoiseFreq = nf
                _lastNoiseOct = no
            End If

            _grainPaint.Color = pal.VignetteBase.WithAlpha(bg.OverlayGrainAlpha)
            _grainPaint.BlendMode = SKBlendMode.SoftLight
            _grainPaint.Shader = _noiseShader

            canvas.DrawRect(rect, _grainPaint)

            _grainPaint.Shader = Nothing
        End Sub

        Private Sub RebuildNoiseShader(tw As Integer, th As Integer, freq As Single, oct As Integer)
            _noiseShader?.Dispose()
            _noiseShader = Nothing

            Dim f As Single = If(freq > 0.0F, freq, 0.9F)
            Dim o As Integer = Math.Max(1, oct)

            Dim tile As New SKSizeI(tw, th)
            _noiseShader = SKShader.CreatePerlinNoiseFractalNoise(f, f, o, 0.0F, tile)
        End Sub

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

            _noiseShader?.Dispose()
            _noiseShader = Nothing

            _tintPaint.Dispose()
            _grainPaint.Dispose()
        End Sub

    End Class

End Namespace