' 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.Threading
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax

Namespace Microsoft.CodeAnalysis.VisualBasic
    Friend Class VisualBasicDeclarationComputer
        Inherits DeclarationComputer

        Public Shared Sub ComputeDeclarationsInSpan(model As SemanticModel,
                                                    span As TextSpan,
                                                    getSymbol As Boolean,
                                                    builder As ArrayBuilder(Of DeclarationInfo),
                                                    cancellationToken As CancellationToken)
            ComputeDeclarationsCore(model, model.SyntaxTree.GetRoot(),
                                    Function(node, level) Not node.Span.OverlapsWith(span) OrElse InvalidLevel(level),
                                    getSymbol, builder, Nothing, cancellationToken)
        End Sub

        Public Shared Sub ComputeDeclarationsInNode(model As SemanticModel,
                                                    node As SyntaxNode,
                                                    getSymbol As Boolean,
                                                    builder As ArrayBuilder(Of DeclarationInfo),
                                                    cancellationToken As CancellationToken, Optional levelsToCompute As Integer? = Nothing)
            ComputeDeclarationsCore(model, node, Function(n, level) InvalidLevel(level), getSymbol, builder, levelsToCompute, cancellationToken)
        End Sub

        Private Shared Function InvalidLevel(level As Integer?) As Boolean
            Return level.HasValue AndAlso level.Value <= 0
        End Function


        Private Shared Function DecrementLevel(level As Integer?) As Integer?
            Return If(level.HasValue, level.Value - 1, level)
        End Function

        Private Shared Sub ComputeDeclarationsCore(model As SemanticModel,
                                                   node As SyntaxNode,
                                                   shouldSkip As Func(Of SyntaxNode, Integer?, Boolean),
                                                   getSymbol As Boolean,
                                                   builder As ArrayBuilder(Of DeclarationInfo),
                                                   levelsToCompute As Integer?,
                                                   cancellationToken As CancellationToken)
            cancellationToken.ThrowIfCancellationRequested()

            If shouldSkip(node, levelsToCompute) Then
                Return
            End If

            Dim newLevel = DecrementLevel(levelsToCompute)

            Select Case node.Kind()
                Case SyntaxKind.NamespaceBlock
                    Dim ns = DirectCast(node, NamespaceBlockSyntax)
                    For Each decl In ns.Members
                        ComputeDeclarationsCore(model, decl, shouldSkip, getSymbol, builder, newLevel, cancellationToken)
                    Next
                    Dim declInfo = GetDeclarationInfo(model, node, getSymbol, cancellationToken)
                    builder.Add(declInfo)

                    Dim name = ns.NamespaceStatement.Name
                    Dim nsSymbol = declInfo.DeclaredSymbol
                    While (name.Kind() = SyntaxKind.QualifiedName)
                        name = (DirectCast(name, QualifiedNameSyntax)).Left
                        Dim declaredSymbol = If(getSymbol, nsSymbol?.ContainingNamespace, Nothing)
                        builder.Add(New DeclarationInfo(name, ImmutableArray(Of SyntaxNode).Empty, declaredSymbol))
                        nsSymbol = declaredSymbol
                    End While

                    Return
                Case SyntaxKind.EnumBlock
                    Dim t = DirectCast(node, EnumBlockSyntax)
                    For Each decl In t.Members
                        ComputeDeclarationsCore(model, decl, shouldSkip, getSymbol, builder, newLevel, cancellationToken)
                    Next
                    Dim attributes = GetAttributes(t.EnumStatement.AttributeLists)
                    builder.Add(GetDeclarationInfo(model, node, getSymbol, attributes, cancellationToken))
                    Return
                Case SyntaxKind.EnumStatement
                    Dim t = DirectCast(node, EnumStatementSyntax)
                    Dim attributes = GetAttributes(t.AttributeLists)
                    builder.Add(GetDeclarationInfo(model, node, getSymbol, attributes, cancellationToken))
                    Return
                Case SyntaxKind.EnumMemberDeclaration
                    Dim t = DirectCast(node, EnumMemberDeclarationSyntax)
                    Dim attributes = GetAttributes(t.AttributeLists)
                    Dim codeBlocks = SpecializedCollections.SingletonEnumerable(Of SyntaxNode)(t.Initializer).Concat(attributes)
                    builder.Add(GetDeclarationInfo(model, node, getSymbol, codeBlocks, cancellationToken))
                    Return
                Case SyntaxKind.EventBlock
                    Dim t = DirectCast(node, EventBlockSyntax)
                    For Each decl In t.Accessors
                        ComputeDeclarationsCore(model, decl, shouldSkip, getSymbol, builder, newLevel, cancellationToken)
                    Next
                    Dim codeBlocks = GetMethodBaseCodeBlocks(t.EventStatement)
                    builder.Add(GetDeclarationInfo(model, node, getSymbol, codeBlocks, cancellationToken))
                    Return
                Case SyntaxKind.FieldDeclaration
                    Dim t = DirectCast(node, FieldDeclarationSyntax)
                    Dim attributes = GetAttributes(t.AttributeLists)
                    For Each decl In t.Declarators
                        Dim initializer = GetInitializerNode(decl)
                        Dim codeBlocks = SpecializedCollections.SingletonEnumerable(initializer).Concat(attributes)
                        For Each identifier In decl.Names
                            builder.Add(GetDeclarationInfo(model, identifier, getSymbol, codeBlocks, cancellationToken))
                        Next
                    Next
                    Return
                Case SyntaxKind.PropertyBlock
                    Dim t = DirectCast(node, PropertyBlockSyntax)
                    For Each decl In t.Accessors
                        ComputeDeclarationsCore(model, decl, shouldSkip, getSymbol, builder, newLevel, cancellationToken)
                    Next
                    Dim codeBlocks = GetPropertyStatementCodeBlocks(t.PropertyStatement)
                    builder.Add(GetDeclarationInfo(model, node, getSymbol, codeBlocks, cancellationToken))
                    Return
                Case SyntaxKind.PropertyStatement
                    Dim t = DirectCast(node, PropertyStatementSyntax)
                    Dim codeBlocks = GetPropertyStatementCodeBlocks(t)
                    builder.Add(GetDeclarationInfo(model, node, getSymbol, codeBlocks, cancellationToken))
                    Return
                Case SyntaxKind.CompilationUnit
                    Dim t = DirectCast(node, CompilationUnitSyntax)
                    For Each decl In t.Members
                        ComputeDeclarationsCore(model, decl, shouldSkip, getSymbol, builder, newLevel, cancellationToken)
                    Next

                    If Not t.Attributes.IsEmpty Then
                        Dim attributes = GetAttributes(t.Attributes)
                        builder.Add(GetDeclarationInfo(model, node, getSymbol, attributes, cancellationToken))
                    End If
                    Return
                Case Else
                    Dim typeBlock = TryCast(node, TypeBlockSyntax)
                    If typeBlock IsNot Nothing Then
                        For Each decl In typeBlock.Members
                            ComputeDeclarationsCore(model, decl, shouldSkip, getSymbol, builder, newLevel, cancellationToken)
                        Next
                        Dim attributes = GetAttributes(typeBlock.BlockStatement.AttributeLists)
                        builder.Add(GetDeclarationInfo(model, node, getSymbol, attributes, cancellationToken))
                        Return
                    End If

                    Dim typeStatement = TryCast(node, TypeStatementSyntax)
                    If typeStatement IsNot Nothing Then
                        Dim attributes = GetAttributes(typeStatement.AttributeLists)
                        builder.Add(GetDeclarationInfo(model, node, getSymbol, attributes, cancellationToken))
                        Return
                    End If

                    Dim methodBlock = TryCast(node, MethodBlockBaseSyntax)
                    If methodBlock IsNot Nothing Then
                        Dim codeBlocks = SpecializedCollections.SingletonEnumerable(Of SyntaxNode)(methodBlock).
                            Concat(GetMethodBaseCodeBlocks(methodBlock.BlockStatement))
                        builder.Add(GetDeclarationInfo(model, node, getSymbol, codeBlocks, cancellationToken))
                        Return
                    End If

                    Dim methodStatement = TryCast(node, MethodBaseSyntax)
                    If methodStatement IsNot Nothing Then
                        Dim codeBlocks = GetMethodBaseCodeBlocks(methodStatement)
                        builder.Add(GetDeclarationInfo(model, node, getSymbol, codeBlocks, cancellationToken))
                        Return
                    End If

                    Return
            End Select
        End Sub

        Private Shared Function GetAttributes(attributeStatements As SyntaxList(Of AttributesStatementSyntax)) As IEnumerable(Of SyntaxNode)
            Dim attributes = SpecializedCollections.EmptyEnumerable(Of SyntaxNode)
            For Each attributeStatement In attributeStatements
                attributes = attributes.Concat(GetAttributes(attributeStatement.AttributeLists))
            Next

            Return attributes
        End Function

        Private Shared Function GetPropertyStatementCodeBlocks(propertyStatement As PropertyStatementSyntax) As IEnumerable(Of SyntaxNode)
            Dim initializer As SyntaxNode = propertyStatement.Initializer
            If initializer Is Nothing Then
                initializer = GetAsNewClauseInitializer(propertyStatement.AsClause)
            End If
            Dim codeBlocks = GetMethodBaseCodeBlocks(propertyStatement)
            Return If(initializer IsNot Nothing,
                SpecializedCollections.SingletonEnumerable(initializer).Concat(codeBlocks),
                codeBlocks)
        End Function

        Private Shared Function GetMethodBaseCodeBlocks(methodBase As MethodBaseSyntax) As IEnumerable(Of SyntaxNode)
            Dim paramInitializers = GetParameterListInitializersAndAttributes(methodBase.ParameterList)
            Dim attributes = GetAttributes(methodBase.AttributeLists).Concat(GetReturnTypeAttributes(GetAsClause(methodBase)))
            Return paramInitializers.Concat(attributes)
        End Function

        Private Shared Function GetAsClause(methodBase As MethodBaseSyntax) As AsClauseSyntax
            Select Case methodBase.Kind
                Case SyntaxKind.SubStatement, SyntaxKind.FunctionStatement
                    Return DirectCast(methodBase, MethodStatementSyntax).AsClause

                Case SyntaxKind.SubLambdaHeader, SyntaxKind.FunctionLambdaHeader
                    Return DirectCast(methodBase, LambdaHeaderSyntax).AsClause

                Case SyntaxKind.DeclareSubStatement, SyntaxKind.DeclareFunctionStatement
                    Return DirectCast(methodBase, DeclareStatementSyntax).AsClause

                Case SyntaxKind.DelegateSubStatement, SyntaxKind.DelegateFunctionStatement
                    Return DirectCast(methodBase, DelegateStatementSyntax).AsClause

                Case SyntaxKind.EventStatement
                    Return DirectCast(methodBase, EventStatementSyntax).AsClause

                Case SyntaxKind.OperatorStatement
                    Return DirectCast(methodBase, OperatorStatementSyntax).AsClause

                Case SyntaxKind.PropertyStatement
                    Return DirectCast(methodBase, PropertyStatementSyntax).AsClause

                Case SyntaxKind.SubNewStatement,
                        SyntaxKind.GetAccessorStatement,
                        SyntaxKind.SetAccessorStatement,
                        SyntaxKind.AddHandlerAccessorStatement,
                        SyntaxKind.RemoveHandlerAccessorStatement,
                        SyntaxKind.RaiseEventAccessorStatement
                    Return Nothing

                Case Else
                    Throw ExceptionUtilities.UnexpectedValue(methodBase.Kind)
            End Select
        End Function

        Private Shared Function GetReturnTypeAttributes(asClause As AsClauseSyntax) As IEnumerable(Of SyntaxNode)
            Return If(asClause IsNot Nothing AndAlso Not asClause.Attributes.IsEmpty,
                GetAttributes(asClause.Attributes),
                SpecializedCollections.EmptyEnumerable(Of SyntaxNode))
        End Function

        Private Shared Iterator Function GetAttributes(attributeLists As SyntaxList(Of AttributeListSyntax)) As IEnumerable(Of SyntaxNode)
            For Each attributeList In attributeLists
                For Each attribute In attributeList.Attributes
                    Yield attribute
                Next
            Next
        End Function

        Private Shared Function GetParameterListInitializersAndAttributes(parameterList As ParameterListSyntax) As IEnumerable(Of SyntaxNode)
            Return If(parameterList IsNot Nothing,
                parameterList.Parameters.SelectMany(Function(p) GetParameterInitializersAndAttributes(p)),
                SpecializedCollections.EmptyEnumerable(Of SyntaxNode))
        End Function

        Private Shared Function GetParameterInitializersAndAttributes(parameter As ParameterSyntax) As IEnumerable(Of SyntaxNode)
            Return SpecializedCollections.SingletonEnumerable(Of SyntaxNode)(parameter.Default).Concat(GetAttributes(parameter.AttributeLists))
        End Function

        Private Shared Function GetInitializerNode(variableDeclarator As VariableDeclaratorSyntax) As SyntaxNode
            Dim initializer As SyntaxNode = variableDeclarator.Initializer
            If initializer Is Nothing Then
                initializer = GetAsNewClauseInitializer(variableDeclarator.AsClause)
            End If

            Return initializer
        End Function

        Private Shared Function GetAsNewClauseInitializer(asClause As AsClauseSyntax) As SyntaxNode
            ' The As New clause itself is necessary rather than the embedded New expression, so that the
            ' code block associated with the declaration appears as an initializer for the purposes
            ' of executing analyzer actions.
            Return If(asClause.IsKind(SyntaxKind.AsNewClause), asClause, Nothing)
        End Function
    End Class
End Namespace
