' 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.Generic
Imports System.Collections.Immutable
Imports System.Threading
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax

Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols
    ''' <summary>
    ''' Represents a namespace.
    ''' </summary>
    Friend MustInherit Class NamespaceSymbol
        Inherits NamespaceOrTypeSymbol
        Implements INamespaceSymbol, INamespaceSymbolInternal

        ' !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        ' Changes to the public interface of this class should remain synchronized with the C# version.
        ' Do not make any changes to the public interface without making the corresponding change
        ' to the C# version.
        ' !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

        ''' <summary>
        ''' Get all the members of this symbol that are namespaces.
        ''' </summary>
        ''' <returns>An ImmutableArray containing all the namespaces that are members of this symbol. If this symbol has no namespace members,
        ''' returns an empty ImmutableArray. Never returns Nothing.</returns>
        Public Overridable Function GetNamespaceMembers() As IEnumerable(Of NamespaceSymbol)
            Return Me.GetMembers().OfType(Of NamespaceSymbol)()
        End Function

        ''' <summary>
        ''' Get all the members of this symbol that are modules.
        ''' </summary>
        ''' <returns>An ImmutableArray containing all the types that are members of this namespace. If this namespace has no module members,
        ''' returns an empty ImmutableArray. Never returns Nothing.</returns>
        Public MustOverride Function GetModuleMembers() As ImmutableArray(Of NamedTypeSymbol)

        ''' <summary>
        ''' Get all the members of this symbol that are modules that have a particular name
        ''' </summary>
        ''' <returns>An ImmutableArray containing all the modules that are members of this namespace with the given name. 
        ''' If this symbol has no modules with this name,
        ''' returns an empty ImmutableArray. Never returns Nothing.</returns>
        Public Overridable Function GetModuleMembers(name As String) As ImmutableArray(Of NamedTypeSymbol)
            ' default implementation does a post-filter. We can override this if its a performance burden, but 
            ' experience is that it won't be.
            Return GetTypeMembers(name).WhereAsArray(Function(t) t.TypeKind = TypeKind.Module)
        End Function

        ''' <summary>
        ''' Returns whether this namespace is the unnamed, global namespace that is 
        ''' at the root of all namespaces.
        ''' </summary>
        Public Overridable ReadOnly Property IsGlobalNamespace As Boolean Implements INamespaceSymbol.IsGlobalNamespace, INamespaceSymbolInternal.IsGlobalNamespace
            Get
                Return ContainingNamespace Is Nothing
            End Get
        End Property

        Friend MustOverride ReadOnly Property Extent As NamespaceExtent

        ''' <summary>
        ''' The kind of namespace: Module, Assembly or Compilation.
        ''' Module namespaces contain only members from the containing module that share the same namespace name.
        ''' Assembly namespaces contain members for all modules in the containing assembly that share the same namespace name.
        ''' Compilation namespaces contain all members, from source or referenced metadata (assemblies and modules) that share the same namespace name.
        ''' </summary>
        Public ReadOnly Property NamespaceKind As NamespaceKind
            Get
                Return Me.Extent.Kind
            End Get
        End Property

        ''' <summary>
        ''' The containing compilation for compilation namespaces.
        ''' </summary>
        Public ReadOnly Property ContainingCompilation As VisualBasicCompilation
            Get
                Return If(Me.NamespaceKind = NamespaceKind.Compilation, Me.Extent.Compilation, Nothing)
            End Get
        End Property

        ''' <summary>
        ''' If a namespace has Assembly or Compilation extent, it may be composed of multiple
        ''' namespaces that are merged together. If so, ConstituentNamespaces returns
        ''' all the namespaces that were merged. If this namespace was not merged, returns
        ''' an array containing only this namespace.
        ''' </summary>
        Public Overridable ReadOnly Property ConstituentNamespaces As ImmutableArray(Of NamespaceSymbol)
            Get
                Return ImmutableArray.Create(Of NamespaceSymbol)(Me)
            End Get
        End Property

        ''' <summary>
        ''' Containing assembly.
        ''' </summary>
        Public MustOverride Overrides ReadOnly Property ContainingAssembly As AssemblySymbol

        Public NotOverridable Overrides ReadOnly Property ContainingType As NamedTypeSymbol
            Get
                Return Nothing
            End Get
        End Property

        ''' <summary>
        ''' Containing module.
        ''' </summary>
        Public Overrides ReadOnly Property ContainingModule As ModuleSymbol
            Get
                Dim extent = Me.Extent

                If extent.Kind = NamespaceKind.Module Then
                    Return extent.Module
                Else
                    Return Nothing
                End If
            End Get
        End Property

        ''' <summary>
        ''' Gets the kind of this symbol.
        ''' </summary>
        Public NotOverridable Overrides ReadOnly Property Kind As SymbolKind
            Get
                Return SymbolKind.Namespace
            End Get
        End Property

        Public NotOverridable Overrides ReadOnly Property IsImplicitlyDeclared As Boolean
            Get
                Return Me.IsGlobalNamespace
            End Get
        End Property

        ''' <summary>
        ''' Implements visitor pattern.
        ''' </summary>
        Friend Overrides Function Accept(Of TArgument, TResult)(visitor As VisualBasicSymbolVisitor(Of TArgument, TResult), arg As TArgument) As TResult
            Return visitor.VisitNamespace(Me, arg)
        End Function

        ' Only the compiler can create namespace symbols.
        Friend Sub New()
        End Sub

        ''' <summary>
        ''' Get this accessibility that was declared on this symbol. For symbols that do
        ''' not have accessibility declared on them, returns NotApplicable.
        ''' </summary>
        Public NotOverridable Overrides ReadOnly Property DeclaredAccessibility As Accessibility
            Get
                Return Accessibility.Public
            End Get
        End Property

        ''' <summary>
        ''' Returns data decoded from Obsolete attribute or null if there is no Obsolete attribute.
        ''' This property returns ObsoleteAttributeData.Uninitialized if attribute arguments haven't been decoded yet.
        ''' </summary>
        Friend NotOverridable Overrides ReadOnly Property ObsoleteAttributeData As ObsoleteAttributeData
            Get
                Return Nothing
            End Get
        End Property

        ''' <summary>
        ''' Returns declared accessibility of most accessible type within this namespace or within a containing namespace recursively.
        ''' Valid return values:
        '''     Friend,
        '''     Public,
        '''     NotApplicable - if there are no types.
        ''' </summary>
        Friend MustOverride ReadOnly Property DeclaredAccessibilityOfMostAccessibleDescendantType As Accessibility

        ''' <summary>
        ''' Calculate declared accessibility of most accessible type within this namespace or within a containing namespace recursively.
        ''' Expected to be called at most once per namespace symbol, unless there is a race condition.
        ''' 
        ''' Valid return values:
        '''     Friend,
        '''     Public,
        '''     NotApplicable - if there are no types.
        ''' </summary>
        Protected Overridable Function GetDeclaredAccessibilityOfMostAccessibleDescendantType() As Accessibility

            Dim result As Accessibility = Accessibility.NotApplicable

            ' First, iterate through types within this namespace.
            For Each typeMember In Me.GetTypeMembersUnordered()
                Select Case typeMember.DeclaredAccessibility
                    Case Accessibility.Public
                        Return Accessibility.Public

                    Case Else
                        result = Accessibility.Friend

                End Select
            Next

            ' Now, iterate through child namespaces
            For Each member In Me.GetMembersUnordered()
                If member.Kind = SymbolKind.Namespace Then
                    Dim [namespace] = DirectCast(member, NamespaceSymbol)
                    Dim childResult As Accessibility = [namespace].DeclaredAccessibilityOfMostAccessibleDescendantType

                    If childResult > result Then
                        If childResult = Accessibility.Public Then
                            Return Accessibility.Public
                        End If

                        result = childResult
                    End If
                End If
            Next

            Return result
        End Function

        ''' <summary>
        ''' Returns true if namespace contains types accessible from the target assembly.
        ''' </summary>
        Friend Overridable Function ContainsTypesAccessibleFrom(fromAssembly As AssemblySymbol) As Boolean
            Dim accessibility = Me.DeclaredAccessibilityOfMostAccessibleDescendantType

            If accessibility = Accessibility.Public Then
                Return True
            End If

            If accessibility = Accessibility.Friend Then
                Dim containingAssembly = Me.ContainingAssembly

                Return (containingAssembly IsNot Nothing) AndAlso AccessCheck.HasFriendAccessTo(fromAssembly, containingAssembly)
            End If

            Return False
        End Function

        ''' <summary>
        ''' Returns true if this symbol is "shared"; i.e., declared with the "Shared"
        ''' modifier or implicitly always shared.
        ''' </summary>
        Public NotOverridable Overrides ReadOnly Property IsShared As Boolean
            Get
                Return True
            End Get
        End Property

        ''' <summary>
        ''' Lookup a nested namespace.
        ''' </summary>
        ''' <param name="names">
        ''' Sequence of names for nested child namespaces.
        ''' </param>
        ''' <returns>
        ''' Symbol for the most nested namespace, if found. Nothing 
        ''' if namespace or any part of it can not be found.
        ''' </returns>
        ''' <remarks></remarks>
        Friend Function LookupNestedNamespace(names As ImmutableArray(Of String)) As NamespaceSymbol
            Dim scope As NamespaceSymbol = Me

            For Each name As String In names

                Dim nextScope As NamespaceSymbol = Nothing

                For Each symbol As NamespaceOrTypeSymbol In scope.GetMembers(name)

                    Dim ns = TryCast(symbol, NamespaceSymbol)

                    If ns IsNot Nothing Then
                        If nextScope IsNot Nothing Then
                            Debug.Assert(nextScope Is Nothing,
                                                            "Why did we run into an unmerged namespace?")
                            nextScope = Nothing
                            Exit For
                        End If

                        nextScope = ns
                    End If
                Next

                scope = nextScope

                If scope Is Nothing Then
                    Exit For
                End If
            Next

            Return scope
        End Function

        Friend Function LookupNestedNamespace(names As String()) As NamespaceSymbol
            Return LookupNestedNamespace(names.AsImmutableOrNull())
        End Function

        ''' <summary>
        ''' Lookup an immediately nested type referenced from metadata, names should be
        ''' compared case-sensitively.
        ''' </summary>
        ''' <param name="fullEmittedName">
        ''' Full type name possibly with generic name mangling.
        ''' </param>
        ''' <returns>
        ''' Symbol for the type, or MissingMetadataSymbol if the type isn't found.
        ''' </returns>
        ''' <remarks></remarks>
        Friend Overridable Function LookupMetadataType(ByRef fullEmittedName As MetadataTypeName) As NamedTypeSymbol
            Debug.Assert(Not fullEmittedName.IsNull)

            Dim namedType As NamedTypeSymbol = Nothing

            ' Because namespaces are merged case-insensitively,
            ' we need to make sure that we have a match for
            ' full emitted name of the type.
            Dim qualifiedName As String = Me.ToDisplayString(SymbolDisplayFormat.QualifiedNameOnlyFormat)

            Dim typeMembers As ImmutableArray(Of NamedTypeSymbol)

            If fullEmittedName.IsMangled Then
                Debug.Assert(Not fullEmittedName.UnmangledTypeName.Equals(fullEmittedName.TypeName) AndAlso fullEmittedName.InferredArity > 0)

                If fullEmittedName.ForcedArity = -1 OrElse fullEmittedName.ForcedArity = fullEmittedName.InferredArity Then

                    ' Let's handle mangling case first.
                    typeMembers = Me.GetTypeMembers(fullEmittedName.UnmangledTypeName)

                    For Each named In typeMembers
                        If fullEmittedName.InferredArity = named.Arity AndAlso named.MangleName AndAlso
                           String.Equals(named.Name, fullEmittedName.UnmangledTypeName, StringComparison.Ordinal) AndAlso
                           String.Equals(fullEmittedName.NamespaceName, If(named.GetEmittedNamespaceName(), qualifiedName), StringComparison.Ordinal) Then

                            If namedType IsNot Nothing Then
                                ' ambiguity
                                namedType = Nothing
                                Exit For
                            End If

                            namedType = named
                        End If
                    Next
                End If
            Else
                Debug.Assert(fullEmittedName.UnmangledTypeName Is fullEmittedName.TypeName AndAlso fullEmittedName.InferredArity = 0)
            End If

            ' Now try lookup without removing generic arity mangling.
            Dim forcedArity As Integer = fullEmittedName.ForcedArity

            If fullEmittedName.UseCLSCompliantNameArityEncoding Then
                ' Only types with arity 0 are acceptable, we already examined types with mangled names.
                If fullEmittedName.InferredArity > 0 Then
                    GoTo Done
                ElseIf forcedArity = -1 Then
                    forcedArity = 0
                ElseIf forcedArity <> 0 Then
                    GoTo Done
                Else
                    Debug.Assert(forcedArity = fullEmittedName.InferredArity)
                End If
            End If

            typeMembers = Me.GetTypeMembers(fullEmittedName.TypeName)

            For Each named In typeMembers
                ' If the name of the type must include generic mangling, it cannot be our match.
                If Not named.MangleName AndAlso (forcedArity = -1 OrElse forcedArity = named.Arity) AndAlso
                   String.Equals(named.Name, fullEmittedName.TypeName, StringComparison.Ordinal) AndAlso
                   String.Equals(fullEmittedName.NamespaceName, If(named.GetEmittedNamespaceName(), qualifiedName), StringComparison.Ordinal) Then

                    If namedType IsNot Nothing Then
                        ' ambiguity
                        namedType = Nothing
                        Exit For
                    End If

                    namedType = named
                End If
            Next

Done:
            If namedType Is Nothing Then
                If fullEmittedName.FullName.StartsWith("Microsoft.CodeAnalysis.VisualBasic.Syntax.SyntaxList", StringComparison.Ordinal) Then
                    Stop
                End If

                Return New MissingMetadataTypeSymbol.TopLevel(Me.ContainingModule, fullEmittedName)
            End If

            Return namedType
        End Function

        Friend Overrides Function IsDefinedInSourceTree(tree As SyntaxTree, definedWithinSpan As TextSpan?, Optional cancellationToken As CancellationToken = Nothing) As Boolean
            If IsGlobalNamespace Then
                ' Every source file defines the global name space. This is a more efficient implementation.
                Return True
            Else
                Return MyBase.IsDefinedInSourceTree(tree, definedWithinSpan, cancellationToken)
            End If
        End Function

        Friend Function GetNestedNamespace(name As String) As NamespaceSymbol
            For Each sym In Me.GetMembers(name)
                If sym.Kind = SymbolKind.Namespace Then
                    Return DirectCast(sym, NamespaceSymbol)
                End If
            Next
            Return Nothing
        End Function

        Friend Function GetNestedNamespace(name As NameSyntax) As NamespaceSymbol
            Select Case name.Kind
                Case SyntaxKind.IdentifierName
                    Return Me.GetNestedNamespace(DirectCast(name, IdentifierNameSyntax).Identifier.ValueText)
                Case SyntaxKind.QualifiedName
                    Dim qn = DirectCast(name, QualifiedNameSyntax)
                    Dim leftNs = Me.GetNestedNamespace(qn.Left)
                    If leftNs IsNot Nothing Then
                        Return leftNs.GetNestedNamespace(qn.Right)
                    End If
            End Select

            Return Nothing
        End Function

        Friend Overridable Function IsDeclaredInSourceModule([module] As ModuleSymbol) As Boolean
            Return Me.ContainingModule Is [module]
        End Function

        ''' <summary>
        ''' This is an entry point for the Binder to collect extension methods with the given name 
        ''' declared within this (compilation merged or module level) namespace, so that methods 
        ''' from the same type are grouped together. 
        ''' </summary>
        Friend MustOverride Sub AppendProbableExtensionMethods(name As String, methods As ArrayBuilder(Of MethodSymbol))

        ''' <summary>
        ''' This is an entry point for the Binder. Its purpose is to add names of viable extension methods declared 
        ''' in this (compilation merged or module level) namespace to nameSet parameter.
        ''' </summary>
        Friend Overridable Overloads Sub AddExtensionMethodLookupSymbolsInfo(nameSet As LookupSymbolsInfo,
                                                                              options As LookupOptions,
                                                                              originalBinder As Binder)
            AddExtensionMethodLookupSymbolsInfo(nameSet, options, originalBinder, appendThrough:=Me)
        End Sub

        ''' <summary>
        ''' Add names of viable extension methods declared in this (compilation merged or module level) 
        ''' namespace to nameSet parameter.
        ''' 
        ''' The 'appendThrough' parameter allows RetargetingNamespaceSymbol to delegate majority of the work 
        ''' to the underlying namespace symbol, but still perform viability check on RetargetingMethodSymbol.
        ''' </summary>
        Friend MustOverride Overloads Sub AddExtensionMethodLookupSymbolsInfo(
            nameSet As LookupSymbolsInfo,
            options As LookupOptions,
            originalBinder As Binder,
            appendThrough As NamespaceSymbol)

        ''' <summary>
        ''' Populate the map with all probable extension methods declared within this namespace, so that methods from
        ''' the same type were grouped together within each bucket. 
        ''' </summary>
        Friend Overridable Sub BuildExtensionMethodsMap(map As Dictionary(Of String, ArrayBuilder(Of MethodSymbol)))
            For Each containedType As NamedTypeSymbol In Me.TypesToCheckForExtensionMethods
                containedType.BuildExtensionMethodsMap(map, appendThrough:=Me)
            Next
        End Sub

        ''' <summary>
        ''' Gets all extension methods in this namespace given a method's name. 
        ''' </summary>
        Friend Overridable Sub GetExtensionMethods(methods As ArrayBuilder(Of MethodSymbol), name As String)
            For Each containedType As NamedTypeSymbol In Me.TypesToCheckForExtensionMethods
                containedType.GetExtensionMethods(methods, appendThrough:=Me, Name:=name)
            Next
        End Sub

        ''' <summary>
        ''' Return the set of types that should be checked for presence of extension methods in order to build
        ''' a map of extension methods for the namespace. 
        ''' </summary>
        Friend MustOverride ReadOnly Property TypesToCheckForExtensionMethods As ImmutableArray(Of NamedTypeSymbol)

        ''' <summary>
        ''' Populate the map with all probable extension methods in membersByName parameter.
        ''' 
        ''' Returns True if an extension method was appended, False otherwise.
        ''' </summary>
        Friend Function BuildExtensionMethodsMap(
            map As Dictionary(Of String, ArrayBuilder(Of MethodSymbol)),
            membersByName As IEnumerable(Of KeyValuePair(Of String, ImmutableArray(Of Symbol)))
        ) As Boolean
            Dim result As Boolean = False

            For Each pair As KeyValuePair(Of String, ImmutableArray(Of Symbol)) In membersByName
                Dim bucket As ArrayBuilder(Of MethodSymbol) = Nothing

                For Each member As Symbol In pair.Value
                    If member.Kind = SymbolKind.Method Then
                        Dim method = DirectCast(member, MethodSymbol)

                        If method.MayBeReducibleExtensionMethod Then

                            If bucket Is Nothing AndAlso Not map.TryGetValue(method.Name, bucket) Then
                                bucket = ArrayBuilder(Of MethodSymbol).GetInstance()
                                map.Add(pair.Key, bucket)
                            End If

                            BuildExtensionMethodsMapBucket(bucket, method)
                            result = True
                        End If
                    End If
                Next
            Next

            Return result
        End Function

        Friend Sub AddMemberIfExtension(bucket As ArrayBuilder(Of MethodSymbol), member As Symbol)
            If member.Kind = SymbolKind.Method Then
                Dim method = DirectCast(member, MethodSymbol)

                If method.MayBeReducibleExtensionMethod Then
                    BuildExtensionMethodsMapBucket(bucket, method)
                End If
            End If
        End Sub

        ''' <summary>
        ''' This method is overridden by RetargetingNamespaceSymbol and allows it to delegate majority of the work 
        ''' to the underlying namespace symbol, but still retarget method symbols before they are added to the map
        ''' of extension methods.
        ''' </summary>
        Friend Overridable Sub BuildExtensionMethodsMapBucket(bucket As ArrayBuilder(Of MethodSymbol), method As MethodSymbol)
            bucket.Add(method)
        End Sub

#Region "INamespaceSymbol"

        Private Function INamespaceSymbol_GetMembers() As IEnumerable(Of INamespaceOrTypeSymbol) Implements INamespaceSymbol.GetMembers
            Return Me.GetMembers().OfType(Of INamespaceOrTypeSymbol)()
        End Function

        Private Function INamespaceSymbol_GetMembers(name As String) As IEnumerable(Of INamespaceOrTypeSymbol) Implements INamespaceSymbol.GetMembers
            Return Me.GetMembers(name).OfType(Of INamespaceOrTypeSymbol)()
        End Function

        Private Function INamespaceSymbol_GetNamespaceMembers() As IEnumerable(Of INamespaceSymbol) Implements INamespaceSymbol.GetNamespaceMembers
            Return Me.GetNamespaceMembers()
        End Function

        Public Overrides Sub Accept(visitor As SymbolVisitor)
            visitor.VisitNamespace(Me)
        End Sub

        Public Overrides Function Accept(Of TResult)(visitor As SymbolVisitor(Of TResult)) As TResult
            Return visitor.VisitNamespace(Me)
        End Function

        Public Overrides Sub Accept(visitor As VisualBasicSymbolVisitor)
            visitor.VisitNamespace(Me)
        End Sub

        Public Overrides Function Accept(Of TResult)(visitor As VisualBasicSymbolVisitor(Of TResult)) As TResult
            Return visitor.VisitNamespace(Me)
        End Function

        Private ReadOnly Property INamespaceSymbol_ConstituentNamespaces As ImmutableArray(Of INamespaceSymbol) Implements INamespaceSymbol.ConstituentNamespaces
            Get
                Return StaticCast(Of INamespaceSymbol).From(Me.ConstituentNamespaces)
            End Get
        End Property

        Private ReadOnly Property INamespaceSymbol_NamespaceKind As NamespaceKind Implements INamespaceSymbol.NamespaceKind
            Get
                Return Me.NamespaceKind
            End Get
        End Property

        Private ReadOnly Property INamespaceSymbol_ContainingCompilation As Compilation Implements INamespaceSymbol.ContainingCompilation
            Get
                Return Me.ContainingCompilation
            End Get
        End Property

#End Region
    End Class
End Namespace
