using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.CSharp.Populators;
using Semmle.Extraction.Entities;
using Semmle.Extraction.Kinds;

namespace Semmle.Extraction.CSharp.Entities.Expressions
{
    abstract class Initializer : Expression<InitializerExpressionSyntax>
    {
        protected Initializer(ExpressionNodeInfo info) : base(info) { }
    }

    class ArrayInitializer : Expression<InitializerExpressionSyntax>
    {
        ArrayInitializer(ExpressionNodeInfo info) : base(info.SetType(NullType.Create(info.Context)).SetKind(ExprKind.ARRAY_INIT)) { }

        public static Expression Create(ExpressionNodeInfo info) => new ArrayInitializer(info).TryPopulate();

        protected override void Populate()
        {
            var child = 0;
            foreach (var e in Syntax.Expressions)
            {
                if (e.Kind() == SyntaxKind.ArrayInitializerExpression)
                {
                    // Recursively create another array initializer
                    Create(new ExpressionNodeInfo(cx, (InitializerExpressionSyntax)e, this, child++));
                }
                else
                {
                    // Create the expression normally.
                    Create(cx, e, this, child++);
                }
            }
        }
    }

    // Array initializer { ..., ... }.
    class ImplicitArrayInitializer : Initializer
    {
        ImplicitArrayInitializer(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.ARRAY_CREATION)) { }

        public static Expression Create(ExpressionNodeInfo info) => new ImplicitArrayInitializer(info).TryPopulate();

        protected override void Populate()
        {
            ArrayInitializer.Create(new ExpressionNodeInfo(cx, Syntax, this, -1));
            cx.Emit(Tuples.implicitly_typed_array_creation(this));
        }
    }

    class ObjectInitializer : Initializer
    {
        ObjectInitializer(ExpressionNodeInfo info)
            : base(info.SetKind(ExprKind.OBJECT_INIT)) { }

        public static Expression Create(ExpressionNodeInfo info) => new ObjectInitializer(info).TryPopulate();

        protected override void Populate()
        {
            var child = 0;

            foreach (var init in Syntax.Expressions)
            {
                var assignment = init as AssignmentExpressionSyntax;

                if (assignment != null)
                {
                    var assignmentEntity = new Expression(new ExpressionNodeInfo(cx, init, this, child++).SetKind(ExprKind.SIMPLE_ASSIGN));

                    CreateFromNode(new ExpressionNodeInfo(cx, assignment.Right, assignmentEntity, 0));

                    var target = cx.GetSymbolInfo(assignment.Left);
                    if (target.Symbol == null)
                    {
                        cx.ModelError(assignment, "Unknown object initializer");
                        new Unknown(new ExpressionNodeInfo(cx, assignment.Left, assignmentEntity, 1));
                    }
                    else
                    {
                        Access.Create(new ExpressionNodeInfo(cx, assignment.Left, assignmentEntity, 1), target.Symbol, false, cx.CreateEntity(target.Symbol));
                    }
                }
                else
                {
                    cx.ModelError(init, "Unexpected object initialization");
                    Create(cx, init, this, child++);
                }
            }
        }
    }

    class CollectionInitializer : Initializer
    {
        CollectionInitializer(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.COLLECTION_INIT)) { }

        public static Expression Create(ExpressionNodeInfo info) => new CollectionInitializer(info).TryPopulate();

        protected override void Populate()
        {
            var child = 0;
            foreach (var i in Syntax.Expressions)
            {
                var collectionInfo = cx.Model(Syntax).GetCollectionInitializerSymbolInfo(i);
                var addMethod = Method.Create(cx, collectionInfo.Symbol as IMethodSymbol);
                var voidType = Entities.Type.Create(cx, new AnnotatedTypeSymbol(cx.Compilation.GetSpecialType(SpecialType.System_Void), NullableAnnotation.NotApplicable));

                var invocation = new Expression(new ExpressionInfo(cx, voidType, cx.Create(i.GetLocation()), ExprKind.METHOD_INVOCATION, this, child++, false, null));

                if (addMethod != null)
                    cx.Emit(Tuples.expr_call(invocation, addMethod));
                else
                    cx.ModelError(Syntax, "Unable to find an Add() method for collection initializer");

                if (i.Kind() == SyntaxKind.ComplexElementInitializerExpression)
                {
                    // Arrays of the form new Foo { { 1,2 }, { 3, 4 } }
                    // where the arguments { 1, 2 } are passed to the Add() method.

                    var init = (InitializerExpressionSyntax)i;

                    int addChild = 0;
                    foreach (var arg in init.Expressions)
                    {
                        Create(cx, arg, invocation, addChild++);
                    }
                }
                else
                {
                    Create(cx, i, invocation, 0);
                }
            }
        }
    }
}
