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 RadioButtonPainter
        Implements IDisposable

        Private _disposed As Boolean

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

        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
                _fill.Dispose()
            Catch masCaughtException3 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException3)
            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 MASRadioButton)(
        NameOf(RadioButtonPainter),
        contract,
        MASRenderLayer.Surface,
        MASRenderLayer.Shadow,
        MASRenderLayer.Text,
        MASRenderLayer.Border,
        MASRenderLayer.Hover,
        MASRenderLayer.Pressed,
        MASRenderLayer.Focus)

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

            dpi = PixelSnap.SafeDpi(dpi)

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

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

            Dim appTheme As IMASAppTheme = ctx.Theme
            Dim rbTheme As IMASRadioButtonTheme = ctx.RadioButtonTheme

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

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

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

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

            If isChecked Then
                DrawValueBase(
                    canvas:=canvas,
                    bulletRect:=lo.BulletRect,
                    contract:=contract,
                    dpi:=dpi,
                    rbTheme:=rbTheme,
                    isEnabled:=isEnabled)
            End If

            Dim masState As MASInteractionState =
                ResolveRadioMASInteractionState(
                    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.BulletRect,
                    contract:=contract,
                    spec:=spec,
                    dpi:=dpi)
            End If

            If isChecked Then
                DrawDot(
                    canvas:=canvas,
                    bulletRect:=lo.BulletRect,
                    dpi:=dpi,
                    rbTheme:=rbTheme,
                    isEnabled:=isEnabled,
                    isPressed:=isPressed)
            End If

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

        Private Sub DrawValueBase(canvas As SKCanvas,
                                  bulletRect As SKRect,
                                  contract As MASResolvedComponentContract,
                                  dpi As Single,
                                  rbTheme As IMASRadioButtonTheme,
                                  isEnabled As Boolean)

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

            Dim valueScope As MASContractRenderScope =
    MASArchitectureRuntime.CreateRenderScopeForComponent(Of MASRadioButton)(
        NameOf(RadioButtonPainter),
        contract,
        MASRenderLayer.Surface,
        MASRenderLayer.Border)

            valueScope.RequireAnyLayer(
    MASRenderLayer.Surface,
    MASRenderLayer.Border)

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

            Dim alpha As Byte = RadioButtonTokens.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(bulletRect, inset))

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

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

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

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

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

        Private Sub DrawDot(canvas As SKCanvas,
                            bulletRect As SKRect,
                            dpi As Single,
                            rbTheme As IMASRadioButtonTheme,
                            isEnabled As Boolean,
                            isPressed As Boolean)

            If canvas Is Nothing Then Return
            If rbTheme Is Nothing OrElse rbTheme.Selection Is Nothing Then Return
            If bulletRect.Width <= 1.0F OrElse bulletRect.Height <= 1.0F Then Return

            Dim dotCol As SKColor = rbTheme.Selection.DotColor

            If Not isEnabled Then
                dotCol = dotCol.WithAlpha(RadioButtonTokens.DotDisabledAlpha)
            End If

            Dim dotRect As SKRect = ResolvePremiumDotRect(bulletRect, dpi, isPressed)
            If dotRect.Width <= 1.0F OrElse dotRect.Height <= 1.0F Then Return

            _fill.Color = dotCol
            canvas.DrawOval(dotRect, _fill)

            Dim highlightSize As Single = Math.Min(dotRect.Width, dotRect.Height) * 0.38F
            Dim highlightRect As New SKRect(
                dotRect.Left + (dotRect.Width * 0.2F),
                dotRect.Top + (dotRect.Height * 0.18F),
                dotRect.Left + (dotRect.Width * 0.2F) + highlightSize,
                dotRect.Top + (dotRect.Height * 0.18F) + highlightSize)
            highlightRect = SnapRectHalfLocal(highlightRect)

            Dim highlight As SKColor = ColorMath.Mix(dotCol, SKColors.White, 0.35F).WithAlpha(CByte(Math.Min(110, CInt(dotCol.Alpha))))
            _fill.Color = highlight
            canvas.DrawOval(highlightRect, _fill)
        End Sub


        Private Shared Function ResolvePremiumDotRect(bulletRect As SKRect,
                                                      dpi As Single,
                                                      isPressed As Boolean) As SKRect
            If bulletRect.Width <= 1.0F OrElse bulletRect.Height <= 1.0F Then Return SKRect.Empty

            Dim dotSize As Single = RadioButtonTokens.DotSize * dpi
            If isPressed Then
                dotSize *= RadioButtonTokens.DotPressedScale
            End If

            Dim dotRect As New SKRect(
                bulletRect.MidX - (dotSize * 0.5F),
                bulletRect.MidY - (dotSize * 0.5F),
                bulletRect.MidX + (dotSize * 0.5F),
                bulletRect.MidY + (dotSize * 0.5F))

            Return SnapRectHalfLocal(dotRect)
        End Function

        Private Shared Function SnapRectHalfLocal(r As SKRect) As SKRect
            Return New SKRect(
                CSng(Math.Round(r.Left / 0.5F) * 0.5F),
                CSng(Math.Round(r.Top / 0.5F) * 0.5F),
                CSng(Math.Round(r.Right / 0.5F) * 0.5F),
                CSng(Math.Round(r.Bottom / 0.5F) * 0.5F))
        End Function

        Private Sub DrawLabel(canvas As SKCanvas,
                              ctx As MASThemeContext,
                              textRect As SKRect,
                              dpi As Single,
                              text As String,
                              rbTheme As IMASRadioButtonTheme,
                              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 rbTheme Is Nothing OrElse rbTheme.Text Is Nothing Then Return

            Dim c As SKColor =
                If(isEnabled, rbTheme.Text.Normal, rbTheme.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 ResolveRadioMASInteractionState(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
