' 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 Microsoft.CodeAnalysis.PooledObjects

Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols

    ''' <summary> 
    ''' Describes anonymous type/delegate in terms of fields/parameters
    ''' </summary>
    Friend Structure AnonymousTypeDescriptor
        Implements IEquatable(Of AnonymousTypeDescriptor)

        Public Const SubReturnParameterName As String = "Sub"
        Public Const FunctionReturnParameterName As String = "Function"

        Friend Shared Function GetReturnParameterName(isFunction As Boolean) As String
            Return If(isFunction, FunctionReturnParameterName, SubReturnParameterName)
        End Function

        ''' <summary> Anonymous type/delegate location </summary>
        Public ReadOnly Location As Location

        ''' <summary> Anonymous type fields </summary>
        Public ReadOnly Fields As ImmutableArray(Of AnonymousTypeField)

        ''' <summary> 
        ''' Anonymous type descriptor Key 
        ''' 
        ''' The key is being used to separate anonymous type templates, for example in an anonymous type 
        ''' symbol cache. The type descriptors with the same keys are supposed to map to 'the same' anonymous
        ''' type template in terms of the same generic type being used for their implementation.
        ''' </summary>
        Public ReadOnly Key As String

        ''' <summary> Anonymous type is implicitly declared </summary>
        Public ReadOnly IsImplicitlyDeclared As Boolean

        ''' <summary> Anonymous delegate parameters, including one for return type </summary>
        Public ReadOnly Property Parameters As ImmutableArray(Of AnonymousTypeField)
            Get
                Return Fields
            End Get
        End Property

        Public Sub New(fields As ImmutableArray(Of AnonymousTypeField), location As Location, isImplicitlyDeclared As Boolean)
            Me.Fields = fields
            Me.Location = location
            Me.IsImplicitlyDeclared = isImplicitlyDeclared
            Me.Key = ComputeKey(fields, Function(f) f.Name, Function(f) f.IsKey)
        End Sub

        Friend Shared Function ComputeKey(Of T)(fields As ImmutableArray(Of T), getName As Func(Of T, String), getIsKey As Func(Of T, Boolean)) As String
            Dim pooledBuilder = PooledStringBuilder.GetInstance()
            Dim builder = pooledBuilder.Builder
            For Each field In fields
                builder.Append("|"c)
                builder.Append(getName(field))
                builder.Append(If(getIsKey(field), "+"c, "-"c))
            Next

            IdentifierComparison.ToLower(builder)
            Return pooledBuilder.ToStringAndFree()

        End Function

        ''' <summary>
        ''' This is ONLY used for debugging purpose
        ''' </summary>
        <Conditional("DEBUG")>
        Friend Sub AssertGood()
            ' Fields exist
            Debug.Assert(Not Fields.IsDefault)

            ' All fields are good
            For Each field In Fields
                field.AssertGood()
            Next
        End Sub

        Public Overloads Function Equals(other As AnonymousTypeDescriptor) As Boolean Implements IEquatable(Of AnonymousTypeDescriptor).Equals
            Return Equals(other, TypeCompareKind.ConsiderEverything)
        End Function

        Public Overloads Function Equals(other As AnonymousTypeDescriptor, compareKind As TypeCompareKind) As Boolean
            ' Comparing keys ensures field count, field names and keyness are equal
            If Not Me.Key.Equals(other.Key) Then
                Return False
            End If

            ' Compare field types
            Dim myFields As ImmutableArray(Of AnonymousTypeField) = Me.Fields
            Dim count As Integer = myFields.Length
            Dim otherFields As ImmutableArray(Of AnonymousTypeField) = other.Fields
            For i = 0 To count - 1
                If Not myFields(i).Type.Equals(otherFields(i).Type, compareKind) Then
                    Return False
                End If
            Next

            Return True
        End Function

        Public Overloads Overrides Function Equals(obj As Object) As Boolean
            Return TypeOf obj Is AnonymousTypeDescriptor AndAlso Equals(DirectCast(obj, AnonymousTypeDescriptor))
        End Function

        Public Overrides Function GetHashCode() As Integer
            Return Me.Key.GetHashCode()
        End Function

        ''' <summary>
        ''' Performs internal substitution of types in anonymous type descriptor fields and returns True 
        ''' if any of the fields was changed, in which case a new descriptor is returned in newDescriptor
        ''' </summary>
        Public Function SubstituteTypeParametersIfNeeded(substitution As TypeSubstitution, <Out> ByRef newDescriptor As AnonymousTypeDescriptor) As Boolean
            Dim fieldCount = Me.Fields.Length
            Dim newFields(fieldCount - 1) As AnonymousTypeField
            Dim anyChange As Boolean = False

            For i = 0 To fieldCount - 1
                Dim current As AnonymousTypeField = Me.Fields(i)
                newFields(i) = New AnonymousTypeField(current.Name,
                                                      current.Type.InternalSubstituteTypeParameters(substitution).Type,
                                                      current.Location,
                                                      current.IsKey)
                If Not anyChange Then
                    anyChange = current.Type IsNot newFields(i).Type
                End If
            Next

            If anyChange Then
                newDescriptor = New AnonymousTypeDescriptor(newFields.AsImmutableOrNull(), Me.Location, Me.IsImplicitlyDeclared)
            Else
                newDescriptor = Nothing
            End If

            Return anyChange
        End Function
    End Structure

    ''' <summary> 
    ''' Describes anonymous type field in terms of its name, type and other attributes.
    ''' Or describes anonymous delegate parameter, including "return" parameter, in terms 
    ''' of its name, type and other attributes.
    ''' </summary>
    Friend Structure AnonymousTypeField

        ''' <summary> Anonymous type field/parameter name, not nothing and not empty </summary>
        Public ReadOnly Name As String

        ''' <summary>Location of the field</summary>
        Public ReadOnly Location As Location

        ''' <summary> Anonymous type field/parameter type, must be not nothing when 
        ''' the field is passed to anonymous type descriptor </summary>
        Public ReadOnly Property Type As TypeSymbol
            Get
                Return Me._type
            End Get
        End Property

        ''' <summary> 
        ''' Anonymous type field/parameter type, may be nothing when field descriptor is created,
        ''' must be assigned before passing the descriptor to anonymous type descriptor.
        ''' Once assigned, is considered to be 'sealed'. 
        ''' </summary>
        Private _type As TypeSymbol

        ''' <summary> Anonymous type field is declared as a 'Key' field </summary>
        Public ReadOnly IsKey As Boolean

        ''' <summary>
        ''' Does this describe a ByRef parameter of an Anonymous Delegate type
        ''' </summary>
        Public ReadOnly Property IsByRef As Boolean
            Get
                Return IsKey
            End Get
        End Property

        Public Sub New(name As String, type As TypeSymbol, location As Location, Optional isKeyOrByRef As Boolean = False)
            Me.Name = If(String.IsNullOrWhiteSpace(name), "<Empty Name>", name)
            Me._type = type
            Me.IsKey = isKeyOrByRef
            Me.Location = location
        End Sub

        Public Sub New(name As String, location As Location, isKey As Boolean)
            Me.New(name, Nothing, location, isKey)
        End Sub

        ''' <summary>
        ''' This is ONLY used for debugging purpose
        ''' </summary>
        <Conditional("DEBUG")>
        Friend Sub AssertGood()
            Debug.Assert(Name IsNot Nothing AndAlso Me.Type IsNot Nothing AndAlso Me.Location IsNot Nothing)
        End Sub

        Friend Sub AssignFieldType(newType As TypeSymbol)
            Debug.Assert(newType IsNot Nothing)
            Debug.Assert(Me._type Is Nothing)
            Me._type = newType
        End Sub

    End Structure

End Namespace
