' 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.Collections.ObjectModel
Imports System.Runtime.InteropServices
Imports System.Threading
Imports Microsoft.CodeAnalysis.CodeGen
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Binder
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports TypeKind = Microsoft.CodeAnalysis.TypeKind

Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols
    Friend MustInherit Class SourceParameterSymbol
        Inherits SourceParameterSymbolBase
        Implements IAttributeTargetSymbol

        Private ReadOnly _location As Location
        Private ReadOnly _name As String
        Private ReadOnly _type As TypeSymbol

        Friend Sub New(
            container As Symbol,
            name As String,
            ordinal As Integer,
            type As TypeSymbol,
            location As Location)
            MyBase.New(container, ordinal)

            _name = name
            _type = type
            _location = location
        End Sub

        Friend ReadOnly Property Location As Location
            Get
                Return _location
            End Get
        End Property

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

        Friend NotOverridable Overrides ReadOnly Property HasOptionCompare As Boolean
            Get
                Return False
            End Get
        End Property

        Friend Overrides ReadOnly Property IsIDispatchConstant As Boolean
            Get
                ' Ideally we should look for IDispatchConstantAttribute on this source parameter symbol,
                ' but the native VB compiler respects this attribute only on metadata parameter symbols, we do the same.
                ' See Devdiv bug #10789 (Handle special processing of object type without a default value per VB Language Spec 11.8.2 Applicable Methods) for details.
                Return False
            End Get
        End Property

        Friend Overrides ReadOnly Property IsIUnknownConstant As Boolean
            Get
                ' Ideally we should look for IUnknownConstantAttribute on this source parameter symbol,
                ' but the native VB compiler respects this attribute only on metadata parameter symbols, we do the same.
                ' See Devdiv bug #10789 (Handle special processing of object type without a default value per VB Language Spec 11.8.2 Applicable Methods) for details.
                Return False
            End Get
        End Property

        Public NotOverridable Overrides ReadOnly Property Locations As ImmutableArray(Of Location)
            Get
                If _location IsNot Nothing Then
                    Return ImmutableArray.Create(Of Location)(_location)
                Else
                    Return ImmutableArray(Of Location).Empty
                End If
            End Get
        End Property

        Public NotOverridable Overrides ReadOnly Property Type As TypeSymbol
            Get
                Return _type
            End Get
        End Property

        Public MustOverride Overrides ReadOnly Property CustomModifiers As ImmutableArray(Of CustomModifier)

        Public MustOverride Overrides ReadOnly Property RefCustomModifiers As ImmutableArray(Of CustomModifier)

        Public Overrides ReadOnly Property DeclaringSyntaxReferences As ImmutableArray(Of SyntaxReference)
            Get
                If IsImplicitlyDeclared Then
                    Return ImmutableArray(Of SyntaxReference).Empty
                Else
                    Return GetDeclaringSyntaxReferenceHelper(Of ParameterSyntax)(Me.Locations)
                End If
            End Get
        End Property


        Public NotOverridable Overrides ReadOnly Property IsImplicitlyDeclared As Boolean
            Get
                If Me.ContainingSymbol.IsImplicitlyDeclared Then

                    If If(TryCast(Me.ContainingSymbol, MethodSymbol)?.MethodKind = MethodKind.DelegateInvoke, False) AndAlso
                       Not Me.ContainingType.AssociatedSymbol?.IsImplicitlyDeclared Then
                        Return False
                    End If

                    Return True
                End If

                Return (GetMatchingPropertyParameter() IsNot Nothing)
            End Get
        End Property

        ''' <summary>
        ''' Is this an accessor parameter that came from the associated property? If so, 
        ''' return it, else return Nothing.
        ''' </summary>
        Private Function GetMatchingPropertyParameter() As ParameterSymbol
            Dim containingMethod = TryCast(ContainingSymbol, MethodSymbol)
            If containingMethod IsNot Nothing AndAlso containingMethod.IsAccessor() Then
                Dim containingProperty = TryCast(containingMethod.AssociatedSymbol, PropertySymbol)
                If containingProperty IsNot Nothing AndAlso Ordinal < containingProperty.ParameterCount Then
                    ' We match a parameter on our containing property.
                    Return containingProperty.Parameters(Ordinal)
                End If
            End If

            Return Nothing
        End Function

#Region "Attributes"

        ' Attributes on corresponding parameters of partial methods are merged. 
        ' We always create a complex parameter for a partial definition to store potential merged attributes there.
        ' At the creation time we don't know if the corresponding partial implementation has attributes so we need to always assume it might.
        ' 
        ' Unlike in C#, where both partial definition and partial implementation have partial syntax and 
        ' hence we create a complex parameter for both of them, in VB partial implementation Sub syntax 
        ' is no different from non-partial Sub. Therefore we can't determine at the creation time whether 
        ' a parameter of a Sub that is not a partial definition might need to store attributes or not.
        '
        ' We therefore need to virtualize the storage for attribute data. Simple parameter of a partial implementation
        ' uses attribute storage of the corresponding partial definition parameter.
        ' 
        ' When an implementation parameter is asked for attributes it gets them from the definition parameter:
        ' 1) If the implementation is a simple parameter it calls GetAttributeBag on the definition.
        ' 2) If it is a complex parameter it copies the data from the definition using BoundAttributesSource.

        Friend MustOverride Function GetAttributesBag() As CustomAttributesBag(Of VisualBasicAttributeData)
        Friend MustOverride Function GetEarlyDecodedWellKnownAttributeData() As ParameterEarlyWellKnownAttributeData
        Friend MustOverride Function GetDecodedWellKnownAttributeData() As CommonParameterWellKnownAttributeData
        Friend MustOverride ReadOnly Property AttributeDeclarationList As SyntaxList(Of AttributeListSyntax)

        Friend NotOverridable Overrides ReadOnly Property HasParamArrayAttribute As Boolean
            Get
                Dim data = GetEarlyDecodedWellKnownAttributeData()
                Return data IsNot Nothing AndAlso data.HasParamArrayAttribute
            End Get
        End Property

        Friend NotOverridable Overrides ReadOnly Property HasDefaultValueAttribute As Boolean
            Get
                Dim data = GetEarlyDecodedWellKnownAttributeData()
                Return data IsNot Nothing AndAlso data.DefaultParameterValue <> ConstantValue.Unset
            End Get
        End Property

        Public ReadOnly Property DefaultAttributeLocation As AttributeLocation Implements IAttributeTargetSymbol.DefaultAttributeLocation
            Get
                Return AttributeLocation.Parameter
            End Get
        End Property

        ''' <summary>
        ''' Gets the attributes applied on this symbol.
        ''' Returns an empty array if there are no attributes.
        ''' </summary>
        Public NotOverridable Overrides Function GetAttributes() As ImmutableArray(Of VisualBasicAttributeData)
            Return Me.GetAttributesBag().Attributes
        End Function

        Friend Overrides Function EarlyDecodeWellKnownAttribute(ByRef arguments As EarlyDecodeWellKnownAttributeArguments(Of EarlyWellKnownAttributeBinder, NamedTypeSymbol, AttributeSyntax, AttributeLocation)) As VisualBasicAttributeData
            ' Declare methods need to know early what marshalling type are their parameters of to determine ByRef-ness.
            Dim containingSymbol = Me.ContainingSymbol
            If containingSymbol.Kind = SymbolKind.Method AndAlso DirectCast(containingSymbol, MethodSymbol).MethodKind = MethodKind.DeclareMethod Then
                If VisualBasicAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.MarshalAsAttribute) Then
                    Dim hasAnyDiagnostics As Boolean = False

                    Dim attrdata = arguments.Binder.GetAttribute(arguments.AttributeSyntax, arguments.AttributeType, hasAnyDiagnostics)
                    If Not attrdata.HasErrors Then
                        arguments.GetOrCreateData(Of ParameterEarlyWellKnownAttributeData).HasMarshalAsAttribute = True
                        Return If(Not hasAnyDiagnostics, attrdata, Nothing)
                    Else
                        Return Nothing
                    End If
                End If
            End If

            Dim possibleValidParamArrayTarget As Boolean = False

            Select Case containingSymbol.Kind
                Case SymbolKind.Property
                    possibleValidParamArrayTarget = True

                Case SymbolKind.Method
                    Select Case DirectCast(containingSymbol, MethodSymbol).MethodKind
                        Case MethodKind.Conversion,
                             MethodKind.UserDefinedOperator,
                             MethodKind.EventAdd,
                             MethodKind.EventRemove
                            Debug.Assert(Not possibleValidParamArrayTarget)

                        Case Else
                            possibleValidParamArrayTarget = True

                    End Select
            End Select

            If possibleValidParamArrayTarget AndAlso
               VisualBasicAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.ParamArrayAttribute) Then
                Dim hasAnyDiagnostics As Boolean = False

                Dim attrdata = arguments.Binder.GetAttribute(arguments.AttributeSyntax, arguments.AttributeType, hasAnyDiagnostics)
                If Not attrdata.HasErrors Then
                    arguments.GetOrCreateData(Of ParameterEarlyWellKnownAttributeData).HasParamArrayAttribute = True
                    Return If(Not hasAnyDiagnostics, attrdata, Nothing)
                Else
                    Return Nothing
                End If
            End If

            If VisualBasicAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.DefaultParameterValueAttribute) Then
                Return EarlyDecodeAttributeForDefaultParameterValue(AttributeDescription.DefaultParameterValueAttribute, arguments)
            ElseIf VisualBasicAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.DecimalConstantAttribute) Then
                Return EarlyDecodeAttributeForDefaultParameterValue(AttributeDescription.DecimalConstantAttribute, arguments)
            ElseIf VisualBasicAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.DateTimeConstantAttribute) Then
                Return EarlyDecodeAttributeForDefaultParameterValue(AttributeDescription.DateTimeConstantAttribute, arguments)
            ElseIf VisualBasicAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.CallerLineNumberAttribute) Then
                arguments.GetOrCreateData(Of ParameterEarlyWellKnownAttributeData).HasCallerLineNumberAttribute = True
            ElseIf VisualBasicAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.CallerFilePathAttribute) Then
                arguments.GetOrCreateData(Of ParameterEarlyWellKnownAttributeData).HasCallerFilePathAttribute = True
            ElseIf VisualBasicAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.CallerMemberNameAttribute) Then
                arguments.GetOrCreateData(Of ParameterEarlyWellKnownAttributeData).HasCallerMemberNameAttribute = True
            End If

            Return MyBase.EarlyDecodeWellKnownAttribute(arguments)
        End Function

        ' It is not strictly necessary to decode default value attributes early in VB,
        ' but it is necessary in C#, so this keeps the implementations consistent.
        Private Function EarlyDecodeAttributeForDefaultParameterValue(description As AttributeDescription, ByRef arguments As EarlyDecodeWellKnownAttributeArguments(Of EarlyWellKnownAttributeBinder, NamedTypeSymbol, AttributeSyntax, AttributeLocation)) As VisualBasicAttributeData
            Debug.Assert(description.Equals(AttributeDescription.DefaultParameterValueAttribute) OrElse
                         description.Equals(AttributeDescription.DecimalConstantAttribute) OrElse
                         description.Equals(AttributeDescription.DateTimeConstantAttribute))

            Dim hasAnyDiagnostics = False
            Dim attribute = arguments.Binder.GetAttribute(arguments.AttributeSyntax, arguments.AttributeType, hasAnyDiagnostics)
            Dim value As ConstantValue

            If attribute.HasErrors Then
                value = ConstantValue.Bad
                hasAnyDiagnostics = True
            Else
                value = DecodeDefaultParameterValueAttribute(description, attribute)
            End If

            Dim paramData = arguments.GetOrCreateData(Of ParameterEarlyWellKnownAttributeData)()
            If paramData.DefaultParameterValue = ConstantValue.Unset Then
                paramData.DefaultParameterValue = value
            End If

            Return If(hasAnyDiagnostics, Nothing, attribute)
        End Function

        Friend Overrides Sub DecodeWellKnownAttribute(ByRef arguments As DecodeWellKnownAttributeArguments(Of AttributeSyntax, VisualBasicAttributeData, AttributeLocation))
            Dim attrData = arguments.Attribute
            Debug.Assert(Not attrData.HasErrors)
            Debug.Assert(arguments.SymbolPart = AttributeLocation.None)

            ' Differences from C#:
            '
            '   DefaultParameterValueAttribute
            '     - emitted as is, not treated as pseudo-custom attribute
            '     - checked (along with DecimalConstantAttribute and DateTimeConstantAttribute) for consistency with any explicit default value
            '
            '   OptionalAttribute 
            '     - Not used by the language, only syntactically optional parameters or metadata optional parameters are recognized by overload resolution.
            '       OptionalAttribute is checked for in emit phase.
            '
            '   ParamArrayAttribute
            '     - emitted as is, no error reported
            '     - Dev11 incorrectly emits the attribute twice
            '
            '  InAttribute, OutAttribute
            '     - metadata flag set, no diagnostics reported, don't influence language semantics

            If attrData.IsTargetAttribute(Me, AttributeDescription.TupleElementNamesAttribute) Then
                arguments.Diagnostics.Add(ERRID.ERR_ExplicitTupleElementNamesAttribute, arguments.AttributeSyntaxOpt.Location)
            End If

            If attrData.IsTargetAttribute(Me, AttributeDescription.DefaultParameterValueAttribute) Then
                ' Attribute decoded and constant value stored during EarlyDecodeWellKnownAttribute.
                DecodeDefaultParameterValueAttribute(AttributeDescription.DefaultParameterValueAttribute, arguments)
            ElseIf attrData.IsTargetAttribute(Me, AttributeDescription.DecimalConstantAttribute) Then
                ' Attribute decoded and constant value stored during EarlyDecodeWellKnownAttribute.
                DecodeDefaultParameterValueAttribute(AttributeDescription.DecimalConstantAttribute, arguments)
            ElseIf attrData.IsTargetAttribute(Me, AttributeDescription.DateTimeConstantAttribute) Then
                ' Attribute decoded and constant value stored during EarlyDecodeWellKnownAttribute.
                DecodeDefaultParameterValueAttribute(AttributeDescription.DateTimeConstantAttribute, arguments)
            ElseIf attrData.IsTargetAttribute(Me, AttributeDescription.InAttribute) Then
                arguments.GetOrCreateData(Of CommonParameterWellKnownAttributeData)().HasInAttribute = True
            ElseIf attrData.IsTargetAttribute(Me, AttributeDescription.OutAttribute) Then
                arguments.GetOrCreateData(Of CommonParameterWellKnownAttributeData)().HasOutAttribute = True
            ElseIf attrData.IsTargetAttribute(Me, AttributeDescription.MarshalAsAttribute) Then
                MarshalAsAttributeDecoder(Of CommonParameterWellKnownAttributeData, AttributeSyntax, VisualBasicAttributeData, AttributeLocation).Decode(arguments, AttributeTargets.Parameter, MessageProvider.Instance)
            End If

            MyBase.DecodeWellKnownAttribute(arguments)
        End Sub

        Private Sub DecodeDefaultParameterValueAttribute(description As AttributeDescription, ByRef arguments As DecodeWellKnownAttributeArguments(Of AttributeSyntax, VisualBasicAttributeData, AttributeLocation))
            Dim attribute = arguments.Attribute
            Dim diagnostics = arguments.Diagnostics

            Debug.Assert(arguments.AttributeSyntaxOpt IsNot Nothing)
            Debug.Assert(diagnostics IsNot Nothing)

            Dim value = DecodeDefaultParameterValueAttribute(description, attribute)
            If Not value.IsBad Then
                VerifyParamDefaultValueMatchesAttributeIfAny(value, arguments.AttributeSyntaxOpt, diagnostics)
            End If
        End Sub

        ''' <summary>
        ''' Verify the default value matches the default value from any earlier attribute
        ''' (DefaultParameterValueAttribute, DateTimeConstantAttribute or DecimalConstantAttribute).
        ''' If not, report ERR_ParamDefaultValueDiffersFromAttribute.
        ''' </summary>
        Protected Sub VerifyParamDefaultValueMatchesAttributeIfAny(value As ConstantValue, syntax As VisualBasicSyntaxNode, diagnostics As DiagnosticBag)
            Dim data = GetEarlyDecodedWellKnownAttributeData()
            If data IsNot Nothing Then
                Dim attrValue = data.DefaultParameterValue
                If attrValue <> ConstantValue.Unset AndAlso
                    value <> attrValue Then
                    Binder.ReportDiagnostic(diagnostics, syntax, ERRID.ERR_ParamDefaultValueDiffersFromAttribute)
                End If
            End If
        End Sub

        Private Function DecodeDefaultParameterValueAttribute(description As AttributeDescription, attribute As VisualBasicAttributeData) As ConstantValue
            Debug.Assert(Not attribute.HasErrors)

            If description.Equals(AttributeDescription.DefaultParameterValueAttribute) Then
                Return DecodeDefaultParameterValueAttribute(attribute)
            ElseIf description.Equals(AttributeDescription.DecimalConstantAttribute) Then
                Return attribute.DecodeDecimalConstantValue()
            Else
                Debug.Assert(description.Equals(AttributeDescription.DateTimeConstantAttribute))
                Return attribute.DecodeDateTimeConstantValue()
            End If
        End Function

        Private Function DecodeDefaultParameterValueAttribute(attribute As VisualBasicAttributeData) As ConstantValue
            Debug.Assert(attribute.CommonConstructorArguments.Length = 1)

            ' the type of the value is the type of the expression in the attribute
            Dim arg = attribute.CommonConstructorArguments(0)

            Dim specialType = If(arg.Kind = TypedConstantKind.Enum,
                                 DirectCast(arg.TypeInternal, NamedTypeSymbol).EnumUnderlyingType.SpecialType,
                                 arg.TypeInternal.SpecialType)
            Dim constantValueDiscriminator = ConstantValue.GetDiscriminator(specialType)

            If constantValueDiscriminator = ConstantValueTypeDiscriminator.Bad Then
                If arg.Kind <> TypedConstantKind.Array AndAlso
                    arg.ValueInternal Is Nothing AndAlso
                    Type.IsReferenceType Then
                    Return ConstantValue.Null
                End If

                Return ConstantValue.Bad
            End If

            Return ConstantValue.Create(arg.ValueInternal, constantValueDiscriminator)
        End Function

        Friend NotOverridable Overrides ReadOnly Property MarshallingInformation As MarshalPseudoCustomAttributeData
            Get
                Dim data = GetDecodedWellKnownAttributeData()
                If data IsNot Nothing Then
                    Return data.MarshallingInformation
                End If

                ' Default marshalling for string parameters of Declare methods (ByRef or ByVal)
                If Type.IsStringType() Then
                    Dim container As Symbol = ContainingSymbol

                    If container.Kind = SymbolKind.Method Then
                        Dim methodSymbol = DirectCast(container, MethodSymbol)
                        If methodSymbol.MethodKind = MethodKind.DeclareMethod Then
                            Dim info As New MarshalPseudoCustomAttributeData()

                            Debug.Assert(IsByRef)

                            If IsExplicitByRef Then
                                Dim pinvoke As DllImportData = methodSymbol.GetDllImportData()

                                Select Case pinvoke.CharacterSet
                                    Case Cci.Constants.CharSet_None, CharSet.Ansi
                                        info.SetMarshalAsSimpleType(Cci.Constants.UnmanagedType_AnsiBStr)

                                    Case Cci.Constants.CharSet_Auto
                                        info.SetMarshalAsSimpleType(Cci.Constants.UnmanagedType_TBStr)

                                    Case CharSet.Unicode
                                        info.SetMarshalAsSimpleType(UnmanagedType.BStr)

                                    Case Else
                                        Throw ExceptionUtilities.UnexpectedValue(pinvoke.CharacterSet)
                                End Select
                            Else
                                info.SetMarshalAsSimpleType(Cci.Constants.UnmanagedType_VBByRefStr)
                            End If

                            Return info
                        End If
                    End If
                End If

                Return Nothing
            End Get
        End Property

        Public NotOverridable Overrides ReadOnly Property IsByRef As Boolean
            Get
                If IsExplicitByRef Then
                    Return True
                End If

                ' String parameters of Declare methods without explicit MarshalAs attribute are always ByRef, even if they are declared ByVal.

                If Type.IsStringType() AndAlso
                   ContainingSymbol.Kind = SymbolKind.Method AndAlso
                   DirectCast(ContainingSymbol, MethodSymbol).MethodKind = MethodKind.DeclareMethod Then

                    Dim data = GetEarlyDecodedWellKnownAttributeData()
                    Return data Is Nothing OrElse Not data.HasMarshalAsAttribute
                End If

                Return False
            End Get
        End Property

        Friend NotOverridable Overrides ReadOnly Property IsMetadataOut As Boolean
            Get
                Dim data = GetDecodedWellKnownAttributeData()
                Return data IsNot Nothing AndAlso data.HasOutAttribute
            End Get
        End Property

        Friend NotOverridable Overrides ReadOnly Property IsMetadataIn As Boolean
            Get
                Dim data = GetDecodedWellKnownAttributeData()
                Return data IsNot Nothing AndAlso data.HasInAttribute
            End Get
        End Property
#End Region

    End Class

    Friend Enum SourceParameterFlags As Byte
        [ByVal] = 1 << 0
        [ByRef] = 1 << 1
        [Optional] = 1 << 2
        [ParamArray] = 1 << 3
    End Enum

End Namespace

