Option Strict On
Option Explicit On

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

Namespace Nexamas.UI.Components

    Friend NotInheritable Class MASTileVisualPainter
        Implements IDisposable

        Private ReadOnly _p As MASVisualPrimitivesPainter
        Private ReadOnly _shadow As MASShadowPainter

        Private ReadOnly _pIcon As New SKPaint With {
            .IsAntialias = True,
            .IsDither = True,
            .FilterQuality = SKFilterQuality.Medium
        }

        Private ReadOnly _pInnerLift As New SKPaint With {
            .IsAntialias = True,
            .IsDither = True,
            .Style = SKPaintStyle.Stroke,
            .StrokeJoin = SKStrokeJoin.Round,
            .StrokeCap = SKStrokeCap.Round,
            .Shader = Nothing
        }

        Private ReadOnly _pInnerRim As New SKPaint With {
            .IsAntialias = True,
            .IsDither = True,
            .Style = SKPaintStyle.Stroke,
            .StrokeJoin = SKStrokeJoin.Round,
            .StrokeCap = SKStrokeCap.Round,
            .Shader = Nothing
        }

        Private _disposed As Boolean

        Friend Sub New(primitives As MASVisualPrimitivesPainter,
                       shadow As MASShadowPainter)
            If primitives Is Nothing Then Throw New ArgumentNullException(NameOf(primitives))
            If shadow Is Nothing Then Throw New ArgumentNullException(NameOf(shadow))

            _p = primitives
            _shadow = shadow
        End Sub

        Friend Sub DrawTile(canvas As SKCanvas,
                    ctx As MASThemeContext,
                    contract As MASResolvedComponentContract,
                    r As SKRect,
                    dpi As Single,
                    radiusPx As Single,
                    isHover As Boolean,
                    isPressed As Boolean,
                    isSel As Boolean,
                    bmp As SKBitmap,
                    enabled As Boolean)

            If _disposed Then Return
            If canvas Is Nothing OrElse ctx Is Nothing OrElse contract Is Nothing Then Return

            contract.AssertConsumable()

            Dim appTheme As IMASAppTheme = ctx.Theme
            If appTheme Is Nothing Then Return

            Dim tileTheme As IMASTileTheme = ctx.TileTheme
            If tileTheme Is Nothing OrElse tileTheme.Surface Is Nothing Then Return

            dpi = PixelSnap.SafeDpi(dpi)
            If r.Width <= 1 OrElse r.Height <= 1 Then Return

            Dim rm As MASSurfaceInteractionRing.Metrics = MASSurfaceInteractionRing.GetMetrics(dpi)
            Dim baseR As SKRect = MASSurfaceInteractionRing.SnapBaseRect(r, rm)
            If baseR.Width <= 1 OrElse baseR.Height <= 1 Then Return

            radiusPx = PixelSnap.SafeRadius(radiusPx)
            If radiusPx <= 0.0F Then
                radiusPx =
    MASRadiusResolver.ResolveSurfacePx(
        contract:=contract,
        rPx:=baseR,
        dpi:=dpi)
                radiusPx = PixelSnap.SafeRadius(radiusPx)
            End If

            Dim stateSpec As MASTileStateResolver.TileStateRenderSpec =
                MASTileStateResolver.ResolveRenderSpec(
                    ctx:=ctx,
                    tileTheme:=tileTheme,
                    contract:=contract,
                    baseRect:=baseR,
                    dpi:=dpi,
                    enabled:=enabled,
                    isHover:=isHover,
                    isPressed:=isPressed,
                    isSelected:=isSel
                )

            Dim surfaceTheme As IMASFoundationSurfaceTheme = MASAppThemeContracts.Foundation(appTheme).Surface
            If surfaceTheme IsNot Nothing Then
                _shadow.DrawLayeredShadow(
    canvas:=canvas,
    r:=stateSpec.ShadowRect,
    contract:=contract,
    dpi:=dpi,
    surfaceTheme:=surfaceTheme
)
            End If

            Dim saveCount As Integer = canvas.Save()
            Try
                Dim baseSpec As MASVisualPrimitivesPainter.ChipBaseSpec =
                    BuildTileBaseSpec(stateSpec.Surface)

                _p.DrawChipBaseNoShadow(
                    canvas:=canvas,
                    r:=baseR,
                    radius:=radiusPx,
                    dpi:=dpi,
                    spec:=baseSpec
                )

                DrawTileInnerLift(
                    canvas:=canvas,
                    baseR:=baseR,
                    radiusPx:=radiusPx,
                    dpi:=dpi,
                    innerHighlight:=stateSpec.Surface.InnerHighlight
                )

                DrawTileIdleRim(
                    canvas:=canvas,
                    baseR:=baseR,
                    radiusPx:=radiusPx,
                    dpi:=dpi,
                    borderHairline:=stateSpec.Surface.BorderHairline
                )

                DrawTileInnerRim(
                    canvas:=canvas,
                    baseR:=baseR,
                    radiusPx:=radiusPx,
                    dpi:=dpi,
                    borderInner:=stateSpec.Surface.BorderInner
                )

                If stateSpec.Overlay.FillEnabled OrElse stateSpec.Overlay.RingEnabled Then
                    MASSurfaceInteractionRing.DrawOverlay(
    canvas:=canvas,
    baseRectPx:=baseR,
    baseRadiusPx:=radiusPx,
    spec:=stateSpec.Overlay,
    dpi:=dpi
)
                End If

                DrawBitmapContent(
                    canvas:=canvas,
                    baseR:=baseR,
                    bmp:=bmp,
                    dpi:=dpi,
                    offsetY:=stateSpec.IconOffsetY,
                    scaleMul:=stateSpec.IconScaleMul
                )

            Finally
                canvas.RestoreToCount(saveCount)
            End Try
        End Sub

        Private Shared Function BuildTileBaseSpec(surface As MASTileStateResolver.ResolvedTileSurface) As MASVisualPrimitivesPainter.ChipBaseSpec
            Dim topA As Byte = If(surface.BaseTop.Alpha = 0, CByte(236), surface.BaseTop.Alpha)
            Dim midA As Byte = If(surface.BaseMid.Alpha = 0, CByte(248), surface.BaseMid.Alpha)
            Dim botA As Byte = If(surface.BaseBottom.Alpha = 0, CByte(242), surface.BaseBottom.Alpha)

            Return MASVisualPrimitivesPainter.BuildChipBaseSpec(
                top:=New SKColor(surface.BaseTop.Red, surface.BaseTop.Green, surface.BaseTop.Blue, 255),
                bot:=New SKColor(surface.BaseBottom.Red, surface.BaseBottom.Green, surface.BaseBottom.Blue, 255),
                midT:=0.55F,
                topA:=topA,
                midA:=midA,
                botA:=botA
            )
        End Function

        Private Sub DrawTileInnerLift(canvas As SKCanvas,
                                             baseR As SKRect,
                                             radiusPx As Single,
                                             dpi As Single,
                                             innerHighlight As SKColor)

            If canvas Is Nothing Then Return
            If baseR.Width <= 2.0F OrElse baseR.Height <= 2.0F Then Return
            If innerHighlight.Alpha <= 0 Then Return

            Dim insetPx As Single = Math.Max(1.0F, 1.0F * dpi)
            Dim innerR As SKRect = PixelSnap.SnapRectHalf(PixelSnap.InsetAll(baseR, insetPx))
            If innerR.Width <= 2.0F OrElse innerR.Height <= 2.0F Then Return

            Dim innerRadius As Single = CornerRadii.ResolveInsetRadiusPx(radiusPx, insetPx)

            _pInnerLift.StrokeWidth = Math.Max(1.0F, 0.9F * dpi)
            _pInnerLift.Color = innerHighlight
            _pInnerLift.Shader = Nothing

            canvas.DrawRoundRect(innerR, innerRadius, innerRadius, _pInnerLift)
        End Sub

        Private Shared Sub DrawTileIdleRim(canvas As SKCanvas,
                                           baseR As SKRect,
                                           radiusPx As Single,
                                           dpi As Single,
                                           borderHairline As SKColor)

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

            MASSurfaceInteractionRing.DrawIdleRim(
                canvas:=canvas,
                baseR:=baseR,
                baseRadiusPx:=radiusPx,
                rimColor:=borderHairline,
                rimAlpha:=borderHairline.Alpha,
              strokePx:=Math.Max(SurfaceTokens.FxRingStroke * dpi,
                   SurfaceTokens.FxRingMinStrokePx),
                dpi:=dpi,
                edgeInsetPx:=0.0F
            )
        End Sub

        Private Sub DrawTileInnerRim(canvas As SKCanvas,
                                            baseR As SKRect,
                                            radiusPx As Single,
                                            dpi As Single,
                                            borderInner As SKColor)

            If canvas Is Nothing Then Return
            If baseR.Width <= 2.0F OrElse baseR.Height <= 2.0F Then Return
            If borderInner.Alpha <= 0 Then Return

            Dim insetPx As Single =
    Math.Max(SurfaceTokens.FxRingInset * dpi,
             SurfaceTokens.FxRingMinStrokePx)

            Dim strokePx As Single =
    Math.Max(SurfaceTokens.FxRingStroke * dpi,
             SurfaceTokens.FxRingMinStrokePx)

            Dim rr As SKRect = baseR
            rr.Inflate(-insetPx, -insetPx)
            rr = PixelSnap.SnapRectHalf(rr)

            If rr.Width <= 2.0F OrElse rr.Height <= 2.0F Then Return

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

            _pInnerRim.StrokeWidth = strokePx
            _pInnerRim.Color = borderInner
            _pInnerRim.Shader = Nothing

            canvas.DrawRoundRect(rr, rad, rad, _pInnerRim)
        End Sub

        Private Sub DrawBitmapContent(canvas As SKCanvas,
                                      baseR As SKRect,
                                      bmp As SKBitmap,
                                      dpi As Single,
                                      offsetY As Single,
                                      scaleMul As Single)

            If canvas Is Nothing Then Return
            If bmp Is Nothing Then Return

            Dim info As SKImageInfo = bmp.Info
            Dim bmpW As Integer = info.Width
            Dim bmpH As Integer = info.Height
            If bmpW <= 0 OrElse bmpH <= 0 Then Return

            Dim insetI As Single = IconGridTokens.IconInsetDip * dpi
            Dim maxInset As Single = IconGridTokens.IconInsetMaxDip * dpi
            If Single.IsNaN(insetI) OrElse Single.IsInfinity(insetI) OrElse insetI < 0 Then insetI = 0.0F
            If Single.IsNaN(maxInset) OrElse Single.IsInfinity(maxInset) OrElse maxInset < 0 Then maxInset = 0.0F
            If insetI > maxInset Then insetI = maxInset

            Dim inner As SKRect = baseR
            inner.Inflate(-insetI, -insetI)

            Dim extraPad As Single = IconGridTokens.IconExtraPadDip * dpi
            If Single.IsNaN(extraPad) OrElse Single.IsInfinity(extraPad) OrElse extraPad < 0 Then extraPad = 0.0F
            inner.Inflate(-extraPad, -extraPad)

            Dim dest As SKRect = AspectFitRect(inner, bmpW, bmpH)

            If Math.Abs(scaleMul - 1.0F) > 0.0001F Then
                Dim cx As Single = dest.MidX
                Dim cy As Single = dest.MidY

                Dim nw As Single = dest.Width * scaleMul
                Dim nh As Single = dest.Height * scaleMul

                dest = New SKRect(
                    cx - nw / 2.0F,
                    cy - nh / 2.0F,
                    cx + nw / 2.0F,
                    cy + nh / 2.0F
                )
            End If

            If offsetY <> 0.0F Then
                dest = New SKRect(dest.Left, dest.Top + offsetY, dest.Right, dest.Bottom + offsetY)
            End If

            dest = PixelSnap.SnapRectHalf(dest)
            canvas.DrawBitmap(bmp, dest, _pIcon)
            GC.KeepAlive(bmp)
        End Sub

        Private Shared Function AspectFitRect(container As SKRect, srcW As Integer, srcH As Integer) As SKRect
            If srcW <= 0 OrElse srcH <= 0 Then Return container

            Dim cw As Single = container.Width
            Dim ch As Single = container.Height
            If cw <= 1 OrElse ch <= 1 Then Return container

            Dim sW As Single = CSng(srcW)
            Dim sH As Single = CSng(srcH)

            Dim scale As Single = Math.Min(cw / sW, ch / sH)
            If Single.IsNaN(scale) OrElse Single.IsInfinity(scale) OrElse scale <= 0 Then scale = 1.0F

            Dim w As Single = sW * scale
            Dim h As Single = sH * scale

            Dim x As Single = container.Left + (cw - w) * 0.5F
            Dim y As Single = container.Top + (ch - h) * 0.5F

            Return New SKRect(x, y, x + w, y + h)
        End Function

        Public Sub Dispose() Implements IDisposable.Dispose
            If _disposed Then Return
            _disposed = True
            Try
                _pIcon.Dispose()
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException1)
            End Try

            Try
                _pInnerLift.Dispose()
            Catch masCaughtException2 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException2)
            End Try

            Try
                _pInnerRim.Dispose()
            Catch masCaughtException3 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException3)
            End Try
        End Sub

    End Class

End Namespace
