﻿// 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.Composition;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.InheritanceMargin;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.InheritanceMargin
{
    [ExportLanguageService(typeof(IInheritanceMarginService), LanguageNames.CSharp), Shared]
    internal class CSharpInheritanceMarginService : AbstractInheritanceMarginService
    {
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
        [ImportingConstructor]
        public CSharpInheritanceMarginService()
        {
        }

        protected override string GlobalImportsTitle => CSharpFeaturesResources.Global_using_directives;

        protected override ImmutableArray<SyntaxNode> GetMembers(IEnumerable<SyntaxNode> nodesToSearch)
        {
            var typeDeclarationNodes = nodesToSearch.OfType<TypeDeclarationSyntax>();

            using var _ = PooledObjects.ArrayBuilder<SyntaxNode>.GetInstance(out var builder);
            foreach (var typeDeclarationNode in typeDeclarationNodes)
            {
                // 1. Add the type declaration node.(e.g. class, struct etc..)
                // Use its identifier's position as the line number, since we want the margin to be placed with the identifier
                builder.Add(typeDeclarationNode);

                // 2. Add type members inside this type declaration.
                foreach (var member in typeDeclarationNode.Members)
                {
                    if (member.Kind() is
                            SyntaxKind.MethodDeclaration or
                            SyntaxKind.PropertyDeclaration or
                            SyntaxKind.EventDeclaration or
                            SyntaxKind.IndexerDeclaration or
                            SyntaxKind.OperatorDeclaration or
                            SyntaxKind.ConversionOperatorDeclaration)
                    {
                        builder.Add(member);
                    }

                    // For multiple events that declared in the same EventFieldDeclaration,
                    // add all VariableDeclarators
                    if (member is EventFieldDeclarationSyntax eventFieldDeclarationNode)
                    {
                        builder.AddRange(eventFieldDeclarationNode.Declaration.Variables);
                    }
                }
            }

            return builder.ToImmutableArray();
        }

        protected override SyntaxToken GetDeclarationToken(SyntaxNode declarationNode)
            => declarationNode switch
            {
                MethodDeclarationSyntax methodDeclarationNode => methodDeclarationNode.Identifier,
                PropertyDeclarationSyntax propertyDeclarationNode => propertyDeclarationNode.Identifier,
                EventDeclarationSyntax eventDeclarationNode => eventDeclarationNode.Identifier,
                VariableDeclaratorSyntax variableDeclaratorNode => variableDeclaratorNode.Identifier,
                TypeDeclarationSyntax baseTypeDeclarationNode => baseTypeDeclarationNode.Identifier,
                IndexerDeclarationSyntax indexerDeclarationNode => indexerDeclarationNode.ThisKeyword,
                OperatorDeclarationSyntax operatorDeclarationNode => operatorDeclarationNode.OperatorToken,
                ConversionOperatorDeclarationSyntax conversionOperatorDeclarationNode => conversionOperatorDeclarationNode.Type.GetFirstToken(),
                // Shouldn't reach here since the input declaration nodes are coming from GetMembers() method above
                _ => throw ExceptionUtilities.UnexpectedValue(declarationNode),
            };
    }
}
