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

namespace Semmle.Extraction.CSharp.Entities.Expressions
{
    internal static class Factory
    {
        internal static Expression Create(ExpressionNodeInfo info)
        {
            // Some expressions can be extremely deep (e.g. string + string + string ...)
            // to the extent that the stack has been known to overflow.
            using (info.Context.StackGuard)
            {
                if (info.Node == null)
                {
                    info.Context.ModelError("Attempt to create a null expression");
                    return new Unknown(info);
                }

                switch (info.Node.Kind())
                {
                    case SyntaxKind.AddExpression:
                    case SyntaxKind.SubtractExpression:
                    case SyntaxKind.LessThanExpression:
                    case SyntaxKind.LessThanOrEqualExpression:
                    case SyntaxKind.GreaterThanExpression:
                    case SyntaxKind.GreaterThanOrEqualExpression:
                    case SyntaxKind.MultiplyExpression:
                    case SyntaxKind.LogicalAndExpression:
                    case SyntaxKind.EqualsExpression:
                    case SyntaxKind.ModuloExpression:
                    case SyntaxKind.BitwiseAndExpression:
                    case SyntaxKind.BitwiseOrExpression:
                    case SyntaxKind.DivideExpression:
                    case SyntaxKind.NotEqualsExpression:
                    case SyntaxKind.LogicalOrExpression:
                    case SyntaxKind.IsExpression:
                    case SyntaxKind.AsExpression:
                    case SyntaxKind.RightShiftExpression:
                    case SyntaxKind.LeftShiftExpression:
                    case SyntaxKind.ExclusiveOrExpression:
                    case SyntaxKind.CoalesceExpression:
                        return Binary.Create(info);

                    case SyntaxKind.FalseLiteralExpression:
                    case SyntaxKind.TrueLiteralExpression:
                    case SyntaxKind.StringLiteralExpression:
                    case SyntaxKind.NullLiteralExpression:
                    case SyntaxKind.NumericLiteralExpression:
                    case SyntaxKind.CharacterLiteralExpression:
                    case SyntaxKind.DefaultLiteralExpression:
                        return Literal.Create(info);

                    case SyntaxKind.InvocationExpression:
                        return Invocation.Create(info);

                    case SyntaxKind.PostIncrementExpression:
                        return PostfixUnary.Create(info.SetKind(ExprKind.POST_INCR), ((PostfixUnaryExpressionSyntax)info.Node).Operand);

                    case SyntaxKind.PostDecrementExpression:
                        return PostfixUnary.Create(info.SetKind(ExprKind.POST_DECR), ((PostfixUnaryExpressionSyntax)info.Node).Operand);

                    case SyntaxKind.AwaitExpression:
                        return Await.Create(info);

                    case SyntaxKind.ElementAccessExpression:
                        return NormalElementAccess.Create(info);

                    case SyntaxKind.SimpleAssignmentExpression:
                    case SyntaxKind.OrAssignmentExpression:
                    case SyntaxKind.AndAssignmentExpression:
                    case SyntaxKind.SubtractAssignmentExpression:
                    case SyntaxKind.AddAssignmentExpression:
                    case SyntaxKind.MultiplyAssignmentExpression:
                    case SyntaxKind.ExclusiveOrAssignmentExpression:
                    case SyntaxKind.LeftShiftAssignmentExpression:
                    case SyntaxKind.RightShiftAssignmentExpression:
                    case SyntaxKind.DivideAssignmentExpression:
                    case SyntaxKind.ModuloAssignmentExpression:
                    case SyntaxKind.CoalesceAssignmentExpression:
                        return Assignment.Create(info);

                    case SyntaxKind.ObjectCreationExpression:
                        return ExplicitObjectCreation.Create(info);

                    case SyntaxKind.ArrayCreationExpression:
                        return NormalArrayCreation.Create(info);

                    case SyntaxKind.ObjectInitializerExpression:
                        return ObjectInitializer.Create(info);

                    case SyntaxKind.ArrayInitializerExpression:
                        return ImplicitArrayInitializer.Create(info);

                    case SyntaxKind.CollectionInitializerExpression:
                        return CollectionInitializer.Create(info);

                    case SyntaxKind.ConditionalAccessExpression:
                        return MemberAccess.Create(info, (ConditionalAccessExpressionSyntax)info.Node);

                    case SyntaxKind.SimpleMemberAccessExpression:
                        return MemberAccess.Create(info, (MemberAccessExpressionSyntax)info.Node);

                    case SyntaxKind.UnaryMinusExpression:
                        return Unary.Create(info.SetKind(ExprKind.MINUS));

                    case SyntaxKind.UnaryPlusExpression:
                        return Unary.Create(info.SetKind(ExprKind.PLUS));

                    case SyntaxKind.SimpleLambdaExpression:
                        return Lambda.Create(info, (SimpleLambdaExpressionSyntax)info.Node);

                    case SyntaxKind.ParenthesizedLambdaExpression:
                        return Lambda.Create(info, (ParenthesizedLambdaExpressionSyntax)info.Node);

                    case SyntaxKind.ConditionalExpression:
                        return Conditional.Create(info);

                    case SyntaxKind.CastExpression:
                        return Cast.Create(info);

                    case SyntaxKind.ParenthesizedExpression:
                        return Create(info.SetNode(((ParenthesizedExpressionSyntax)info.Node).Expression));

                    case SyntaxKind.PointerType:
                    case SyntaxKind.ArrayType:
                    case SyntaxKind.PredefinedType:
                    case SyntaxKind.NullableType:
                        return TypeAccess.Create(info);

                    case SyntaxKind.TypeOfExpression:
                        return TypeOf.Create(info);

                    case SyntaxKind.QualifiedName:
                    case SyntaxKind.IdentifierName:
                    case SyntaxKind.AliasQualifiedName:
                    case SyntaxKind.GenericName:
                        return Name.Create(info);

                    case SyntaxKind.LogicalNotExpression:
                        return Unary.Create(info.SetKind(ExprKind.LOG_NOT));

                    case SyntaxKind.BitwiseNotExpression:
                        return Unary.Create(info.SetKind(ExprKind.BIT_NOT));

                    case SyntaxKind.PreIncrementExpression:
                        return Unary.Create(info.SetKind(ExprKind.PRE_INCR));

                    case SyntaxKind.PreDecrementExpression:
                        return Unary.Create(info.SetKind(ExprKind.PRE_DECR));

                    case SyntaxKind.ThisExpression:
                        return This.CreateExplicit(info);

                    case SyntaxKind.AddressOfExpression:
                        return Unary.Create(info.SetKind(ExprKind.ADDRESS_OF));

                    case SyntaxKind.PointerIndirectionExpression:
                        return Unary.Create(info.SetKind(ExprKind.POINTER_INDIRECTION));

                    case SyntaxKind.DefaultExpression:
                        return Default.Create(info);

                    case SyntaxKind.CheckedExpression:
                        return Checked.Create(info);

                    case SyntaxKind.UncheckedExpression:
                        return Unchecked.Create(info);

                    case SyntaxKind.BaseExpression:
                        return Base.Create(info);

                    case SyntaxKind.AnonymousMethodExpression:
                        return Lambda.Create(info, (AnonymousMethodExpressionSyntax)info.Node);

                    case SyntaxKind.ImplicitArrayCreationExpression:
                        return ImplicitArrayCreation.Create(info);

                    case SyntaxKind.AnonymousObjectCreationExpression:
                        return ImplicitObjectCreation.Create(info);

                    case SyntaxKind.ComplexElementInitializerExpression:
                        return CollectionInitializer.Create(info);

                    case SyntaxKind.SizeOfExpression:
                        return SizeOf.Create(info);

                    case SyntaxKind.PointerMemberAccessExpression:
                        return PointerMemberAccess.Create(info);

                    case SyntaxKind.QueryExpression:
                        return Query.Create(info);

                    case SyntaxKind.InterpolatedStringExpression:
                        return InterpolatedString.Create(info);

                    case SyntaxKind.MemberBindingExpression:
                        return MemberAccess.Create(info, (MemberBindingExpressionSyntax)info.Node);

                    case SyntaxKind.ElementBindingExpression:
                        return BindingElementAccess.Create(info);

                    case SyntaxKind.StackAllocArrayCreationExpression:
                        return StackAllocArrayCreation.Create(info);

                    case SyntaxKind.ArgListExpression:
                        return ArgList.Create(info);

                    case SyntaxKind.RefTypeExpression:
                        return RefType.Create(info);

                    case SyntaxKind.RefValueExpression:
                        return RefValue.Create(info);

                    case SyntaxKind.MakeRefExpression:
                        return MakeRef.Create(info);

                    case SyntaxKind.ThrowExpression:
                        return Throw.Create(info);

                    case SyntaxKind.DeclarationExpression:
                        return VariableDeclaration.Create(info.Context, (DeclarationExpressionSyntax)info.Node, info.Parent, info.Child);

                    case SyntaxKind.TupleExpression:
                        return Tuple.Create(info);

                    case SyntaxKind.RefExpression:
                        return Ref.Create(info);

                    case SyntaxKind.IsPatternExpression:
                        return IsPattern.Create(info);

                    case SyntaxKind.RangeExpression:
                        return RangeExpression.Create(info);

                    case SyntaxKind.IndexExpression:
                        return Unary.Create(info.SetKind(ExprKind.INDEX));

                    case SyntaxKind.SwitchExpression:
                        return Switch.Create(info);

                    case SyntaxKind.SuppressNullableWarningExpression:
                        return PostfixUnary.Create(info.SetKind(ExprKind.SUPPRESS_NULLABLE_WARNING), ((PostfixUnaryExpressionSyntax)info.Node).Operand);

                    default:
                        info.Context.ModelError(info.Node, $"Unhandled expression '{info.Node}' of kind '{info.Node.Kind()}'");
                        return new Unknown(info);
                }
            }
        }
    }
}
