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

#nullable disable

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Emit;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp
{
    internal abstract partial class
#if DEBUG
        SymbolAdapter
#else
        Symbol
#endif 
        : Cci.IReference
    {
        Cci.IDefinition Cci.IReference.AsDefinition(EmitContext context)
        {
            throw ExceptionUtilities.Unreachable;
        }

        CodeAnalysis.Symbols.ISymbolInternal Cci.IReference.GetInternalSymbol() => AdaptedSymbol;

        void Cci.IReference.Dispatch(Cci.MetadataVisitor visitor)
        {
            throw ExceptionUtilities.Unreachable;
        }

        IEnumerable<Cci.ICustomAttribute> Cci.IReference.GetAttributes(EmitContext context)
        {
            return AdaptedSymbol.GetCustomAttributesToEmit((PEModuleBuilder)context.Module);
        }
    }

    internal partial class Symbol
    {
#if DEBUG
        internal SymbolAdapter GetCciAdapter() => GetCciAdapterImpl();
        protected virtual SymbolAdapter GetCciAdapterImpl() => throw ExceptionUtilities.Unreachable;
#else
        internal Symbol AdaptedSymbol => this;
        internal Symbol GetCciAdapter() => this;
#endif 

        /// <summary>
        /// Checks if this symbol is a definition and its containing module is a SourceModuleSymbol.
        /// </summary>
        [Conditional("DEBUG")]
        protected internal void CheckDefinitionInvariant()
        {
            // can't be generic instantiation
            Debug.Assert(this.IsDefinition);

            // must be declared in the module we are building
            Debug.Assert(this.ContainingModule is SourceModuleSymbol ||
                         (this.Kind == SymbolKind.Assembly && this is SourceAssemblySymbol) ||
                         (this.Kind == SymbolKind.NetModule && this is SourceModuleSymbol));
        }

        Cci.IReference CodeAnalysis.Symbols.ISymbolInternal.GetCciAdapter() => GetCciAdapter();

        /// <summary>
        /// Return whether the symbol is either the original definition
        /// or distinct from the original. Intended for use in Debug.Assert
        /// only since it may include a deep comparison.
        /// </summary>
        internal bool IsDefinitionOrDistinct()
        {
            return this.IsDefinition || !this.Equals(this.OriginalDefinition, SymbolEqualityComparer.ConsiderEverything.CompareKind);
        }

        internal virtual IEnumerable<CSharpAttributeData> GetCustomAttributesToEmit(PEModuleBuilder moduleBuilder)
        {
            CheckDefinitionInvariant();

            Debug.Assert(this.Kind != SymbolKind.Assembly);
            return GetCustomAttributesToEmit(moduleBuilder, emittingAssemblyAttributesInNetModule: false);
        }

        internal IEnumerable<CSharpAttributeData> GetCustomAttributesToEmit(PEModuleBuilder moduleBuilder, bool emittingAssemblyAttributesInNetModule)
        {
            CheckDefinitionInvariant();
            Debug.Assert(this.Kind != SymbolKind.Assembly);

            ImmutableArray<CSharpAttributeData> userDefined;
            ArrayBuilder<SynthesizedAttributeData> synthesized = null;
            userDefined = this.GetAttributes();
            this.AddSynthesizedAttributes(moduleBuilder, ref synthesized);

            // Note that callers of this method (CCI and ReflectionEmitter) have to enumerate 
            // all items of the returned iterator, otherwise the synthesized ArrayBuilder may leak.
            return GetCustomAttributesToEmit(userDefined, synthesized, isReturnType: false, emittingAssemblyAttributesInNetModule: emittingAssemblyAttributesInNetModule);
        }

        /// <summary>
        /// Returns a list of attributes to emit to CustomAttribute table.
        /// The <paramref name="synthesized"/> builder is freed after all its items are enumerated.
        /// </summary>
        internal IEnumerable<CSharpAttributeData> GetCustomAttributesToEmit(
            ImmutableArray<CSharpAttributeData> userDefined,
            ArrayBuilder<SynthesizedAttributeData> synthesized,
            bool isReturnType,
            bool emittingAssemblyAttributesInNetModule)
        {
            CheckDefinitionInvariant();

            //PERF: Avoid creating an iterator for the common case of no attributes.
            if (userDefined.IsEmpty && synthesized == null)
            {
                return SpecializedCollections.EmptyEnumerable<CSharpAttributeData>();
            }

            return GetCustomAttributesToEmitIterator(userDefined, synthesized, isReturnType, emittingAssemblyAttributesInNetModule);
        }

        private IEnumerable<CSharpAttributeData> GetCustomAttributesToEmitIterator(
            ImmutableArray<CSharpAttributeData> userDefined,
            ArrayBuilder<SynthesizedAttributeData> synthesized,
            bool isReturnType,
            bool emittingAssemblyAttributesInNetModule)
        {
            CheckDefinitionInvariant();

            if (synthesized != null)
            {
                foreach (var attribute in synthesized)
                {
                    // only synthesize attributes that are emitted:
                    Debug.Assert(attribute.ShouldEmitAttribute(this, isReturnType, emittingAssemblyAttributesInNetModule));
                    yield return attribute;
                }

                synthesized.Free();
            }

            for (int i = 0; i < userDefined.Length; i++)
            {
                CSharpAttributeData attribute = userDefined[i];
                if (this.Kind == SymbolKind.Assembly)
                {
                    // We need to filter out duplicate assembly attributes (i.e. attributes that
                    // bind to the same constructor and have identical arguments) and invalid
                    // InternalsVisibleTo attributes.
                    if (((SourceAssemblySymbol)this).IsIndexOfOmittedAssemblyAttribute(i))
                    {
                        continue;
                    }
                }

                if (attribute.ShouldEmitAttribute(this, isReturnType, emittingAssemblyAttributesInNetModule))
                {
                    yield return attribute;
                }
            }
        }
    }

#if DEBUG
    internal partial class SymbolAdapter
    {
        internal abstract Symbol AdaptedSymbol { get; }

        public sealed override string ToString()
        {
            return AdaptedSymbol.ToString();
        }

        public sealed override bool Equals(object obj)
        {
            // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used.
            throw Roslyn.Utilities.ExceptionUtilities.Unreachable;
        }

        public sealed override int GetHashCode()
        {
            // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used.
            throw Roslyn.Utilities.ExceptionUtilities.Unreachable;
        }

        [Conditional("DEBUG")]
        protected internal void CheckDefinitionInvariant() => AdaptedSymbol.CheckDefinitionInvariant();

        internal bool IsDefinitionOrDistinct()
        {
            return AdaptedSymbol.IsDefinitionOrDistinct();
        }
    }
#endif
}
