Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Reflection
Imports Nexamas.UI.Controls
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Quality

    ''' <summary>
    ''' Runtime proof that ControlsLayer render clipping matches hit-test clipping for a retained
    ''' control subtree. A parent-owned clip must constrain the parent body, direct children, and
    ''' owned child-layer rendering so the SDK never exposes visible-but-not-interactive pixels.
    ''' </summary>
    Friend NotInheritable Class MASControlsSubtreeClipContractGate

        Private Sub New()
        End Sub

        Friend Shared Function Passes() As Boolean
            Return ProbeDirectChildCannotRenderOutsideParentClip() AndAlso
                   ProbeChildLayerCannotRenderOutsideParentClip() AndAlso
                   ProbeHitTestRejectsSameOutsideClipPoint()
        End Function

        Private Shared Function ProbeDirectChildCannotRenderOutsideParentClip() As Boolean
            Try
                ThemeManager.EnsureInitializedCore()

                Dim ctx As MASThemeContext = CreateThemeContext()
                Dim root As New SolidProbeControl("clip-parent", New SKColor(0, 96, 0, 255))
                Dim child As New SolidProbeControl("direct-child-outside-clip", New SKColor(220, 0, 0, 255))

                root.SetLayoutPlacementInternal(New SKRect(0.0F, 0.0F, 90.0F, 90.0F), New System.Nullable(Of SKRect)(New SKRect(0.0F, 0.0F, 50.0F, 50.0F)))
                child.SetLocalLayoutBoundsInternal(New SKRect(60.0F, 10.0F, 86.0F, 36.0F))
                root.AddProbeChild(child)

                Using bitmap As New SKBitmap(96, 96)
                    Using canvas As New SKCanvas(bitmap)
                        canvas.Clear(SKColors.White)

                        RenderSingleControlTreeForGate(canvas, ctx, root)

                        Return bitmap.GetPixel(10, 10).Equals(New SKColor(0, 96, 0, 255)) AndAlso
                               bitmap.GetPixel(65, 15).Equals(SKColors.White)
                    End Using
                End Using
            Catch ex As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDiagnosticOnly(ex, "MASControlsSubtreeClipContractGate.DirectChildClip")
                Return False
            End Try
        End Function

        Private Shared Function ProbeChildLayerCannotRenderOutsideParentClip() As Boolean
            Try
                ThemeManager.EnsureInitializedCore()

                Dim ctx As MASThemeContext = CreateThemeContext()
                Dim root As New SolidProbeControl("clip-parent-child-layer", New SKColor(0, 96, 0, 255))
                Dim layerChild As New SolidProbeControl("child-layer-outside-clip", New SKColor(0, 0, 220, 255))

                root.SetLayoutPlacementInternal(New SKRect(0.0F, 0.0F, 90.0F, 90.0F), New System.Nullable(Of SKRect)(New SKRect(0.0F, 0.0F, 50.0F, 50.0F)))
                layerChild.SetLocalLayoutBoundsInternal(New SKRect(60.0F, 10.0F, 86.0F, 36.0F))
                AddControlToLayerForGate(root.ChildLayerInternal, layerChild)

                Using bitmap As New SKBitmap(96, 96)
                    Using canvas As New SKCanvas(bitmap)
                        canvas.Clear(SKColors.White)

                        RenderSingleControlTreeForGate(canvas, ctx, root)

                        Return bitmap.GetPixel(10, 10).Equals(New SKColor(0, 96, 0, 255)) AndAlso
                               bitmap.GetPixel(65, 15).Equals(SKColors.White)
                    End Using
                End Using
            Catch ex As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDiagnosticOnly(ex, "MASControlsSubtreeClipContractGate.ChildLayerClip")
                Return False
            End Try
        End Function

        Private Shared Function ProbeHitTestRejectsSameOutsideClipPoint() As Boolean
            Try
                ThemeManager.EnsureInitializedCore()

                Dim ctx As MASThemeContext = CreateThemeContext()
                Dim root As New SolidProbeControl("hit-parent", New SKColor(0, 96, 0, 255))
                Dim child As New SolidProbeControl("hit-child-outside-clip", New SKColor(220, 0, 0, 255))
                Dim predicateCalls As Integer = 0

                root.SetLayoutPlacementInternal(New SKRect(0.0F, 0.0F, 90.0F, 90.0F), New System.Nullable(Of SKRect)(New SKRect(0.0F, 0.0F, 50.0F, 50.0F)))
                child.SetLocalLayoutBoundsInternal(New SKRect(60.0F, 10.0F, 86.0F, 36.0F))
                root.AddProbeChild(child)

                Dim roots As MASControlBase() = New MASControlBase() {root}
                Dim hit As MASControlBase = MASControlsHitTestService.HitTestTopPointerEnabled(
                    roots,
                    ctx,
                    New SKPoint(65.0F, 15.0F),
                    Function(ctrl As MASControlBase)
                        predicateCalls += 1
                        Return True
                    End Function)

                Return hit Is Nothing AndAlso predicateCalls = 0
            Catch ex As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDiagnosticOnly(ex, "MASControlsSubtreeClipContractGate.HitRejectsOutsideClip")
                Return False
            End Try
        End Function

        Private Shared Function CreateThemeContext() As MASThemeContext
            Return New MASThemeContext(1.0F, New MASTypography(1.0F), ThemeManager.CurrentCore, 0)
        End Function

        Private Shared Sub RenderSingleControlTreeForGate(canvas As SKCanvas,
                                                          ctx As MASThemeContext,
                                                          root As MASControlBase)
            Dim layer As New ControlsLayer()
            Dim method As MethodInfo = GetType(ControlsLayer).GetMethod(
                "RenderControlTree",
                BindingFlags.Instance Or BindingFlags.NonPublic)

            If method Is Nothing Then
                Throw New MissingMethodException(GetType(ControlsLayer).FullName, "RenderControlTree")
            End If

            method.Invoke(layer, New Object() {canvas, ctx, root, New SKRect(0.0F, 0.0F, 96.0F, 96.0F)})
        End Sub

        Private Shared Sub AddControlToLayerForGate(layer As ControlsLayer,
                                                    control As MASControlBase)
            If layer Is Nothing Then Throw New ArgumentNullException(NameOf(layer))
            If control Is Nothing Then Throw New ArgumentNullException(NameOf(control))

            Dim itemsField As FieldInfo = GetType(ControlsLayer).GetField("_items", BindingFlags.Instance Or BindingFlags.NonPublic)
            Dim snapshotDirtyField As FieldInfo = GetType(ControlsLayer).GetField("_snapshotDirty", BindingFlags.Instance Or BindingFlags.NonPublic)
            Dim snapshotCacheField As FieldInfo = GetType(ControlsLayer).GetField("_snapshotCache", BindingFlags.Instance Or BindingFlags.NonPublic)

            If itemsField Is Nothing OrElse snapshotDirtyField Is Nothing OrElse snapshotCacheField Is Nothing Then
                Throw New MissingFieldException(GetType(ControlsLayer).FullName, "ControlsLayer snapshot fields")
            End If

            Dim items As List(Of MASControlBase) = TryCast(itemsField.GetValue(layer), List(Of MASControlBase))
            If items Is Nothing Then Throw New InvalidOperationException("ControlsLayer items store has an unexpected shape.")

            items.Add(control)
            snapshotDirtyField.SetValue(layer, True)
            snapshotCacheField.SetValue(layer, Array.Empty(Of MASControlBase)())
        End Sub

        Private NotInheritable Class SolidProbeControl
            Inherits MASControlBase

            Private ReadOnly _fill As SKColor

            Friend Sub New(name As String,
                           fill As SKColor)
                Me.Name = If(name, String.Empty)
                _fill = fill
            End Sub

            Friend Sub AddProbeChild(child As MASControlBase)
                RegisterChild(child)
            End Sub

            Protected Overrides Sub Render(canvas As SKCanvas,
                                           ctx As MASThemeContext,
                                           pixelBounds As SKRect)
                If canvas Is Nothing Then Return
                If pixelBounds.Width <= 0.5F OrElse pixelBounds.Height <= 0.5F Then Return

                Using paint As New SKPaint()
                    paint.IsAntialias = False
                    paint.Style = SKPaintStyle.Fill
                    paint.Color = _fill
                    canvas.DrawRect(pixelBounds, paint)
                End Using
            End Sub
        End Class

    End Class

End Namespace
