Option Strict On
Option Explicit On

Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports System.Reflection
Imports Nexamas.UI.Application
Imports Nexamas.UI.Components
Imports Nexamas.UI.Controls

Namespace Nexamas.UI.Quality

    ''' <summary>
    ''' Static lifecycle proof for MASApplicationSurfaceMaterialScope. The scope may outlive a hosted surface during application
    ''' teardown, so it must not retain MASApplicationWindow or registered controls through direct strong fields or strong lists.
    ''' </summary>
    Friend NotInheritable Class MASApplicationSurfaceMaterialScopeLifecycleGate

        Private Sub New()
        End Sub

        Friend Shared Function Passes() As Boolean
            Dim scopeType As Type = GetType(MASApplicationSurfaceMaterialScope)
            Dim fields As FieldInfo() = scopeType.GetFields(BindingFlags.Instance Or BindingFlags.NonPublic)
            If fields Is Nothing OrElse fields.Length = 0 Then Return False

            Dim hasWindowWeakReference As Boolean = False
            Dim weakCollectionCount As Integer = 0

            For Each field As FieldInfo In fields
                If field Is Nothing Then Continue For
                Dim fieldType As Type = field.FieldType
                If fieldType Is Nothing Then Return False

                If IsRetainedSurfaceType(fieldType) Then Return False
                If IsStrongListOfRetainedSurfaceTypes(fieldType) Then Return False

                If IsWeakReferenceOf(fieldType, GetType(MASApplicationWindow)) Then hasWindowWeakReference = True
                If IsListOfWeakReferenceToRetainedSurfaceType(fieldType) Then weakCollectionCount += 1
            Next

            If Not hasWindowWeakReference OrElse weakCollectionCount < 6 Then Return False
            Return ProbeWeakReferenceCollectionCleanup(scopeType)
        End Function


        Private Shared Function ProbeWeakReferenceCollectionCleanup(scopeType As Type) As Boolean
            If scopeType Is Nothing Then Return False

            Dim addMethod As MethodInfo = scopeType.GetMethod("AddUnique", BindingFlags.Static Or BindingFlags.NonPublic)
            Dim resolveMethod As MethodInfo = scopeType.GetMethod("ResolveLive", BindingFlags.Static Or BindingFlags.NonPublic)
            If addMethod Is Nothing OrElse resolveMethod Is Nothing Then Return False

            Dim list As New List(Of WeakReference(Of MASLabel))()
            Dim label As MASLabel = MASLabel.Create("scope-probe")
            Dim addLabelMethod As MethodInfo = addMethod.MakeGenericMethod(GetType(MASLabel))
            Dim resolveLabelMethod As MethodInfo = resolveMethod.MakeGenericMethod(GetType(MASLabel))

            addLabelMethod.Invoke(Nothing, New Object() {list, label})
            addLabelMethod.Invoke(Nothing, New Object() {list, label})
            If list.Count <> 1 Then Return False

            DirectCast(list, IList).Add(New WeakReference(Of MASLabel)(Nothing))
            Dim live As IReadOnlyList(Of MASLabel) = TryCast(resolveLabelMethod.Invoke(Nothing, New Object() {list}), IReadOnlyList(Of MASLabel))
            If live Is Nothing OrElse live.Count <> 1 Then Return False
            If Not Object.ReferenceEquals(live(0), label) Then Return False
            Return list.Count = 1
        End Function

        Private Shared Function IsRetainedSurfaceType(type As Type) As Boolean
            If type Is Nothing Then Return False
            Return type Is GetType(MASApplicationWindow) OrElse
                   type Is GetType(MASTopBarComponent) OrElse
                   type Is GetType(MASToolbar) OrElse
                   type Is GetType(MASGroupBox) OrElse
                   type Is GetType(MASProgressBar) OrElse
                   type Is GetType(MASOperationProgressBox) OrElse
                   type Is GetType(MASCard)
        End Function

        Private Shared Function IsStrongListOfRetainedSurfaceTypes(type As Type) As Boolean
            If type Is Nothing OrElse Not type.IsGenericType Then Return False
            If type.GetGenericTypeDefinition() IsNot GetType(List(Of )) Then Return False
            Dim args As Type() = type.GetGenericArguments()
            Return args IsNot Nothing AndAlso args.Length = 1 AndAlso IsRetainedSurfaceType(args(0))
        End Function

        Private Shared Function IsWeakReferenceOf(type As Type, targetType As Type) As Boolean
            If type Is Nothing OrElse targetType Is Nothing OrElse Not type.IsGenericType Then Return False
            If type.GetGenericTypeDefinition() IsNot GetType(WeakReference(Of )) Then Return False
            Dim args As Type() = type.GetGenericArguments()
            Return args IsNot Nothing AndAlso args.Length = 1 AndAlso args(0) Is targetType
        End Function

        Private Shared Function IsListOfWeakReferenceToRetainedSurfaceType(type As Type) As Boolean
            If type Is Nothing OrElse Not type.IsGenericType Then Return False
            If type.GetGenericTypeDefinition() IsNot GetType(List(Of )) Then Return False

            Dim listArgs As Type() = type.GetGenericArguments()
            If listArgs Is Nothing OrElse listArgs.Length <> 1 Then Return False

            Dim itemType As Type = listArgs(0)
            If itemType Is Nothing OrElse Not itemType.IsGenericType Then Return False
            If itemType.GetGenericTypeDefinition() IsNot GetType(WeakReference(Of )) Then Return False

            Dim weakArgs As Type() = itemType.GetGenericArguments()
            Return weakArgs IsNot Nothing AndAlso weakArgs.Length = 1 AndAlso IsRetainedSurfaceType(weakArgs(0))
        End Function

    End Class

End Namespace
