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

    ''' <summary>
    ''' MAS-owned checkbox renderer. It is intentionally Friend: external
    ''' applications configure MASCheckBox only, while this painter consumes the
    ''' internal architecture contract, layout helper, and theme selection model.
    ''' </summary>
    Friend NotInheritable Class CheckBoxPainter
        Implements IDisposable

        Private _disposed As Boolean

        Private ReadOnly _surface As New MASVisualPrimitivesPainter()
        Private ReadOnly _shadow As New MASShadowPainter()

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

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

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

            Try
                _surface.Dispose()
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException1)
            End Try
            Try
                _shadow.Dispose()
            Catch masCaughtException2 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException2)
            End Try
            Try
                _glyph.Dispose()
            Catch masCaughtException3 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException3)
            End Try
            Try
                _fill.Dispose()
            Catch masCaughtException4 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException4)
            End Try
        End Sub

        Friend Sub Draw(canvas As SKCanvas,
                        ctx As MASThemeContext,
                        boundsPx As SKRect,
                        text As String,
                        dpi As Single,
                        isHover As Boolean,
                        isPressed As Boolean,
                        hasFocus As Boolean,
                        isEnabled As Boolean,
                        isChecked As Boolean,
                        contract As MASResolvedComponentContract)

            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 boundsPx.Width <= 1.0F OrElse boundsPx.Height <= 1.0F Then Return

            Dim scope As MASContractRenderScope =
    MASArchitectureRuntime.CreateRenderScopeForComponent(Of MASCheckBox)(
        NameOf(CheckBoxPainter),
        contract,
        MASRenderLayer.Surface,
        MASRenderLayer.Shadow,
        MASRenderLayer.Text,
        MASRenderLayer.Hover,
        MASRenderLayer.Pressed,
        MASRenderLayer.Focus)

            scope.RequireAnyLayer(
    MASRenderLayer.Surface,
    MASRenderLayer.Shadow,
    MASRenderLayer.Text,
    MASRenderLayer.Hover,
    MASRenderLayer.Pressed,
    MASRenderLayer.Focus)

            dpi = PixelSnap.SafeDpi(dpi)

            Dim lo As CheckBoxLayoutHelper.LayoutInfo =
                CheckBoxLayoutHelper.BuildLayout(
                    ctx:=ctx,
                    boundsPx:=boundsPx,
                    text:=text)

            If lo.BoxRect.Width <= 1.0F OrElse lo.BoxRect.Height <= 1.0F Then Return

            Dim appTheme As IMASAppTheme = ctx.Theme
            Dim cbTheme As IMASCheckBoxTheme = ctx.CheckBoxTheme

            If appTheme Is Nothing Then Return
            If cbTheme Is Nothing Then Return
            If cbTheme.Text Is Nothing Then Return
            If cbTheme.Selection Is Nothing Then Return

            Dim surfaceTheme As IMASFoundationSurfaceTheme = MASAppThemeContracts.Foundation(appTheme).Surface

            If CheckBoxTokens.ShadowEnabled AndAlso isEnabled Then
                _shadow.DrawLayeredShadow(
                    canvas:=canvas,
                    r:=lo.BoxRect,
                    contract:=contract,
                    dpi:=dpi,
                    surfaceTheme:=surfaceTheme)
            End If

            _surface.DrawAcrylicSurface(
                canvas:=canvas,
                ctx:=ctx,
                r:=lo.BoxRect,
                dpi:=dpi,
                contract:=contract)

            If isChecked Then
                DrawValueBase(
                    canvas:=canvas,
                    boxRect:=lo.BoxRect,
                    contract:=contract,
                    dpi:=dpi,
                    cbTheme:=cbTheme,
                    isEnabled:=isEnabled)
            End If

            Dim masState As MASInteractionState =
    ResolveCheckBoxMASInteractionState(
        isEnabled:=isEnabled,
        isHover:=isHover,
        isPressed:=isPressed,
        hasFocus:=hasFocus,
        isChecked:=isChecked)

            Dim spec As MASVisualInteractionOverlaySpec =
    MASInteractionResolver.Build(
        contract:=contract,
        state:=masState,
        theme:=appTheme)

            If spec.FillEnabled OrElse spec.RingEnabled Then
                MASSurfaceInteractionRing.DrawOverlay(
                    canvas:=canvas,
                    baseRectPx:=lo.BoxRect,
                    contract:=contract,
                    spec:=spec,
                    dpi:=dpi)
            End If

            If isChecked Then
                DrawCheckGlyph(
                    canvas:=canvas,
                    boxRect:=lo.BoxRect,
                    dpi:=dpi,
                    cbTheme:=cbTheme,
                    isEnabled:=isEnabled)
            End If

            DrawLabel(
                canvas:=canvas,
                ctx:=ctx,
                textRect:=lo.TextRect,
                dpi:=dpi,
                text:=text,
                cbTheme:=cbTheme,
                isEnabled:=isEnabled)
        End Sub

        Private Sub DrawValueBase(canvas As SKCanvas,
                                  boxRect As SKRect,
                                  contract As MASResolvedComponentContract,
                                  dpi As Single,
                                  cbTheme As IMASCheckBoxTheme,
                                  isEnabled As Boolean)

            If canvas Is Nothing Then Return
            If contract Is Nothing Then Throw New ArgumentNullException(NameOf(contract))
            If cbTheme Is Nothing OrElse cbTheme.Selection Is Nothing Then Return
            If boxRect.Width <= 1.0F OrElse boxRect.Height <= 1.0F Then Return

            Dim valueScope As MASContractRenderScope =
    MASArchitectureRuntime.CreateRenderScopeForComponent(Of MASCheckBox)(
        NameOf(CheckBoxPainter),
        contract,
        MASRenderLayer.Surface)

            valueScope.RequireAnyLayer(
    MASRenderLayer.Surface)

            Dim fillCol As SKColor =
                ColorMath.Mix(cbTheme.Selection.FillBase, SKColors.White, 0.16F)

            Dim alpha As Byte = CheckBoxTokens.CheckedFillAlpha

            If Not isEnabled Then
                alpha = CByte(Math.Max(40, CInt(alpha) - 20))
            End If

            Dim inset As Single = 1.25F * dpi
            Dim inner As SKRect =
                PixelSnap.SnapRectHalf(PixelSnap.InsetAll(boxRect, inset))

            If inner.Width <= 1.0F OrElse inner.Height <= 1.0F Then Return

            Dim baseRadius As Single =
                MASRadiusResolver.ResolveSurfacePx(
                    contract:=contract,
                    rPx:=boxRect,
                    dpi:=dpi)

            Dim innerRadius As Single =
                CornerRadii.ResolveInsetRadiusPx(baseRadius, inset)

            _fill.Color = fillCol.WithAlpha(alpha)
            canvas.DrawRoundRect(inner, innerRadius, innerRadius, _fill)

            Dim ringCol As SKColor =
                ColorMath.Mix(cbTheme.Selection.RingBase, SKColors.White, 0.08F)

            Dim spec As New MASVisualInteractionOverlaySpec With {
                .FillEnabled = False,
                .RingEnabled = True,
                .Fill = SKColors.Transparent,
                .Ring = ringCol.WithAlpha(CheckBoxTokens.CheckedRingAlpha),
                .FillBlendMode = SKBlendMode.SrcOver,
                .ExtraFillInsetPx = 0.0F,
                .ExtraRingInsetPx = 1.0F * dpi,
                .Shape = MASVisualInteractionOverlayShape.Closed
            }

            MASSurfaceInteractionRing.DrawOverlay(
                canvas:=canvas,
                baseRectPx:=boxRect,
                contract:=contract,
                spec:=spec,
                dpi:=dpi,
                fillInsetDipOverride:=0.0F,
                ringInsetDipOverride:=0.0F,
                ringStrokeDipOverride:=1.0F,
                ringMinStrokePxOverride:=1.0F)
        End Sub

        Private Sub DrawCheckGlyph(canvas As SKCanvas,
                                   boxRect As SKRect,
                                   dpi As Single,
                                   cbTheme As IMASCheckBoxTheme,
                                   isEnabled As Boolean)

            If canvas Is Nothing Then Return
            If cbTheme Is Nothing OrElse cbTheme.Selection Is Nothing Then Return
            If boxRect.Width <= 1.0F OrElse boxRect.Height <= 1.0F Then Return

            Dim glyphCol As SKColor = cbTheme.Selection.GlyphColor

            If Not isEnabled Then
                glyphCol = glyphCol.WithAlpha(CheckBoxTokens.DisabledGlyphAlpha)
            End If

            _glyph.Color = glyphCol
            _glyph.StrokeWidth = Math.Max(1.15F, CheckBoxTokens.GlyphStroke * dpi)

            Using path As SKPath = ResolvePremiumCheckGlyphPath(boxRect, dpi)
                If path IsNot Nothing Then
                    canvas.DrawPath(path, _glyph)
                End If
            End Using
        End Sub


        Private Shared Function ResolvePremiumCheckGlyphPath(boxRect As SKRect,
                                                             dpi As Single) As SKPath
            If boxRect.Width <= 1.0F OrElse boxRect.Height <= 1.0F Then Return Nothing

            Dim inset As Single = CheckBoxTokens.GlyphInset * dpi
            Dim r As New SKRect(
                boxRect.Left + inset,
                boxRect.Top + inset,
                boxRect.Right - inset,
                boxRect.Bottom - inset)
            If r.Width <= 1.0F OrElse r.Height <= 1.0F Then Return Nothing

            Dim p1 As New SKPoint(r.Left + (r.Width * 0.06F), r.Top + (r.Height * 0.56F))
            Dim p2 As New SKPoint(r.Left + (r.Width * 0.38F), r.Bottom - (r.Height * 0.12F))
            Dim p3 As New SKPoint(r.Right - (r.Width * 0.04F), r.Top + (r.Height * 0.16F))

            Dim path As New SKPath()
            path.MoveTo(p1)
            path.CubicTo(
                r.Left + (r.Width * 0.18F), r.Top + (r.Height * 0.66F),
                r.Left + (r.Width * 0.27F), r.Bottom - (r.Height * 0.08F),
                p2.X, p2.Y)
            path.CubicTo(
                r.Left + (r.Width * 0.56F), r.Top + (r.Height * 0.62F),
                r.Right - (r.Width * 0.22F), r.Top + (r.Height * 0.28F),
                p3.X, p3.Y)
            Return path
        End Function

        Private Sub DrawLabel(canvas As SKCanvas,
                              ctx As MASThemeContext,
                              textRect As SKRect,
                              dpi As Single,
                              text As String,
                              cbTheme As IMASCheckBoxTheme,
                              isEnabled As Boolean)

            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If textRect.Width <= 1.0F OrElse textRect.Height <= 1.0F Then Return
            If String.IsNullOrEmpty(text) Then Return
            If cbTheme Is Nothing OrElse cbTheme.Text Is Nothing Then Return

            Dim c As SKColor =
                If(isEnabled, cbTheme.Text.Normal, cbTheme.Text.Disabled)

            TypographyTextPrimitives.DrawSingleLineText(
                canvas:=canvas,
                ctx:=ctx,
                text:=text,
                bounds:=textRect,
                dpi:=dpi,
                style:=MASTypography.MASTextStyle.Body,
                color:=c,
                align:=TypographyTextPrimitives.TextAlign.Left,
                drawShadow:=False)
        End Sub

        Private Shared Function ResolveCheckBoxMASInteractionState(isEnabled As Boolean,
                                                                   isHover As Boolean,
                                                                   isPressed As Boolean,
                                                                   hasFocus As Boolean,
                                                                   isChecked As Boolean) As MASInteractionState

            If Not isEnabled Then Return MASInteractionState.Disabled
            If isPressed Then Return MASInteractionState.Pressed
            If hasFocus Then Return MASInteractionState.Focused
            If isHover Then Return MASInteractionState.Hovered
            If isChecked Then Return MASInteractionState.Checked

            Return MASInteractionState.Enabled
        End Function

    End Class

End Namespace
