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

Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols
    ''' <summary>
    ''' This class represents a synthesized delegate type derived from an Event declaration
    ''' </summary>
    ''' <remarks>
    ''' <example>
    ''' Class C
    '''   Event Name(a As Integer, b As Integer)
    ''' End Class
    ''' 
    ''' defines an event and a delegate type:
    ''' 
    ''' Event Name As NamedEventHandler
    ''' Delegate Sub NameEventHandler(a As Integer, b As Integer)
    ''' 
    ''' </example>
    ''' </remarks>
    Friend NotInheritable Class SynthesizedEventDelegateSymbol
        Inherits InstanceTypeSymbol

        Private ReadOnly _eventName As String
        Private ReadOnly _name As String
        Private ReadOnly _containingType As NamedTypeSymbol
        Private ReadOnly _syntaxRef As SyntaxReference

        Private _lazyMembers As ImmutableArray(Of Symbol)
        Private _lazyEventSymbol As EventSymbol

        Private _reportedAllDeclarationErrors As Integer = 0 ' An integer to be able to do Interlocked operations.

        Friend Sub New(syntaxRef As SyntaxReference, containingSymbol As NamedTypeSymbol)
            Me._containingType = containingSymbol
            Me._syntaxRef = syntaxRef

            Dim eventName = Me.EventSyntax.Identifier.ValueText
            Me._eventName = eventName
            Me._name = _eventName & EVENT_DELEGATE_SUFFIX
        End Sub

        Public Overloads Overrides Function GetMembers() As ImmutableArray(Of Symbol)
            If Not _lazyMembers.IsDefault Then
                Return _lazyMembers
            End If

            Dim sourceModule = DirectCast(Me.ContainingModule, SourceModuleSymbol)
            Dim binder As binder = BinderBuilder.CreateBinderForType(sourceModule, _syntaxRef.SyntaxTree, Me.ContainingType)

            Dim diagBag = DiagnosticBag.GetInstance()

            Dim syntax = Me.EventSyntax
            Dim paramListOpt = syntax.ParameterList

            Dim ctor As MethodSymbol = Nothing
            Dim beginInvoke As MethodSymbol = Nothing
            Dim endInvoke As MethodSymbol = Nothing
            Dim invoke As MethodSymbol = Nothing

            SourceDelegateMethodSymbol.MakeDelegateMembers(Me, Me.EventSyntax, syntax.ParameterList, binder, ctor, beginInvoke, endInvoke, invoke, diagBag)

            ' We shouldn't need to check if this is a winmd compilation because 
            ' winmd output requires that all events be declared Event ... As ...,
            ' but we can't add Nothing to the array, even if a diagnostic will be produced later
            ' Invoke must always be the last member
            Dim members As ImmutableArray(Of Symbol)
            If beginInvoke Is Nothing OrElse endInvoke Is Nothing Then
                members = ImmutableArray.Create(Of Symbol)(ctor, invoke)
            Else
                members = ImmutableArray.Create(Of Symbol)(ctor, beginInvoke, endInvoke, invoke)
            End If

            sourceModule.AtomicStoreArrayAndDiagnostics(_lazyMembers, members, diagBag, CompilationStage.Declare)
            diagBag.Free()

            Return _lazyMembers
        End Function

        Public Overloads Overrides Function GetMembers(name As String) As ImmutableArray(Of Symbol)
            Return (From m In GetMembers() Where IdentifierComparison.Equals(m.Name, name)).AsImmutable
        End Function

        Friend Overrides Function GetFieldsToEmit() As IEnumerable(Of FieldSymbol)
            Return SpecializedCollections.EmptyEnumerable(Of FieldSymbol)()
        End Function

        Private ReadOnly Property EventSyntax As EventStatementSyntax
            Get
                Return DirectCast(Me._syntaxRef.GetSyntax, EventStatementSyntax)
            End Get
        End Property

        Public Overrides ReadOnly Property AssociatedSymbol As Symbol
            Get
                If _lazyEventSymbol Is Nothing Then
                    Dim events = _containingType.GetMembers(_eventName)
                    For Each e In events
                        Dim asEvent = TryCast(e, SourceEventSymbol)
                        If asEvent IsNot Nothing Then
                            Dim evSyntax = asEvent.SyntaxReference.GetSyntax
                            If evSyntax IsNot Nothing AndAlso evSyntax Is EventSyntax Then
                                _lazyEventSymbol = asEvent
                            End If
                        End If
                    Next
                End If

                Debug.Assert(_lazyEventSymbol IsNot Nothing, "We should have found our event here")
                Return _lazyEventSymbol
            End Get
        End Property

        ''' <summary>
        ''' This property may be called while containing type is still being constructed.
        ''' Therefore it can take membersInProgress context to ensure that returned symbol
        ''' is relevant to the current type construction.
        ''' (there could be several optimistically concurrent sessions)
        ''' </summary>
        Friend Overrides ReadOnly Property ImplicitlyDefinedBy(Optional membersInProgress As Dictionary(Of String, ArrayBuilder(Of Symbol)) = Nothing) As Symbol
            Get
                If membersInProgress Is Nothing Then
                    Return AssociatedSymbol
                End If

                Dim candidates = membersInProgress(_eventName)
                Dim eventInCurrentContext As SourceEventSymbol = Nothing

                Debug.Assert(candidates IsNot Nothing, "where is my event?")
                If candidates IsNot Nothing Then
                    For Each e In candidates
                        Dim asEvent = TryCast(e, SourceEventSymbol)
                        If asEvent IsNot Nothing Then
                            Dim evSyntax = asEvent.SyntaxReference.GetSyntax
                            If evSyntax IsNot Nothing AndAlso evSyntax Is EventSyntax Then
                                eventInCurrentContext = asEvent
                            End If
                        End If
                    Next
                End If

                Return eventInCurrentContext
            End Get
        End Property

        Public Overrides ReadOnly Property Arity As Integer
            Get
                Return 0
            End Get
        End Property

        Public Overrides ReadOnly Property ContainingSymbol As Symbol
            Get
                Return _containingType
            End Get
        End Property

        Public Overrides ReadOnly Property ContainingType As NamedTypeSymbol
            Get
                Return _containingType
            End Get
        End Property

        Public Overrides ReadOnly Property DeclaredAccessibility As Accessibility
            Get
                Return AssociatedSymbol.DeclaredAccessibility
            End Get
        End Property

        Friend Overrides ReadOnly Property DefaultPropertyName As String
            Get
                Return Nothing
            End Get
        End Property

        Public Overloads Overrides Function GetTypeMembers() As ImmutableArray(Of NamedTypeSymbol)
            Return ImmutableArray(Of NamedTypeSymbol).Empty
        End Function

        Public Overloads Overrides Function GetTypeMembers(name As String) As ImmutableArray(Of NamedTypeSymbol)
            Return ImmutableArray(Of NamedTypeSymbol).Empty
        End Function

        Public Overloads Overrides Function GetTypeMembers(name As String, arity As Integer) As ImmutableArray(Of NamedTypeSymbol)
            Return ImmutableArray(Of NamedTypeSymbol).Empty
        End Function

        Public Overrides ReadOnly Property IsMustInherit As Boolean
            Get
                Return False
            End Get
        End Property

        Public Overrides ReadOnly Property IsNotInheritable As Boolean
            Get
                Return True
            End Get
        End Property

        Friend Overrides ReadOnly Property ShadowsExplicitly As Boolean
            Get
                Return AssociatedSymbol.ShadowsExplicitly
            End Get
        End Property

        Friend Overrides Function GetLexicalSortKey() As LexicalSortKey
            ' WARNING: this should not allocate memory!
            Return New LexicalSortKey(_syntaxRef, Me.DeclaringCompilation)
        End Function

        Public Overrides ReadOnly Property Locations As ImmutableArray(Of Location)
            Get
                Return ImmutableArray.Create(_syntaxRef.GetLocation())
            End Get
        End Property

        Public Overrides ReadOnly Property DeclaringSyntaxReferences As ImmutableArray(Of SyntaxReference)
            Get
                Return ImmutableArray(Of SyntaxReference).Empty
            End Get
        End Property

        Friend Overrides Function MakeAcyclicBaseType(diagnostics As DiagnosticBag) As NamedTypeSymbol
            Return MakeDeclaredBase(Nothing, diagnostics)
        End Function

        Friend Overrides Function MakeAcyclicInterfaces(diagnostics As DiagnosticBag) As ImmutableArray(Of NamedTypeSymbol)
            Return ImmutableArray(Of NamedTypeSymbol).Empty
        End Function

        Friend Overrides Function MakeDeclaredBase(basesBeingResolved As BasesBeingResolved, diagnostics As DiagnosticBag) As NamedTypeSymbol
            Return _containingType.ContainingAssembly.GetSpecialType(Microsoft.CodeAnalysis.SpecialType.System_MulticastDelegate)
        End Function

        Friend Overrides Function MakeDeclaredInterfaces(basesBeingResolved As BasesBeingResolved, diagnostics As DiagnosticBag) As ImmutableArray(Of NamedTypeSymbol)
            Return ImmutableArray(Of NamedTypeSymbol).Empty
        End Function

        Friend Overrides ReadOnly Property MangleName As Boolean
            Get
                Return False
            End Get
        End Property

        Public Overrides ReadOnly Property MemberNames As System.Collections.Generic.IEnumerable(Of String)
            Get
                Return New HashSet(Of String)(From member In GetMembers() Select member.Name)
            End Get
        End Property

        Public Overrides ReadOnly Property MightContainExtensionMethods As Boolean
            Get
                Return False
            End Get
        End Property

        Friend Overrides ReadOnly Property HasCodeAnalysisEmbeddedAttribute As Boolean
            Get
                Return False
            End Get
        End Property

        Friend Overrides ReadOnly Property HasVisualBasicEmbeddedAttribute As Boolean
            Get
                Return False
            End Get
        End Property

        Friend Overrides ReadOnly Property IsExtensibleInterfaceNoUseSiteDiagnostics As Boolean
            Get
                Return False
            End Get
        End Property

        Friend Overrides ReadOnly Property IsWindowsRuntimeImport As Boolean
            Get
                Return False
            End Get
        End Property

        Friend Overrides ReadOnly Property ShouldAddWinRTMembers As Boolean
            Get
                Return False
            End Get
        End Property

        Friend Overrides ReadOnly Property IsComImport As Boolean
            Get
                Return False
            End Get
        End Property

        Friend Overrides ReadOnly Property CoClassType As TypeSymbol
            Get
                Return Nothing
            End Get
        End Property

        Friend Overrides Function GetAppliedConditionalSymbols() As ImmutableArray(Of String)
            Return ImmutableArray(Of String).Empty
        End Function

        Friend Overrides Function GetAttributeUsageInfo() As AttributeUsageInfo
            Throw ExceptionUtilities.Unreachable
        End Function

        Friend Overrides ReadOnly Property HasDeclarativeSecurity As Boolean
            Get
                Return False
            End Get
        End Property

        Friend Overrides Function GetSecurityInformation() As IEnumerable(Of Microsoft.Cci.SecurityAttribute)
            Throw ExceptionUtilities.Unreachable
        End Function

        Public Overrides ReadOnly Property Name As String
            Get
                Return _name
            End Get
        End Property

        Friend Overrides ReadOnly Property HasSpecialName As Boolean
            Get
                Return False
            End Get
        End Property

        Public Overrides ReadOnly Property IsSerializable As Boolean
            Get
                Return False
            End Get
        End Property

        Friend Overrides ReadOnly Property Layout As TypeLayout
            Get
                Return Nothing
            End Get
        End Property

        Friend Overrides ReadOnly Property MarshallingCharSet As CharSet
            Get
                Return DefaultMarshallingCharSet
            End Get
        End Property

        Public Overrides ReadOnly Property TypeKind As TYPEKIND
            Get
                Return TypeKind.Delegate
            End Get
        End Property

        Friend Overrides ReadOnly Property IsInterface As Boolean
            Get
                Return False
            End Get
        End Property

        Public Overrides ReadOnly Property TypeParameters As ImmutableArray(Of TypeParameterSymbol)
            Get
                Return ImmutableArray(Of TypeParameterSymbol).Empty
            End Get
        End Property

        ''' <summary>
        ''' Force all declaration errors to be generated.
        ''' </summary>
        Friend Overrides Sub GenerateDeclarationErrors(cancellationToken As CancellationToken)
            If _reportedAllDeclarationErrors <> 0 Then
                Return
            End If

            GetMembers()

            cancellationToken.ThrowIfCancellationRequested()

            Dim diagnostics As DiagnosticBag = DiagnosticBag.GetInstance()

            ' Force parameters and return value of Invoke method to be bound and errors reported.
            ' Parameters on other delegate methods are derived from Invoke so we don't need to call those.
            Me.DelegateInvokeMethod.GenerateDeclarationErrors(cancellationToken)

            Dim container = _containingType
            Dim outermostVariantInterface As NamedTypeSymbol = Nothing

            Do
                If Not container.IsInterfaceType() Then
                    Debug.Assert(Not container.IsDelegateType())
                    ' Non-interface, non-delegate containers are illegal within variant interfaces.
                    ' An error on the container will be sufficient if we haven't run into an interface already.
                    Exit Do
                End If

                If container.TypeParameters.HaveVariance() Then
                    ' We are inside of a variant interface
                    outermostVariantInterface = container
                End If

                container = container.ContainingType
            Loop While container IsNot Nothing

            If outermostVariantInterface IsNot Nothing Then
                ' "Event definitions with parameters are not allowed in an interface such as '|1' that has
                ' 'In' or 'Out' type parameters. Consider declaring the event by using a delegate type which
                ' is not defined within '|1'. For example, 'Event |2 As Action(Of ...)'."
                diagnostics.Add(New VBDiagnostic(ErrorFactory.ErrorInfo(ERRID.ERR_VariancePreventsSynthesizedEvents2,
                                                                      CustomSymbolDisplayFormatter.QualifiedName(outermostVariantInterface),
                                                                      AssociatedSymbol.Name),
                                                Locations(0)))
            End If

            DirectCast(ContainingModule, SourceModuleSymbol).AtomicStoreIntegerAndDiagnostics(_reportedAllDeclarationErrors, 1, 0, diagnostics, CompilationStage.Declare)

            diagnostics.Free()
        End Sub

        Public Overrides ReadOnly Property IsImplicitlyDeclared As Boolean
            Get
                ' these are always implicitly declared.
                Return True
            End Get
        End Property

        Friend Overrides ReadOnly Property EmbeddedSymbolKind As EmbeddedSymbolKind
            Get
                Return Me.ContainingType.EmbeddedSymbolKind
            End Get
        End Property

        Friend Overrides ReadOnly Property ObsoleteAttributeData As ObsoleteAttributeData
            Get
                Return Nothing
            End Get
        End Property

        Friend Overrides Function GetSynthesizedWithEventsOverrides() As IEnumerable(Of PropertySymbol)
            Return SpecializedCollections.EmptyEnumerable(Of PropertySymbol)()
        End Function
    End Class
End Namespace

