// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Roslyn.Utilities;

using static Microsoft.CodeAnalysis.CodeGeneration.CodeGenerationHelpers;

namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration
{
    internal static class AttributeGenerator
    {
        public static SyntaxList<AttributeListSyntax> GenerateAttributeLists(
            ImmutableArray<AttributeData> attributes,
            CSharpCodeGenerationContextInfo info,
            SyntaxToken? target = null)
        {
            if (info.Context.MergeAttributes)
            {
                var attributeNodes =
                    attributes.OrderBy(a => a.AttributeClass?.Name)
                              .Select(a => TryGenerateAttribute(a, info))
                              .WhereNotNull().ToList();
                return attributeNodes.Count == 0
                    ? default
                    : SyntaxFactory.SingletonList(SyntaxFactory.AttributeList(
                        target.HasValue ? SyntaxFactory.AttributeTargetSpecifier(target.Value) : null,
                        SyntaxFactory.SeparatedList(attributeNodes)));
            }
            else
            {
                var attributeDeclarations =
                    attributes.OrderBy(a => a.AttributeClass?.Name)
                              .Select(a => TryGenerateAttributeDeclaration(a, target, info))
                              .WhereNotNull().ToList();
                return attributeDeclarations.Count == 0
                    ? default
                    : SyntaxFactory.List<AttributeListSyntax>(attributeDeclarations);
            }
        }

        private static AttributeListSyntax? TryGenerateAttributeDeclaration(
            AttributeData attribute, SyntaxToken? target, CSharpCodeGenerationContextInfo info)
        {
            var attributeSyntax = TryGenerateAttribute(attribute, info);
            return attributeSyntax == null
                ? null
                : SyntaxFactory.AttributeList(
                    target.HasValue
                        ? SyntaxFactory.AttributeTargetSpecifier(target.Value)
                        : null,
                    SyntaxFactory.SingletonSeparatedList(attributeSyntax));
        }

        private static AttributeSyntax? TryGenerateAttribute(AttributeData attribute, CSharpCodeGenerationContextInfo info)
        {
            if (IsCompilerInternalAttribute(attribute))
                return null;

            if (!info.Context.MergeAttributes)
            {
                var reusableSyntax = GetReuseableSyntaxNodeForAttribute<AttributeSyntax>(attribute, info);
                if (reusableSyntax != null)
                {
                    return reusableSyntax;
                }
            }

            if (attribute.AttributeClass == null)
                return null;

            var attributeArguments = GenerateAttributeArgumentList(info.Generator, attribute);
            return attribute.AttributeClass.GenerateTypeSyntax() is NameSyntax nameSyntax
                ? SyntaxFactory.Attribute(nameSyntax, attributeArguments)
                : null;
        }

        private static AttributeArgumentListSyntax? GenerateAttributeArgumentList(SyntaxGenerator generator, AttributeData attribute)
        {
            if (attribute.ConstructorArguments.Length == 0 && attribute.NamedArguments.Length == 0)
                return null;

            var arguments = new List<AttributeArgumentSyntax>();
            arguments.AddRange(attribute.ConstructorArguments.Select(c =>
                SyntaxFactory.AttributeArgument(ExpressionGenerator.GenerateExpression(generator, c))));

            arguments.AddRange(attribute.NamedArguments.Select(kvp =>
                SyntaxFactory.AttributeArgument(
                    SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName(kvp.Key)), null,
                    ExpressionGenerator.GenerateExpression(generator, kvp.Value))));

            return SyntaxFactory.AttributeArgumentList(SyntaxFactory.SeparatedList(arguments));
        }
    }
}
