// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp
{
    // An invariant of a merged type declaration is that all of its children are also merged
    // declarations.
    [DebuggerDisplay("{GetDebuggerDisplay(), nq}")]
    internal sealed class MergedTypeDeclaration : MergedNamespaceOrTypeDeclaration
    {
        private readonly ImmutableArray<SingleTypeDeclaration> _declarations;
        private ImmutableArray<MergedTypeDeclaration> _lazyChildren;
        private ICollection<string> _lazyMemberNames;

        internal MergedTypeDeclaration(ImmutableArray<SingleTypeDeclaration> declarations)
            : base(declarations[0].Name)
        {
            _declarations = declarations;
        }

        public ImmutableArray<SingleTypeDeclaration> Declarations
        {
            get
            {
                return _declarations;
            }
        }

        public ImmutableArray<SyntaxReference> SyntaxReferences
        {
            get
            {
                return _declarations.SelectAsArray(r => r.SyntaxReference);
            }
        }

        /// <summary>
        /// Returns the original syntax nodes for this type declaration across all its parts.  If
        /// <paramref name="quickAttributes"/> is provided, attributes will not be returned if it
        /// is certain there are none that could match the request.  This prevents going back to 
        /// source unnecessarily.
        /// </summary>
        public ImmutableArray<SyntaxList<AttributeListSyntax>> GetAttributeDeclarations(QuickAttributes? quickAttributes)
        {
            var attributeSyntaxListBuilder = ArrayBuilder<SyntaxList<AttributeListSyntax>>.GetInstance();

            foreach (var decl in _declarations)
            {
                if (!decl.HasAnyAttributes)
                {
                    continue;
                }

                if (quickAttributes != null && (decl.QuickAttributes & quickAttributes.Value) == 0)
                {
                    continue;
                }

                var syntaxRef = decl.SyntaxReference;
                var typeDecl = syntaxRef.GetSyntax();
                SyntaxList<AttributeListSyntax> attributesSyntaxList;
                switch (typeDecl.Kind())
                {
                    case SyntaxKind.ClassDeclaration:
                    case SyntaxKind.StructDeclaration:
                    case SyntaxKind.UnionDeclaration:
                    case SyntaxKind.InterfaceDeclaration:
                    case SyntaxKind.RecordDeclaration:
                    case SyntaxKind.RecordStructDeclaration:
                    case SyntaxKind.ExtensionBlockDeclaration:
                        attributesSyntaxList = ((TypeDeclarationSyntax)typeDecl).AttributeLists;
                        break;

                    case SyntaxKind.DelegateDeclaration:
                        attributesSyntaxList = ((DelegateDeclarationSyntax)typeDecl).AttributeLists;
                        break;

                    case SyntaxKind.EnumDeclaration:
                        attributesSyntaxList = ((EnumDeclarationSyntax)typeDecl).AttributeLists;
                        break;

                    default:
                        throw ExceptionUtilities.UnexpectedValue(typeDecl.Kind());
                }

                attributeSyntaxListBuilder.Add(attributesSyntaxList);
            }

            return attributeSyntaxListBuilder.ToImmutableAndFree();
        }

        public override DeclarationKind Kind
        {
            get
            {
                return this.Declarations[0].Kind;
            }
        }

        public int Arity
        {
            get
            {
                return this.Declarations[0].Arity;
            }
        }

        public bool ContainsExtensionMethods
        {
            get
            {
                foreach (var decl in this.Declarations)
                {
                    if (decl.AnyMemberHasExtensionMethodSyntax)
                        return true;
                }

                return false;
            }
        }

        public bool ContainsExtensionDeclarations
        {
            get
            {
                foreach (var decl in this.Declarations)
                {
                    if (decl.AnyExtensionDeclarationSyntax)
                        return true;
                }

                return false;
            }
        }

        public bool HasPrimaryConstructor
        {
            get
            {
                foreach (var decl in this.Declarations)
                {
                    if (decl.HasPrimaryConstructor)
                        return true;
                }

                return false;
            }
        }

        public bool AnyMemberHasAttributes
        {
            get
            {
                foreach (var decl in this.Declarations)
                {
                    if (decl.AnyMemberHasAttributes)
                        return true;
                }

                return false;
            }
        }

        public LexicalSortKey GetLexicalSortKey(CSharpCompilation compilation)
        {
            LexicalSortKey sortKey = new LexicalSortKey(Declarations[0].NameLocation, compilation);
            for (var i = 1; i < Declarations.Length; i++)
            {
                sortKey = LexicalSortKey.First(sortKey, new LexicalSortKey(Declarations[i].NameLocation, compilation));
            }

            return sortKey;
        }

        public OneOrMany<SourceLocation> NameLocations
        {
            get
            {
                if (Declarations.Length == 1)
                    return OneOrMany.Create(Declarations[0].NameLocation);

                var builder = ArrayBuilder<SourceLocation>.GetInstance(Declarations.Length);
                foreach (var decl in Declarations)
                    builder.AddIfNotNull(decl.NameLocation);

                return builder.ToOneOrManyAndFree();
            }
        }

        private ImmutableArray<MergedTypeDeclaration> MakeChildren()
        {
            ArrayBuilder<SingleTypeDeclaration> nestedTypes = null;

            foreach (var decl in this.Declarations)
            {
                foreach (var child in decl.Children)
                {
                    var asType = child as SingleTypeDeclaration;
                    if (asType != null)
                    {
                        if (nestedTypes == null)
                        {
                            nestedTypes = ArrayBuilder<SingleTypeDeclaration>.GetInstance();
                        }
                        nestedTypes.Add(asType);
                    }
                }
            }

            var children = ArrayBuilder<MergedTypeDeclaration>.GetInstance();

            if (nestedTypes != null)
            {
                var typesGrouped = nestedTypes.ToDictionary(t => t.Identity);
                nestedTypes.Free();

                foreach (var typeGroup in typesGrouped.Values)
                {
                    children.Add(new MergedTypeDeclaration(typeGroup));
                }
            }

            return children.ToImmutableAndFree();
        }

        public new ImmutableArray<MergedTypeDeclaration> Children
        {
            get
            {
                if (_lazyChildren.IsDefault)
                {
                    ImmutableInterlocked.InterlockedInitialize(ref _lazyChildren, MakeChildren());
                }

                return _lazyChildren;
            }
        }

        protected override ImmutableArray<Declaration> GetDeclarationChildren()
        {
            return StaticCast<Declaration>.From(this.Children);
        }

        public ICollection<string> MemberNames
        {
            get
            {
                if (_lazyMemberNames == null)
                {
                    var names = UnionCollection<string>.Create(this.Declarations, d => d.MemberNames.Value);
                    Interlocked.CompareExchange(ref _lazyMemberNames, names, null);
                }

                return _lazyMemberNames;
            }
        }

        internal string GetDebuggerDisplay()
        {
            string identifier = (Kind is DeclarationKind.Extension) ? "extension" : Name;
            return $"{nameof(MergedTypeDeclaration)} {identifier}";
        }
    }
}
