using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.CSharp.Populators;
using Semmle.Extraction.Entities;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Semmle.Extraction.CSharp.Entities
{
    class Attribute : FreshEntity, IExpressionParentEntity
    {
        bool IExpressionParentEntity.IsTopLevelParent => true;

        private readonly AttributeData AttributeData;
        private readonly IEntity Entity;

        public Attribute(Context cx, AttributeData attribute, IEntity entity)
            : base(cx)
        {
            AttributeData = attribute;
            Entity = entity;
            TryPopulate();
        }

        protected override void Populate(TextWriter trapFile)
        {
            if (AttributeData.ApplicationSyntaxReference != null)
            {
                // !! Extract attributes from assemblies.
                // This is harder because the "expression" entities presume the
                // existence of a syntax tree. This is not the case for compiled
                // attributes.
                var syntax = AttributeData.ApplicationSyntaxReference.GetSyntax() as AttributeSyntax;
                ExtractAttribute(cx.TrapWriter.Writer, syntax, AttributeData.AttributeClass, Entity);
            }
        }

        public Attribute(Context cx, AttributeSyntax attribute, IEntity entity)
            : base(cx)
        {
            var info = cx.GetSymbolInfo(attribute);
            ExtractAttribute(cx.TrapWriter.Writer, attribute, info.Symbol.ContainingType, entity);
        }

        void ExtractAttribute(System.IO.TextWriter trapFile, AttributeSyntax syntax, ITypeSymbol attributeClass, IEntity entity)
        {
            var type = Type.Create(cx, attributeClass);
            trapFile.attributes(this, type.TypeRef, entity);

            trapFile.attribute_location(this, cx.Create(syntax.Name.GetLocation()));

            if (cx.Extractor.OutputPath != null)
                trapFile.attribute_location(this, Assembly.CreateOutputAssembly(cx));

            TypeMention.Create(cx, syntax.Name, this, type);

            if (syntax.ArgumentList != null)
            {
                cx.PopulateLater(() =>
                {
                    int child = 0;
                    foreach (var arg in syntax.ArgumentList.Arguments)
                    {
                        var expr = Expression.Create(cx, arg.Expression, this, child++);
                        if (!(arg.NameEquals is null))
                        {
                            trapFile.expr_argument_name(expr, arg.NameEquals.Name.Identifier.Text);
                        }
                    }
                });
            }
        }

        public static void ExtractAttributes(Context cx, ISymbol symbol, IEntity entity)
        {
            foreach (var attribute in symbol.GetAttributes())
            {
                new Attribute(cx, attribute, entity);
            }
        }

        public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel;
    }
}
