' 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.Runtime.CompilerServices
Imports System.Threading
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax

Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions.ContextQuery
    Friend Module VisualBasicSyntaxTreeExtensions

        <Extension>
        Friend Function IsAccessibleEventContext(context As VisualBasicSyntaxContext, startAtEnclosingBaseType As Boolean) As Boolean
            If context.FollowsEndOfStatement Then
                Return False
            End If

            Dim targetToken = context.TargetToken
            If targetToken.IsChildToken(Of HandlesClauseSyntax)(Function(hc) hc.HandlesKeyword) OrElse
                targetToken.IsChildSeparatorToken(Function(hc As HandlesClauseSyntax) hc.Events) Then

                Dim container = context.EnclosingNamedType
                If container Is Nothing Then
                    Return False
                End If

                If startAtEnclosingBaseType Then
                    Return container.BaseType.GetAccessibleMembersInThisAndBaseTypes(Of IEventSymbol)(container).Any()
                End If

                Return container.GetAccessibleMembersInThisAndBaseTypes(Of IEventSymbol)(container).Any()
            End If

            Return False
        End Function

        <Extension()>
        Friend Function IsFollowingCompleteStatement(Of TParent As SyntaxNode)(context As VisualBasicSyntaxContext, childGetter As Func(Of TParent, StatementSyntax)) As Boolean
            Dim targetToken = context.TargetToken

            Dim parent = targetToken.GetAncestor(Of TParent)()
            If parent Is Nothing Then
                Return False
            End If

            Dim statement = childGetter(parent)
            If statement Is Nothing Then
                Return False
            End If

            Dim visitor As New IsStatementTerminatingTokenVisitor(targetToken)
            Return visitor.Visit(statement)
        End Function

        ''' <summary>
        ''' The specified position is immediately following a statement of one of the given kinds.
        ''' </summary>
        <Extension()>
        Friend Function IsAfterStatementOfKind(context As VisualBasicSyntaxContext, ParamArray kinds As SyntaxKind()) As Boolean
            If Not context.FollowsEndOfStatement Then
                Return False
            End If

            Dim targetToken = context.TargetToken
            If targetToken.Kind = SyntaxKind.None OrElse targetToken.Parent Is Nothing Then
                Return False
            End If

            Return targetToken.GetAncestor(Of StatementSyntax).IsKind(kinds)
        End Function

        <Extension()>
        Friend Function IsInStatementBlockOfKind(context As VisualBasicSyntaxContext, kind As SyntaxKind) As Boolean
            Return IsInStatementBlockHelper(context, Function(n, k) n.IsKind(k), kind)
        End Function

        <Extension()>
        Friend Function IsInStatementBlockOfKind(context As VisualBasicSyntaxContext, ParamArray kinds As SyntaxKind()) As Boolean
            Return IsInStatementBlockHelper(context, Function(n, k) n.IsKind(k), kinds)
        End Function

        Private Function IsInStatementBlockHelper(Of TArg)(context As VisualBasicSyntaxContext, predicate As Func(Of SyntaxNode, TArg, Boolean), arg As TArg) As Boolean
            Dim ancestor = context.TargetToken.Parent

            Do While ancestor IsNot Nothing
                If TypeOf ancestor Is EndBlockStatementSyntax Then
                    ' If we're within the End Block, skip the block itself
                    ancestor = ancestor.Parent.Parent

                    If ancestor Is Nothing Then
                        Return False
                    End If
                End If

                If predicate(ancestor, arg) Then
                    Return True
                End If

                If TypeOf ancestor Is LambdaExpressionSyntax Then
                    If Not (context.FollowsEndOfStatement AndAlso context.TargetToken = ancestor.GetLastToken()) Then
                        ' We should not look past lambdas
                        Return False
                    End If
                End If

                ancestor = ancestor.Parent
            Loop

            Return False
        End Function

        <Extension()>
        Public Function IsDelegateCreationContext(context As VisualBasicSyntaxContext) As Boolean
            If context.FollowsEndOfStatement Then
                Return False
            End If

            Dim token = context.TargetToken
            If token.Parent.IsKind(SyntaxKind.ArgumentList) AndAlso
               TypeOf token.Parent.Parent Is NewExpressionSyntax Then

                Dim symbolInfo = context.SemanticModel.GetSymbolInfo(DirectCast(token.Parent.Parent, NewExpressionSyntax).Type())
                Dim objectCreationType = TryCast(symbolInfo.Symbol, ITypeSymbol)
                If objectCreationType IsNot Nothing AndAlso
                   objectCreationType.TypeKind = TypeKind.Delegate Then

                    Return True
                End If
            End If

            Return False
        End Function

        <Extension()>
        Public Function CanDeclareCustomEventAccessor(context As VisualBasicSyntaxContext, accessorBlockKind As SyntaxKind) As Boolean
            If context.IsCustomEventContext Then
                Dim accessors = context.TargetToken.GetAncestor(Of EventBlockSyntax)().Accessors
                Return Not accessors.Any(Function(a) a.IsKind(accessorBlockKind)) AndAlso
                    Not accessors.Any(Function(a) a.Span.Contains(context.Position))
            End If

            Return False
        End Function
    End Module
End Namespace
