' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.

Imports System.Collections.Immutable
Imports System.Collections.ObjectModel
Imports System.Reflection.Metadata
Imports System.Reflection.Metadata.Ecma335
Imports System.Runtime.CompilerServices
Imports Microsoft.CodeAnalysis.ExpressionEvaluator
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE

Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator

    Friend Module CompilationExtensions
        Private Function [GetType]([module] As PEModuleSymbol, typeHandle As TypeDefinitionHandle) As PENamedTypeSymbol
            Dim metadataDecoder = New MetadataDecoder([module])
            Return DirectCast(metadataDecoder.GetTypeOfToken(typeHandle), PENamedTypeSymbol)
        End Function

        <Extension>
        Friend Function [GetType](compilation As VisualBasicCompilation, moduleVersionId As Guid, typeToken As Integer) As PENamedTypeSymbol
            Return [GetType](compilation.GetModule(moduleVersionId), CType(MetadataTokens.Handle(typeToken), TypeDefinitionHandle))
        End Function

        <Extension>
        Friend Function GetSourceMethod(compilation As VisualBasicCompilation, moduleVersionId As Guid, methodHandle As MethodDefinitionHandle) As PEMethodSymbol
            Dim method = GetMethod(compilation, moduleVersionId, methodHandle)
            Dim metadataDecoder = New MetadataDecoder(DirectCast(method.ContainingModule, PEModuleSymbol))
            Dim containingType = method.ContainingType
            Dim sourceMethodName As String = Nothing
            If GeneratedNameParser.TryParseStateMachineTypeName(containingType.Name, sourceMethodName) Then
                For Each member In containingType.ContainingType.GetMembers(sourceMethodName)
                    Dim candidateMethod = TryCast(member, PEMethodSymbol)
                    If candidateMethod IsNot Nothing Then
                        Dim stateMachineTypeName As String = Nothing
                        If metadataDecoder.Module.HasStateMachineAttribute(candidateMethod.Handle, stateMachineTypeName) AndAlso
                            metadataDecoder.GetTypeSymbolForSerializedType(stateMachineTypeName).OriginalDefinition.Equals(containingType) Then
                            Return candidateMethod
                        End If
                    End If
                Next
            End If
            Return method
        End Function

        <Extension>
        Friend Function GetMethod(compilation As VisualBasicCompilation, moduleVersionId As Guid, methodHandle As MethodDefinitionHandle) As PEMethodSymbol
            Dim [module] = compilation.GetModule(moduleVersionId)
            Dim reader = [module].Module.MetadataReader
            Dim typeHandle = reader.GetMethodDefinition(methodHandle).GetDeclaringType()
            Dim type = [GetType]([module], typeHandle)
            Dim method = DirectCast(New MetadataDecoder([module], type).GetMethodSymbolForMethodDefOrMemberRef(methodHandle, type), PEMethodSymbol)
            Return method
        End Function

        <Extension>
        Friend Function GetModule(compilation As VisualBasicCompilation, moduleVersionId As Guid) As PEModuleSymbol
            For Each pair In compilation.GetBoundReferenceManager().GetReferencedAssemblies()
                Dim assembly = DirectCast(pair.Value, AssemblySymbol)
                For Each [module] In assembly.Modules
                    Dim m = DirectCast([module], PEModuleSymbol)
                    Dim id = m.Module.GetModuleVersionIdOrThrow()
                    If id = moduleVersionId Then
                        Return m
                    End If
                Next
            Next

            Throw New ArgumentException($"No module found with MVID '{moduleVersionId}'", NameOf(moduleVersionId))
        End Function

        <Extension>
        Friend Function ToCompilation(metadataBlocks As ImmutableArray(Of MetadataBlock)) As VisualBasicCompilation
            Return ToCompilation(metadataBlocks, moduleVersionId:=Nothing, MakeAssemblyReferencesKind.AllAssemblies)
        End Function

        <Extension>
        Friend Function ToCompilationReferencedModulesOnly(metadataBlocks As ImmutableArray(Of MetadataBlock), moduleVersionId As Guid) As VisualBasicCompilation
            Return ToCompilation(metadataBlocks, moduleVersionId, MakeAssemblyReferencesKind.DirectReferencesOnly)
        End Function

        <Extension>
        Friend Function ToCompilation(metadataBlocks As ImmutableArray(Of MetadataBlock), moduleVersionId As Guid, kind As MakeAssemblyReferencesKind) As VisualBasicCompilation
            Dim referencesBySimpleName As IReadOnlyDictionary(Of String, ImmutableArray(Of (AssemblyIdentity, MetadataReference))) = Nothing
            Dim references = metadataBlocks.MakeAssemblyReferences(moduleVersionId, IdentityComparer, kind, referencesBySimpleName)
            Dim options = s_compilationOptions
            If referencesBySimpleName IsNot Nothing Then
                Debug.Assert(kind = MakeAssemblyReferencesKind.AllReferences)
                Dim resolver = New EEMetadataReferenceResolver(IdentityComparer, referencesBySimpleName)
                options = options.WithMetadataReferenceResolver(resolver)
            End If
            Return VisualBasicCompilation.Create(
                assemblyName:=ExpressionCompilerUtilities.GenerateUniqueName(),
                references:=references,
                options:=options)
        End Function

        <Extension>
        Friend Function GetCustomTypeInfoPayload(compilation As VisualBasicCompilation, type As TypeSymbol) As ReadOnlyCollection(Of Byte)
            Dim builder = ArrayBuilder(Of String).GetInstance()
            Dim names = If(VisualBasicCompilation.TupleNamesEncoder.TryGetNames(type, builder) AndAlso compilation.HasTupleNamesAttributes,
                New ReadOnlyCollection(Of String)(builder.ToArray()),
                Nothing)
            builder.Free()
            Return CustomTypeInfo.Encode(Nothing, names)
        End Function

        Friend ReadOnly IdentityComparer As AssemblyIdentityComparer = DesktopAssemblyIdentityComparer.Default

        ' XML file references, #r directives not supported:
        Private ReadOnly s_compilationOptions As VisualBasicCompilationOptions = New VisualBasicCompilationOptions(
            outputKind:=OutputKind.DynamicallyLinkedLibrary,
            platform:=Platform.AnyCpu, ' Platform should match PEModule.Machine, in this case I386.
            optimizationLevel:=OptimizationLevel.Release,
            assemblyIdentityComparer:=IdentityComparer).
            WithMetadataImportOptions(MetadataImportOptions.All).
            WithReferencesSupersedeLowerVersions(True).
            WithSuppressEmbeddedDeclarations(True).
            WithIgnoreCorLibraryDuplicatedTypes(True)

    End Module

End Namespace
