// 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.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.Cci;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
    internal sealed partial class FunctionPointerTypeSymbol : TypeSymbol
    {
        public static FunctionPointerTypeSymbol CreateFromSource(FunctionPointerTypeSyntax syntax, Binder typeBinder, BindingDiagnosticBag diagnostics, ConsList<TypeSymbol> basesBeingResolved, bool suppressUseSiteDiagnostics)
            => new FunctionPointerTypeSymbol(
                FunctionPointerMethodSymbol.CreateFromSource(
                    syntax,
                    typeBinder,
                    diagnostics,
                    basesBeingResolved,
                    suppressUseSiteDiagnostics));

        /// <summary>
        /// Creates a function pointer from individual parts. This method should only be used when diagnostics are not needed. This is
        /// intended for use in test code.
        /// </summary>
        public static FunctionPointerTypeSymbol CreateFromPartsForTests(
            CallingConvention callingConvention,
            TypeWithAnnotations returnType,
            ImmutableArray<CustomModifier> refCustomModifiers,
            RefKind returnRefKind,
            ImmutableArray<TypeWithAnnotations> parameterTypes,
            ImmutableArray<ImmutableArray<CustomModifier>> parameterRefCustomModifiers,
            ImmutableArray<RefKind> parameterRefKinds,
            CSharpCompilation compilation)
            => new FunctionPointerTypeSymbol(FunctionPointerMethodSymbol.CreateFromPartsForTest(callingConvention, returnType, refCustomModifiers, returnRefKind, parameterTypes, parameterRefCustomModifiers, parameterRefKinds, compilation));

        /// <summary>
        /// Creates a function pointer from individual parts. This method should only be used when diagnostics are not needed.
        /// </summary>
        public static FunctionPointerTypeSymbol CreateFromParts(
            CallingConvention callingConvention,
            ImmutableArray<CustomModifier> callingConventionModifiers,
            TypeWithAnnotations returnType,
            RefKind returnRefKind,
            ImmutableArray<TypeWithAnnotations> parameterTypes,
            ImmutableArray<RefKind> parameterRefKinds,
            CSharpCompilation compilation)
            => new FunctionPointerTypeSymbol(FunctionPointerMethodSymbol.CreateFromParts(callingConvention, callingConventionModifiers, returnType, returnRefKind, parameterTypes, parameterRefKinds, compilation));

        public static FunctionPointerTypeSymbol CreateFromMetadata(ModuleSymbol containingModule, Cci.CallingConvention callingConvention, ImmutableArray<ParamInfo<TypeSymbol>> retAndParamTypes)
            => new FunctionPointerTypeSymbol(
                FunctionPointerMethodSymbol.CreateFromMetadata(containingModule, callingConvention, retAndParamTypes));

        public FunctionPointerTypeSymbol SubstituteTypeSymbol(
            TypeWithAnnotations substitutedReturnType,
            ImmutableArray<TypeWithAnnotations> substitutedParameterTypes,
            ImmutableArray<CustomModifier> refCustomModifiers,
            ImmutableArray<ImmutableArray<CustomModifier>> paramRefCustomModifiers)
            => new FunctionPointerTypeSymbol(Signature.SubstituteParameterSymbols(substitutedReturnType, substitutedParameterTypes, refCustomModifiers, paramRefCustomModifiers));

        private FunctionPointerTypeSymbol(FunctionPointerMethodSymbol signature)
        {
            Signature = signature;
        }

        public FunctionPointerMethodSymbol Signature { get; }

        public override bool IsReferenceType => false;
        public override bool IsValueType => true;
        public override TypeKind TypeKind => TypeKind.FunctionPointer;
        public override bool IsRefLikeType => false;
        public override bool IsReadOnly => false;
        public override SymbolKind Kind => SymbolKind.FunctionPointerType;
        public override Symbol? ContainingSymbol => null;
        public override ImmutableArray<Location> Locations => ImmutableArray<Location>.Empty;
        public override ImmutableArray<SyntaxReference> DeclaringSyntaxReferences => ImmutableArray<SyntaxReference>.Empty;
        public override Accessibility DeclaredAccessibility => Accessibility.NotApplicable;
        public override bool IsStatic => false;
        public override bool IsAbstract => false;
        public override bool IsSealed => false;
        // Pointers do not support boxing, so they really have no base type.
        internal override NamedTypeSymbol? BaseTypeNoUseSiteDiagnostics => null;
        internal override ManagedKind GetManagedKind(ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo) => ManagedKind.Unmanaged;
        internal override ObsoleteAttributeData? ObsoleteAttributeData => null;
        public override void Accept(CSharpSymbolVisitor visitor) => visitor.VisitFunctionPointerType(this);
        public override TResult Accept<TResult>(CSharpSymbolVisitor<TResult> visitor) => visitor.VisitFunctionPointerType(this);
        public override ImmutableArray<Symbol> GetMembers() => ImmutableArray<Symbol>.Empty;
        public override ImmutableArray<Symbol> GetMembers(string name) => ImmutableArray<Symbol>.Empty;
        public override ImmutableArray<NamedTypeSymbol> GetTypeMembers() => ImmutableArray<NamedTypeSymbol>.Empty;
        public override ImmutableArray<NamedTypeSymbol> GetTypeMembers(ReadOnlyMemory<char> name) => ImmutableArray<NamedTypeSymbol>.Empty;
        internal override TResult Accept<TArgument, TResult>(CSharpSymbolVisitor<TArgument, TResult> visitor, TArgument a) => visitor.VisitFunctionPointerType(this, a);
        internal override ImmutableArray<NamedTypeSymbol> InterfacesNoUseSiteDiagnostics(ConsList<TypeSymbol>? basesBeingResolved = null) => ImmutableArray<NamedTypeSymbol>.Empty;

        internal override bool Equals(TypeSymbol t2, TypeCompareKind compareKind)
        {
            if (ReferenceEquals(this, t2))
            {
                return true;
            }

            if (!(t2 is FunctionPointerTypeSymbol other))
            {
                return false;
            }

            return Signature.Equals(other.Signature, compareKind);
        }

        public override int GetHashCode()
        {
            return Hash.Combine(1, Signature.GetHashCode());
        }

        protected override ISymbol CreateISymbol()
        {
            return new PublicModel.FunctionPointerTypeSymbol(this, DefaultNullableAnnotation);
        }

        protected override ITypeSymbol CreateITypeSymbol(CodeAnalysis.NullableAnnotation nullableAnnotation)
        {
            Debug.Assert(nullableAnnotation != DefaultNullableAnnotation);
            return new PublicModel.FunctionPointerTypeSymbol(this, nullableAnnotation);
        }

        internal override void AddNullableTransforms(ArrayBuilder<byte> transforms)
        {
            Signature.AddNullableTransforms(transforms);
        }

        internal override bool ApplyNullableTransforms(byte defaultTransformFlag, ImmutableArray<byte> transforms, ref int position, out TypeSymbol result)
        {
            var newSignature = Signature.ApplyNullableTransforms(defaultTransformFlag, transforms, ref position);
            bool madeChanges = (object)Signature != newSignature;
            result = madeChanges ? new FunctionPointerTypeSymbol(newSignature) : this;
            return madeChanges;
        }

        internal override UseSiteInfo<AssemblySymbol> GetUseSiteInfo()
        {
            UseSiteInfo<AssemblySymbol> fromSignature = Signature.GetUseSiteInfo();

            if (fromSignature.DiagnosticInfo?.Code == (int)ErrorCode.ERR_BindToBogus && fromSignature.DiagnosticInfo.Arguments.AsSingleton() == (object)Signature)
            {
                return new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.ERR_BogusType, this));
            }

            return fromSignature;
        }

        internal override bool GetUnificationUseSiteDiagnosticRecursive(ref DiagnosticInfo? result, Symbol owner, ref HashSet<TypeSymbol> checkedTypes)
        {
            return Signature.GetUnificationUseSiteDiagnosticRecursive(ref result, owner, ref checkedTypes);
        }

        internal override TypeSymbol MergeEquivalentTypes(TypeSymbol other, VarianceKind variance)
        {
            Debug.Assert(this.Equals(other, TypeCompareKind.AllIgnoreOptions));
            var mergedSignature = Signature.MergeEquivalentTypes(((FunctionPointerTypeSymbol)other).Signature, variance);
            if ((object)mergedSignature != Signature)
            {
                return new FunctionPointerTypeSymbol(mergedSignature);
            }

            return this;
        }

        internal override TypeSymbol SetNullabilityForReferenceTypes(Func<TypeWithAnnotations, TypeWithAnnotations> transform)
        {
            var substitutedSignature = Signature.SetNullabilityForReferenceTypes(transform);
            if ((object)Signature != substitutedSignature)
            {
                return new FunctionPointerTypeSymbol(substitutedSignature);
            }

            return this;
        }

        /// <summary>
        /// For scenarios such as overriding with differing ref kinds (such as out vs in or ref)
        /// we need to compare function pointer parameters assuming that Ref matches RefReadonly/In
        /// and Out. This is done because you cannot overload on ref vs out vs in in regular method
        /// signatures, and we are disallowing similar overloads in source with function pointers.
        /// </summary>
        internal static bool RefKindEquals(TypeCompareKind compareKind, RefKind refKind1, RefKind refKind2)
            => (compareKind & TypeCompareKind.FunctionPointerRefOutInRefReadonlyMatch) != 0
               ? (refKind1 == RefKind.None) == (refKind2 == RefKind.None)
               : refKind1 == refKind2;

        /// <summary>
        /// For scenarios such as overriding with differing ref kinds (such as out vs in or ref)
        /// we need to compare function pointer parameters assuming that Ref matches RefReadonly/In
        /// and Out. For that reason, we must also ensure that GetHashCode returns equal hashcodes
        /// for types that only differ by the type of ref they have.
        /// </summary>
        internal static RefKind GetRefKindForHashCode(RefKind refKind)
            => refKind == RefKind.None ? RefKind.None : RefKind.Ref;

        /// <summary>
        /// Return true if the given type is valid as a calling convention modifier type.
        /// </summary>
        internal static bool IsCallingConventionModifier(NamedTypeSymbol modifierType)
        {
            Debug.Assert(modifierType.ContainingAssembly is not null || modifierType.IsErrorType());
            return (object?)modifierType.ContainingAssembly == modifierType.ContainingAssembly?.CorLibrary
                   && modifierType.Arity == 0
                   && modifierType.Name != "CallConv"
                   && modifierType.Name.StartsWith("CallConv", StringComparison.Ordinal)
                   && modifierType.IsCompilerServicesTopLevelType();
        }

        internal override bool IsRecord => false;

        internal override bool IsRecordStruct => false;

        internal override IEnumerable<(MethodSymbol Body, MethodSymbol Implemented)> SynthesizedInterfaceMethodImpls()
        {
            return SpecializedCollections.EmptyEnumerable<(MethodSymbol Body, MethodSymbol Implemented)>();
        }

        internal override bool HasInlineArrayAttribute(out int length)
        {
            length = 0;
            return false;
        }
    }
}
