Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.Diagnostics
Imports Nexamas.UI.Host
Imports SkiaSharp

Namespace Nexamas.UI.Quality

    ''' <summary>
    ''' Runtime proof that hostile or faulty render stages cannot leak SKCanvas clip/matrix state
    ''' into later host stages. This protects Controls, Composition, and FloatRuntime from
    ''' accidental translate/scale/clip contamination by a previous render callback.
    ''' </summary>
    Friend NotInheritable Class MASRenderCanvasStateIsolationGate

        Private Sub New()
        End Sub

        Friend Shared Function Passes() As Boolean
            Return RunWithoutStrictSwallowThrows(AddressOf ProbeMaliciousClipTranslateDoesNotLeakToFollowingDraw) AndAlso
                   RunWithoutStrictSwallowThrows(AddressOf ProbeThrowingStageRestoresCanvasStateBeforeRethrow)
        End Function

        Private Shared Function RunWithoutStrictSwallowThrows(probe As Func(Of Boolean)) As Boolean
            Dim previousThrowOnAny As Boolean = MASExceptionSilencer.ThrowOnSwallowedException
            Dim previousThrowOnLifecycle As Boolean = MASExceptionSilencer.ThrowOnLifecycleBreakingException
            Dim previousTraceRepeated As Boolean = MASExceptionSilencer.TraceRepeatedBoundaryExceptions

            Try
                MASExceptionSilencer.ConfigureStrictness(False, False, previousTraceRepeated)
                Return probe IsNot Nothing AndAlso probe.Invoke()
            Finally
                MASExceptionSilencer.ConfigureStrictness(previousThrowOnAny, previousThrowOnLifecycle, previousTraceRepeated)
            End Try
        End Function

        Private Shared Function ProbeMaliciousClipTranslateDoesNotLeakToFollowingDraw() As Boolean
            Try
                MASRuntimeFaultChannel.ResetForTests()

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

                        MASSkiaCanvasStateIsolation.Invoke(
                            "MASRenderCanvasStateIsolationGate.MaliciousStage",
                            canvas,
                            Sub()
                                canvas.Translate(64.0F, 64.0F)
                                canvas.ClipRect(New SKRect(0.0F, 0.0F, 8.0F, 8.0F))

                                Using paint As New SKPaint()
                                    paint.IsAntialias = False
                                    paint.Color = SKColors.Red
                                    canvas.DrawRect(New SKRect(0.0F, 0.0F, 96.0F, 96.0F), paint)
                                End Using
                            End Sub)

                        Using paint As New SKPaint()
                            paint.IsAntialias = False
                            paint.Color = SKColors.Black
                            canvas.DrawRect(New SKRect(80.0F, 80.0F, 90.0F, 90.0F), paint)
                        End Using

                        Return bitmap.GetPixel(85, 85).Equals(SKColors.Black) AndAlso
                               FaultRecordedFor("MASRenderCanvasStateIsolationGate.MaliciousStage")
                    End Using
                End Using
            Catch ex As Exception
                MASExceptionSilencer.SwallowDiagnosticOnly(ex, "MASRenderCanvasStateIsolationGate.MaliciousClipTranslate")
                Return False
            End Try
        End Function

        Private Shared Function ProbeThrowingStageRestoresCanvasStateBeforeRethrow() As Boolean
            Try
                MASRuntimeFaultChannel.ResetForTests()

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

                        Dim threw As Boolean = False

                        Try
                            MASSkiaCanvasStateIsolation.Invoke(
                                "MASRenderCanvasStateIsolationGate.ThrowingStage",
                                canvas,
                                Sub()
                                    canvas.Translate(64.0F, 64.0F)
                                    canvas.ClipRect(New SKRect(0.0F, 0.0F, 8.0F, 8.0F))
                                    Throw New InvalidOperationException("Synthetic render-stage failure for canvas-state isolation proof.")
                                End Sub)
                        Catch ex As InvalidOperationException
                            threw = True
                        End Try

                        Using paint As New SKPaint()
                            paint.IsAntialias = False
                            paint.Color = SKColors.Black
                            canvas.DrawRect(New SKRect(80.0F, 80.0F, 90.0F, 90.0F), paint)
                        End Using

                        Return threw AndAlso
                               bitmap.GetPixel(85, 85).Equals(SKColors.Black) AndAlso
                               FaultRecordedFor("MASRenderCanvasStateIsolationGate.ThrowingStage")
                    End Using
                End Using
            Catch ex As Exception
                MASExceptionSilencer.SwallowDiagnosticOnly(ex, "MASRenderCanvasStateIsolationGate.ThrowingStageRestore")
                Return False
            End Try
        End Function

        Private Shared Function FaultRecordedFor(contextFragment As String) As Boolean
            Dim snapshot As IReadOnlyList(Of MASRuntimeFaultRecord) = MASRuntimeFaultChannel.GetRecentFaultsSnapshot()

            For Each record As MASRuntimeFaultRecord In snapshot
                If record Is Nothing Then Continue For

                If record.Category = MASExceptionHandlingCategory.RenderFallback AndAlso
                   record.Context.IndexOf("Render.CanvasStateIsolation.", StringComparison.Ordinal) >= 0 AndAlso
                   record.Context.IndexOf(contextFragment, StringComparison.Ordinal) >= 0 Then
                    Return True
                End If
            Next

            Return False
        End Function

    End Class

End Namespace
