using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.CSharp.Entities.Expressions;
using Semmle.Extraction.Entities;
using Semmle.Extraction.Kinds;
using System.Linq;

namespace Semmle.Extraction.CSharp.Entities
{
    class Property : CachedSymbol<IPropertySymbol>, IExpressionParentEntity
    {
        protected Property(Context cx, IPropertySymbol init)
            : base(cx, init) { }

        public override IId Id
        {
            get
            {
                return new Key(tb =>
                {
                    tb.Append(ContainingType);
                    tb.Append(".");
                    Method.AddExplicitInterfaceQualifierToId(Context, tb, symbol.ExplicitInterfaceImplementations);
                    tb.Append(symbol.Name);
                    tb.Append(";property");
                });
            }
        }

        public override void Populate()
        {
            ExtractMetadataHandle();
            ExtractAttributes();
            ExtractModifiers();
            BindComments();
            ExtractNullability(symbol.NullableAnnotation);
            ExtractRefKind(symbol.RefKind);

            var type = Type.Create(Context, symbol.Type);
            Context.Emit(Tuples.properties(this, symbol.GetName(), ContainingType, type.TypeRef, Create(Context, symbol.OriginalDefinition)));

            var getter = symbol.GetMethod;
            var setter = symbol.SetMethod;

            if (!(getter is null))
                Method.Create(Context, getter);

            if (!(setter is null))
                Method.Create(Context, setter);

            var declSyntaxReferences = IsSourceDeclaration ?
                symbol.DeclaringSyntaxReferences.
                Select(d => d.GetSyntax()).OfType<PropertyDeclarationSyntax>().ToArray()
                : Enumerable.Empty<PropertyDeclarationSyntax>();

            foreach (var explicitInterface in symbol.ExplicitInterfaceImplementations.Select(impl => Type.Create(Context, impl.ContainingType)))
            {
                Context.Emit(Tuples.explicitly_implements(this, explicitInterface.TypeRef));

                foreach (var syntax in declSyntaxReferences)
                    TypeMention.Create(Context, syntax.ExplicitInterfaceSpecifier.Name, this, explicitInterface);
            }

            foreach (var l in Locations)
                Context.Emit(Tuples.property_location(this, l));

            if (IsSourceDeclaration && symbol.FromSource())
            {
                var expressionBody = ExpressionBody;
                if (expressionBody != null)
                {
                    Context.PopulateLater(() => Expression.Create(Context, expressionBody, this, 0));
                }

                int child = 1;
                foreach (var initializer in declSyntaxReferences.
                    Select(n => n.Initializer).
                    Where(i => i != null))
                {
                    Context.PopulateLater(() =>
                    {
                        var loc = Context.Create(initializer.GetLocation());
                        var annotatedType = new AnnotatedType(type, TypeAnnotation.None);
                        var simpleAssignExpr = new Expression(new ExpressionInfo(Context, annotatedType, loc, ExprKind.SIMPLE_ASSIGN, this, child++, false, null));
                        Expression.CreateFromNode(new ExpressionNodeInfo(Context, initializer.Value, simpleAssignExpr, 0));
                        var access = new Expression(new ExpressionInfo(Context, annotatedType, Location, ExprKind.PROPERTY_ACCESS, simpleAssignExpr, 1, false, null));
                        Context.Emit(Tuples.expr_access(access, this));
                        if (!symbol.IsStatic)
                        {
                            This.CreateImplicit(Context, Type.Create(Context, symbol.ContainingType), Location, access, -1);
                        }
                    });
                }

                foreach (var syntax in declSyntaxReferences)
                    TypeMention.Create(Context, syntax.Type, this, type);
            }
        }

        public override Microsoft.CodeAnalysis.Location FullLocation
        {
            get
            {
                return
                    symbol.
                    DeclaringSyntaxReferences.
                    Select(r => r.GetSyntax()).
                    OfType<PropertyDeclarationSyntax>().
                    Select(s => s.GetLocation()).
                    Concat(symbol.Locations).
                    First();
            }
        }

        bool IExpressionParentEntity.IsTopLevelParent => true;

        public static Property Create(Context cx, IPropertySymbol prop)
        {
            bool isIndexer = prop.IsIndexer || prop.ExplicitInterfaceImplementations.Any(e => e.IsIndexer);

            return isIndexer ? Indexer.Create(cx, prop) : PropertyFactory.Instance.CreateEntity(cx, prop);
        }

        public void VisitDeclaration(Context cx, PropertyDeclarationSyntax p)
        {
        }

        class PropertyFactory : ICachedEntityFactory<IPropertySymbol, Property>
        {
            public static readonly PropertyFactory Instance = new PropertyFactory();

            public Property Create(Context cx, IPropertySymbol init) => new Property(cx, init);
        }

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