// <auto-generated/>
/*
The MIT License (MIT)

Copyright (c) 2016-2020 Maksim Volkau

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

// ReSharper disable CoVariantArrayConversion
/*
// Lists the target platforms that are Not supported by FEC - simplifies the direct referencing of Expression.cs file
// Don't forget to uncomment #endif at the end of file
*/
#if !PCL && !NET35 && !NET40 && !NET403 && !NETSTANDARD1_0 && !NETSTANDARD1_1 && !NETSTANDARD1_2 && !NETCOREAPP1_0 && !NETCOREAPP1_1
#define SUPPORTS_FAST_EXPRESSION_COMPILER
#endif

#if SUPPORTS_FAST_EXPRESSION_COMPILER

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using static System.Environment;
using SysExpr = System.Linq.Expressions.Expression;

namespace FastExpressionCompiler.LightExpression
{
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member

    /// <summary>Facade for constructing Expression.</summary>
    public abstract class Expression
    {
        /// <summary>Expression node type.</summary>
        public abstract ExpressionType NodeType { get; }

        /// <summary>All expressions should have a Type.</summary>
        public abstract Type Type { get; }

        internal struct LightAndSysExpr
        {
            public Expression LightExpr;
            public SysExpr SysExpr;
        }

        public SysExpr ToExpression()
        {
            var exprsConverted = new LiveCountArray<LightAndSysExpr>(Tools.Empty<LightAndSysExpr>());
            return ToExpression(ref exprsConverted);
        }

        /// Converts back to respective System Expression, so you may Compile it by usual means.
        internal SysExpr ToExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted)
        {
            var i = exprsConverted.Count - 1;
            while (i != -1 && !ReferenceEquals(exprsConverted.Items[i].LightExpr, this)) --i;
            if (i != -1)
                return exprsConverted.Items[i].SysExpr;

            var sysExpr = CreateSysExpression(ref exprsConverted);

            ref var item = ref exprsConverted.PushSlot();
            item.LightExpr = this;
            item.SysExpr = sysExpr;

            return sysExpr;
        }

        internal abstract SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted);

        /// Tries to print the expression in its constructing syntax - helpful to get it from debug and put into code to test,
        /// e.g. <code><![CDATA[ Lambda(New(typeof(X).GetTypeInfo().DeclaredConstructors.ToArray()[1]), Parameter(typeof(X), "x")) ]]></code>.
        /// 
        /// NOTE: It is trying hard but the Parameter expression are not consolidated into one. Hopefully R# will help you to re-factor them into a single variable. 
        public abstract string CodeString { get; }

        /// <summary>Converts to Expression and outputs its as string</summary>
        public override string ToString() => ToExpression().ToString();

        /// <summary>Reduces the Expression to simple ones</summary>
        public virtual Expression Reduce() => this;

        internal static SysExpr[] ToExpressions(IReadOnlyList<Expression> exprs, ref LiveCountArray<LightAndSysExpr> exprsConverted)
        {
            if (exprs.Count == 0)
                return Tools.Empty<SysExpr>();

            if (exprs.Count == 1)
                return new[] { exprs[0].ToExpression(ref exprsConverted) };

            var result = new SysExpr[exprs.Count];
            for (var i = 0; i < result.Length; ++i)
                result[i] = exprs[i].ToExpression(ref exprsConverted);
            return result;
        }

        internal static string ToParamsCode<T>(IReadOnlyList<T> arguments) where T : Expression 
        {
            if (arguments.Count == 0)
                return $"new {typeof(T).Name}[0]";

            var s = new StringBuilder();
            for (var i = 0; i < arguments.Count; i++)
            {
                if (i > 0)
                    s.Append(",");
                s.Append(arguments[i].CodeString);
            }

            return s.ToString();
        }

        public static ParameterExpression Parameter(Type type, string name = null) =>
            new ParameterExpression(type.IsByRef ? type.GetElementType() : type, name, type.IsByRef);

        public static ParameterExpression Variable(Type type, string name = null) => Parameter(type, name);

        public static readonly ConstantExpression NullConstant  = new TypedConstantExpression(null, typeof(object));
        public static readonly ConstantExpression FalseConstant = new ConstantExpression(false);
        public static readonly ConstantExpression TrueConstant  = new ConstantExpression(true);
        public static readonly ConstantExpression ZeroConstant  = new ConstantExpression(0);

        public static ConstantExpression Constant(bool value) =>
            value ? TrueConstant : FalseConstant;

        public static ConstantExpression Constant(int value) =>
            value == 0 ? ZeroConstant : new TypedConstantExpression<int>(value);

        public static ConstantExpression Constant<T>(T value) => 
            new TypedConstantExpression<T>(value);

        public static ConstantExpression Constant(object value)
        {
            if (value == null)
                return NullConstant;

            if (value is bool b)
                return b ? TrueConstant : FalseConstant;

            if (value is int n)
                return n == 0 ? ZeroConstant : new TypedConstantExpression<int>(n);

            return new ConstantExpression(value);
        }

        public static ConstantExpression Constant(object value, Type type) =>
            new TypedConstantExpression(value, type);

        public static NewExpression New(Type type)
        {
            if (type.IsValueType())
                return new NewValueTypeExpression(type);

            foreach (var x in type.GetTypeInfo().DeclaredConstructors)
                if (x.GetParameters().Length == 0)
                    return new NewExpression(x);

            throw new ArgumentException($"The type {type} is missing the default constructor");
        }

        public static NewExpression New(ConstructorInfo ctor, params Expression[] arguments) =>
            arguments == null || arguments.Length == 0 ? new NewExpression(ctor): new ManyArgumentsNewExpression(ctor, arguments);

        public static NewExpression New(ConstructorInfo ctor, IEnumerable<Expression> arguments)
        {
            var args = arguments.AsReadOnlyList();
            return args == null || args.Count == 0 ? new NewExpression(ctor) : new ManyArgumentsNewExpression(ctor, args);
        }

        public static NewExpression New(ConstructorInfo ctor) => new NewExpression(ctor);

        public static NewExpression New(ConstructorInfo ctor, Expression arg) =>
            new OneArgumentNewExpression(ctor, arg);

        public static NewExpression New(ConstructorInfo ctor, Expression arg0, Expression arg1) =>
            new TwoArgumentsNewExpression(ctor, arg0, arg1);

        public static NewExpression New(ConstructorInfo ctor, Expression arg0, Expression arg1, Expression arg2) =>
            new ThreeArgumentsNewExpression(ctor, arg0, arg1, arg2);

        public static NewExpression New(ConstructorInfo ctor, 
            Expression arg0, Expression arg1, Expression arg2, Expression arg3) =>
            new FourArgumentsNewExpression(ctor, arg0, arg1, arg2, arg3);

        public static NewExpression New(ConstructorInfo ctor,
            Expression arg0, Expression arg1, Expression arg2, Expression arg3, Expression arg4) =>
            new FiveArgumentsNewExpression(ctor, arg0, arg1, arg2, arg3, arg4);

        public static MethodCallExpression Call(MethodInfo method, params Expression[] arguments)
        {
            if (arguments == null || arguments.Length == 0)
                return new MethodCallExpression(method);
            return new ManyArgumentsMethodCallExpression(method, arguments);
        }

        public static MethodCallExpression Call(MethodInfo method, IEnumerable<Expression> arguments)
        {
            var args = arguments.AsReadOnlyList();
            if (args == null || args.Count == 0)
                return new MethodCallExpression(method);
            return new ManyArgumentsMethodCallExpression(method, args);
        }

        public static MethodCallExpression Call(Expression instance, MethodInfo method, params Expression[] arguments)
        {
            if (arguments == null || arguments.Length == 0)
                return new InstanceMethodCallExpression(instance, method);
            return new InstanceManyArgumentsMethodCallExpression(instance, method, arguments);
        }

        public static MethodCallExpression Call(Expression instance, MethodInfo method, IEnumerable<Expression> arguments)
        {
            var args = arguments.AsReadOnlyList();
            if (args == null || args.Count == 0)
                return new InstanceMethodCallExpression(instance, method);
            return new InstanceManyArgumentsMethodCallExpression(instance, method, args);
        }

        public static MethodCallExpression Call(Type type, string methodName, Type[] typeArguments, params Expression[] arguments)
        {
            if (arguments == null || arguments.Length == 0)
                return new MethodCallExpression(type.FindMethod(methodName, typeArguments, arguments, isStatic: true));
            return new ManyArgumentsMethodCallExpression(type.FindMethod(methodName, typeArguments, arguments, isStatic: true), arguments);
        }

        public static MethodCallExpression Call(Type type, string methodName, Type[] typeArguments, IEnumerable<Expression> arguments)
        {
            var args = arguments.AsReadOnlyList();
            if (args == null || args.Count == 0)
                return new MethodCallExpression(type.FindMethod(methodName, typeArguments, args, isStatic: true));
            return new ManyArgumentsMethodCallExpression(type.FindMethod(methodName, typeArguments, args, isStatic: true), args);
        }

        public static MethodCallExpression Call(Expression instance, string methodName, Type[] typeArguments, params Expression[] arguments)
        {
            if (arguments == null || arguments.Length == 0)
                return new InstanceMethodCallExpression(instance, instance.Type.FindMethod(methodName, typeArguments, arguments));
            return new InstanceManyArgumentsMethodCallExpression(instance, instance.Type.FindMethod(methodName, typeArguments, arguments), arguments);
        }

        public static MethodCallExpression Call(Expression instance, string methodName, Type[] typeArguments, IEnumerable<Expression> arguments)
        {
            var args = arguments.AsReadOnlyList();
            if (args == null || args.Count == 0)
                return new InstanceMethodCallExpression(instance, instance.Type.FindMethod(methodName, typeArguments, args));
            return new InstanceManyArgumentsMethodCallExpression(instance, instance.Type.FindMethod(methodName, typeArguments, args), args);
        }

        public static MethodCallExpression Call(MethodInfo method) => 
            new MethodCallExpression(method);

        public static MethodCallExpression Call(Expression instance, MethodInfo method) =>
            instance == null
            ? new MethodCallExpression(method)
            : new InstanceMethodCallExpression(instance, method);

        public static MethodCallExpression Call(MethodInfo method, Expression argument) =>
            new OneArgumentMethodCallExpression(method, argument);

        public static MethodCallExpression Call(Expression instance, MethodInfo method, Expression argument) =>
            instance == null
            ? new OneArgumentMethodCallExpression(method, argument)
            : new InstanceOneArgumentMethodCallExpression(instance, method, argument);

        public static MethodCallExpression Call(MethodInfo method, Expression arg0, Expression arg1) =>
            new TwoArgumentsMethodCallExpression(method, arg0, arg1);

        public static MethodCallExpression Call(Expression instance, MethodInfo method, Expression arg0, Expression arg1) =>
            instance == null
            ? new TwoArgumentsMethodCallExpression(method, arg0, arg1)
            : new InstanceTwoArgumentsMethodCallExpression(instance, method, arg0, arg1);

        public static MethodCallExpression Call(MethodInfo method, Expression arg0, Expression arg1, Expression arg2) =>
            new ThreeArgumentsMethodCallExpression(method, arg0, arg1, arg2);

        public static MethodCallExpression Call(Expression instance, MethodInfo method, Expression arg0, Expression arg1, Expression arg2) =>
            instance == null
            ? new ThreeArgumentsMethodCallExpression(method, arg0, arg1, arg2)
            : new InstanceThreeArgumentsMethodCallExpression(instance, method, arg0, arg1, arg2);

        public static MethodCallExpression Call(MethodInfo method,
            Expression arg0, Expression arg1, Expression arg2, Expression arg3) =>
            new FourArgumentsMethodCallExpression(method, arg0, arg1, arg2, arg3);

        public static MethodCallExpression Call(Expression instance, MethodInfo method,
            Expression arg0, Expression arg1, Expression arg2, Expression arg3) =>
            instance == null 
                ? new FourArgumentsMethodCallExpression(method, arg0, arg1, arg2, arg3)
                : new InstanceFourArgumentsMethodCallExpression(instance, method, arg0, arg1, arg2, arg3);

        public static MethodCallExpression Call(MethodInfo method,
            Expression arg0, Expression arg1, Expression arg2, Expression arg3, Expression arg4) =>
            new FiveArgumentsMethodCallExpression(method, arg0, arg1, arg2, arg3, arg4);

        public static MethodCallExpression Call(Expression instance, MethodInfo method,
            Expression arg0, Expression arg1, Expression arg2, Expression arg3, Expression arg4) =>
            instance == null
            ? new FiveArgumentsMethodCallExpression(method, arg0, arg1, arg2, arg3, arg4)
            : new InstanceFiveArgumentsMethodCallExpression(instance, method, arg0, arg1, arg2, arg3, arg4);

        public static Expression CallIfNotNull(Expression instance, MethodInfo method) =>
            CallIfNotNull(instance, method, Tools.Empty<Expression>());

        public static Expression CallIfNotNull(Expression instance, MethodInfo method, IEnumerable<Expression> arguments)
        {
            var instanceVar = Parameter(instance.Type, "f");
            return Block(instanceVar,
                Assign(instanceVar, instance),
                Condition(Equal(instanceVar, Constant(null)),
                    Constant(null), Call(instanceVar, method, arguments)));
        }

        public static MemberExpression Property(PropertyInfo property) =>
            new PropertyExpression(null, property);

        public static MemberExpression Property(Expression instance, PropertyInfo property) =>
            new PropertyExpression(instance, property);

        public static MemberExpression Property(Expression expression, string propertyName) =>
            Property(expression, expression.Type.FindProperty(propertyName) 
                ?? throw new ArgumentException($"Declared property with the name '{propertyName}' is not found in '{expression.Type}'", nameof(propertyName)));

        public static IndexExpression Property(Expression instance, PropertyInfo indexer, params Expression[] arguments) =>
            new IndexExpression(instance, indexer, arguments);

        public static IndexExpression Property(Expression instance, PropertyInfo indexer, IEnumerable<Expression> arguments) =>
            new IndexExpression(instance, indexer, arguments.AsReadOnlyList());

        public static MemberExpression PropertyOrField(Expression expression, string propertyName) =>
            expression.Type.FindProperty(propertyName) != null ?
                (MemberExpression)new PropertyExpression(expression, expression.Type.FindProperty(propertyName)
                    ?? throw new ArgumentException($"Declared property with the name '{propertyName}' is not found in '{expression.Type}'", nameof(propertyName))) :
                new FieldExpression(expression, expression.Type.FindField(propertyName)
                    ?? throw new ArgumentException($"Declared field with the name '{propertyName}' is not found '{expression.Type}'", nameof(propertyName)));

        public static MemberExpression MakeMemberAccess(Expression expression, MemberInfo member)
        {
            if (member is FieldInfo field)
                return Field(expression, field);
            if (member is PropertyInfo property)
                return Property(expression, property);
            throw new ArgumentException($"Member is not field or property: {member}", nameof(member));
        }

        public static IndexExpression MakeIndex(Expression instance, PropertyInfo indexer, IEnumerable<Expression> arguments) =>
            indexer != null ? Property(instance, indexer, arguments) : ArrayAccess(instance, arguments);

        public static IndexExpression ArrayAccess(Expression array, params Expression[] indexes) =>
            new IndexExpression(array, null, indexes);

        public static IndexExpression ArrayAccess(Expression array, IEnumerable<Expression> indexes) =>
            new IndexExpression(array, null, indexes.AsReadOnlyList());

        public static MemberExpression Field(FieldInfo field) =>
            new FieldExpression(null, field);

        public static MemberExpression Field(Expression instance, FieldInfo field) =>
            new FieldExpression(instance, field);

        public static MemberExpression Field(Expression instance, string fieldName) =>
            new FieldExpression(instance, instance.Type.FindField(fieldName));

        /// <summary>Creates a UnaryExpression that represents a bitwise complement operation.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <returns>A UnaryExpression that has the NodeType property equal to Not and the Operand property set to the specified value.</returns>
        public static UnaryExpression Not(Expression expression) =>
            new UnaryExpression(ExpressionType.Not, expression);

        /// <summary>Creates a UnaryExpression that represents an explicit reference or boxing conversion where null is supplied if the conversion fails.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <param name="type">A Type to set the Type property equal to.</param>
        /// <returns>A UnaryExpression that has the NodeType property equal to TypeAs and the Operand and Type properties set to the specified values.</returns>
        public static UnaryExpression TypeAs(Expression expression, Type type) =>
            new TypedUnaryExpression(ExpressionType.TypeAs, expression, type);

        public static TypeBinaryExpression TypeEqual(Expression operand, Type type) =>
            new TypeBinaryExpression(ExpressionType.TypeEqual, operand, type);

        public static TypeBinaryExpression TypeIs(Expression operand, Type type) =>
            new TypeBinaryExpression(ExpressionType.TypeIs, operand, type);

        /// <summary>Creates a UnaryExpression that represents an expression for obtaining the length of a one-dimensional array.</summary>
        /// <param name="array">An Expression to set the Operand property equal to.</param>
        /// <returns>A UnaryExpression that has the NodeType property equal to ArrayLength and the Operand property equal to array.</returns>
        public static UnaryExpression ArrayLength(Expression array) =>
            new TypedUnaryExpression<int>(ExpressionType.ArrayLength, array);

        /// <summary>Creates a UnaryExpression that represents a type conversion operation.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <param name="type">A Type to set the Type property equal to.</param>
        /// <returns>A UnaryExpression that has the NodeType property equal to Convert and the Operand and Type properties set to the specified values.</returns>
        public static UnaryExpression Convert(Expression expression, Type type) =>
            new TypedUnaryExpression(ExpressionType.Convert, expression, type);

        /// <summary>Creates a UnaryExpression that represents a type conversion operation.</summary>
        /// <typeparam name="TTo">A Type to set the Type property equal to.</typeparam>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <returns>A UnaryExpression that has the NodeType property equal to Convert and the Operand and Type properties set to the specified values.</returns>
        public static UnaryExpression Convert<TTo>(Expression expression) =>
            new TypedUnaryExpression<TTo>(ExpressionType.Convert, expression);

        /// <summary>Creates a UnaryExpression that represents a conversion operation for which the implementing method is specified.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <param name="type">A Type to set the Type property equal to.</param>
        /// <param name="method">A MethodInfo to set the Method property equal to.</param>
        /// <returns>A UnaryExpression that has the NodeType property equal to Convert and the Operand, Type, and Method properties set to the specified values.</returns>
        public static UnaryExpression Convert(Expression expression, Type type, MethodInfo method) =>
            new ConvertWithMethodUnaryExpression(ExpressionType.Convert, expression, type, method);

        /// <summary>Creates a UnaryExpression that represents a conversion operation that throws an exception if the target type is overflowed.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <param name="type">A Type to set the Type property equal to.</param>
        /// <returns>A UnaryExpression that has the NodeType property equal to ConvertChecked and the Operand and Type properties set to the specified values.</returns>
        public static UnaryExpression ConvertChecked(Expression expression, Type type) =>
            new TypedUnaryExpression(ExpressionType.ConvertChecked, expression, type);

        /// <summary>Creates a UnaryExpression that represents a conversion operation that throws an exception if the target type is overflowed and for which the implementing method is specified.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <param name="type">A Type to set the Type property equal to.</param>
        /// <param name="method">A MethodInfo to set the Method property equal to.</param>
        /// <returns>A UnaryExpression that has the NodeType property equal to ConvertChecked and the Operand, Type, and Method properties set to the specified values.</returns>
        public static UnaryExpression ConvertChecked(Expression expression, Type type, MethodInfo method) =>
            new ConvertWithMethodUnaryExpression(ExpressionType.ConvertChecked, expression, type, method);

        /// <summary>Creates a UnaryExpression that represents the decrementing of the expression by 1.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <returns>A UnaryExpression that represents the decremented expression.</returns>
        public static UnaryExpression Decrement(Expression expression) =>
            new UnaryExpression(ExpressionType.Decrement, expression);

        /// <summary>Creates a UnaryExpression that represents the incrementing of the expression value by 1.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <returns>A UnaryExpression that represents the incremented expression.</returns>
        public static UnaryExpression Increment(Expression expression) =>
            new UnaryExpression(ExpressionType.Increment, expression);

        /// <summary>Returns whether the expression evaluates to false.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <returns>An instance of UnaryExpression.</returns>
        public static UnaryExpression IsFalse(Expression expression) =>
            new TypedUnaryExpression<bool>(ExpressionType.IsFalse, expression);

        /// <summary>Returns whether the expression evaluates to true.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <returns>An instance of UnaryExpression.</returns>
        public static UnaryExpression IsTrue(Expression expression) =>
            new TypedUnaryExpression<bool>(ExpressionType.IsTrue, expression);

        /// <summary>Creates a UnaryExpression, given an operand, by calling the appropriate factory method.</summary>
        /// <param name="unaryType">The ExpressionType that specifies the type of unary operation.</param>
        /// <param name="operand">An Expression that represents the operand.</param>
        /// <param name="type">The Type that specifies the type to be converted to (pass null if not applicable).</param>
        /// <returns>The UnaryExpression that results from calling the appropriate factory method.</returns>
        public static UnaryExpression MakeUnary(ExpressionType unaryType, Expression operand, Type type) =>
            type == null 
                ? new UnaryExpression(unaryType, operand) 
                : new TypedUnaryExpression(unaryType, operand, type);

        /// <summary>Creates a UnaryExpression that represents an arithmetic negation operation.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <returns>A UnaryExpression that has the NodeType property equal to Negate and the Operand property set to the specified value.</returns>
        public static UnaryExpression Negate(Expression expression) =>
            new UnaryExpression(ExpressionType.Negate, expression);

        /// <summary>Creates a UnaryExpression that represents an arithmetic negation operation that has overflow checking.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <returns>A UnaryExpression that has the NodeType property equal to NegateChecked and the Operand property set to the specified value.</returns>
        public static UnaryExpression NegateChecked(Expression expression) =>
            new UnaryExpression(ExpressionType.NegateChecked, expression);

        /// <summary>Returns the expression representing the ones complement.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <returns>An instance of UnaryExpression.</returns>
        public static UnaryExpression OnesComplement(Expression expression) =>
            new UnaryExpression(ExpressionType.OnesComplement, expression);

        /// <summary>Creates a UnaryExpression that increments the expression by 1 and assigns the result back to the expression.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <returns>A UnaryExpression that represents the resultant expression.</returns>
        public static UnaryExpression PreIncrementAssign(Expression expression) =>
            new UnaryExpression(ExpressionType.PreIncrementAssign, expression);

        /// <summary>Creates a UnaryExpression that represents the assignment of the expression followed by a subsequent increment by 1 of the original expression.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <returns>A UnaryExpression that represents the resultant expression.</returns>
        public static UnaryExpression PostIncrementAssign(Expression expression) =>
            new UnaryExpression(ExpressionType.PostIncrementAssign, expression);

        /// <summary>Creates a UnaryExpression that decrements the expression by 1 and assigns the result back to the expression.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <returns>A UnaryExpression that represents the resultant expression.</returns>
        public static UnaryExpression PreDecrementAssign(Expression expression) =>
            new UnaryExpression(ExpressionType.PreDecrementAssign, expression);

        /// <summary>Creates a UnaryExpression that represents the assignment of the expression followed by a subsequent decrement by 1 of the original expression.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <returns>A UnaryExpression that represents the resultant expression.</returns>
        public static UnaryExpression PostDecrementAssign(Expression expression) =>
            new UnaryExpression(ExpressionType.PostDecrementAssign, expression);

        /// <summary>Creates a UnaryExpression that represents an expression that has a constant value of type Expression.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <returns>A UnaryExpression that has the NodeType property equal to Quote and the Operand property set to the specified value.</returns>
        public static UnaryExpression Quote(Expression expression) =>
            new UnaryExpression(ExpressionType.Quote, expression);

        /// <summary>Creates a UnaryExpression that represents a unary plus operation.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <returns>A UnaryExpression that has the NodeType property equal to UnaryPlus and the Operand property set to the specified value.</returns>
        public static UnaryExpression UnaryPlus(Expression expression) =>
            new UnaryExpression(ExpressionType.UnaryPlus, expression);

        /// <summary>Creates a UnaryExpression that represents an explicit unboxing.</summary>
        /// <param name="expression">An Expression to set the Operand property equal to.</param>
        /// <param name="type">A Type to set the Type property equal to.</param>
        /// <returns>A UnaryExpression that has the NodeType property equal to unbox and the Operand and Type properties set to the specified values.</returns>
        public static UnaryExpression Unbox(Expression expression, Type type) =>
            new TypedUnaryExpression(ExpressionType.Unbox, expression, type);

        public static LambdaExpression Lambda(Expression body) =>
            new LambdaExpression(Tools.GetFuncOrActionType(Tools.Empty<Type>(), body.Type), 
                body, body.Type);

        public static LambdaExpression Lambda(Type delegateType, Expression body) =>
            new LambdaExpression(delegateType, body, body.Type);

        public static LambdaExpression Lambda(Type delegateType, Expression body, Type returnType) =>
            new LambdaExpression(delegateType, body, returnType);

        public static LambdaExpression Lambda(Expression body, params ParameterExpression[] parameters) =>
            Lambda(Tools.GetFuncOrActionType(Tools.GetParamTypes(parameters), body.Type), body, parameters, body.Type);

        public static LambdaExpression Lambda(Type delegateType, Expression body, params ParameterExpression[] parameters) =>
            Lambda(delegateType, body, parameters, GetDelegateReturnType(delegateType));

        public static LambdaExpression Lambda(Type delegateType, Expression body, ParameterExpression[] parameters, Type returnType) =>
            parameters == null || parameters.Length == 0 
                ? new LambdaExpression(delegateType, body, returnType)
                : new ManyParametersLambdaExpression(delegateType, body, parameters, returnType);

        public static Expression<TDelegate> Lambda<TDelegate>(Expression body) =>
            new Expression<TDelegate>(body, typeof(TDelegate).FindDelegateInvokeMethod().ReturnType);

        public static Expression<TDelegate> Lambda<TDelegate>(Expression body, Type returnType) =>
            new Expression<TDelegate>(body, returnType);

        public static Expression<TDelegate> Lambda<TDelegate>(Expression body, params ParameterExpression[] parameters) =>
            Lambda<TDelegate>(body, parameters, GetDelegateReturnType(typeof(TDelegate)));

        public static Expression<TDelegate> Lambda<TDelegate>(Expression body, ParameterExpression[] parameters, Type returnType) =>
            parameters == null || parameters.Length == 0 
                ? new Expression<TDelegate>(body, returnType)
                : new ManyParametersExpression<TDelegate>(body, parameters, returnType);

        /// <summary>
        /// <paramref name="name"/> is ignored for now, the method is just for compatibility with SysExpression
        /// </summary>
        public static Expression<TDelegate> Lambda<TDelegate>(Expression body, string name, params ParameterExpression[] parameters) where TDelegate : class =>
            new ManyParametersExpression<TDelegate>(body, parameters, GetDelegateReturnType(typeof(TDelegate)));

        private static Type GetDelegateReturnType(Type delType)
        {
            var typeInfo = delType.GetTypeInfo();
            if (typeInfo.IsGenericType)
            {
                var typeArguments = typeInfo.GenericTypeArguments;
                var index = typeArguments.Length - 1;
                var typeDef = typeInfo.GetGenericTypeDefinition();
                if (typeDef == FuncTypes[index])
                    return typeArguments[index];

                if (typeDef == ActionTypes[index])
                    return typeof(void);
            }
            else if (delType == typeof(Action))
                return typeof(void);

            return delType.FindDelegateInvokeMethod().ReturnType;
        }

        internal static readonly Type[] FuncTypes =
        {
            typeof(Func<>), typeof(Func<,>), typeof(Func<,,>), typeof(Func<,,,>), typeof(Func<,,,,>),
            typeof(Func<,,,,,>), typeof(Func<,,,,,,>), typeof(Func<,,,,,,,>)
        };

        internal static readonly Type[] ActionTypes =
        {
            typeof(Action<>), typeof(Action<,>), typeof(Action<,,>), typeof(Action<,,,>),
            typeof(Action<,,,,>), typeof(Action<,,,,,>), typeof(Action<,,,,,,>)
        };

        /// <summary>Creates a BinaryExpression that represents applying an array index operator to an array of rank one.</summary>
        /// <param name="array">A Expression to set the Left property equal to.</param>
        /// <param name="index">A Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to ArrayIndex and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression ArrayIndex(Expression array, Expression index) =>
            new ArrayIndexExpression(array, index, array.Type.GetElementType());

        public static MemberAssignment Bind(MemberInfo member, Expression expression) =>
            new MemberAssignment(member, expression);

        public static MemberInitExpression MemberInit(NewExpression newExpr, params MemberBinding[] bindings) =>
            new MemberInitExpression(newExpr, bindings);

        /// <summary>Does not present in System Expression. Enables member assignment on existing instance expression.</summary>
        public static MemberInitExpression MemberInit(Expression instanceExpr, params MemberBinding[] assignments) =>
            new MemberInitExpression(instanceExpr, assignments);

        public static NewArrayExpression NewArrayInit(Type type, params Expression[] initializers) =>
            new NewArrayExpression(ExpressionType.NewArrayInit, type.MakeArrayType(), initializers);

        public static NewArrayExpression NewArrayBounds(Type type, params Expression[] bounds) =>
            new NewArrayExpression(ExpressionType.NewArrayBounds,
                bounds.Length == 1 ? type.MakeArrayType() : type.MakeArrayType(bounds.Length),
                bounds);

        /// <summary>Creates a BinaryExpression that represents an assignment operation.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to Assign and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression Assign(Expression left, Expression right) =>
            new AssignBinaryExpression(left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents raising an expression to a power and assigning the result back to the expression.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to PowerAssign and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression PowerAssign(Expression left, Expression right) =>
            new AssignBinaryExpression(ExpressionType.PowerAssign, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents an addition assignment operation that does not have overflow checking.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to AddAssign and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression AddAssign(Expression left, Expression right) =>
            new AssignBinaryExpression(ExpressionType.AddAssign, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents an addition assignment operation that has overflow checking.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to AddAssignChecked and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression AddAssignChecked(Expression left, Expression right) =>
            new AssignBinaryExpression(ExpressionType.AddAssignChecked, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a bitwise AND assignment operation.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to AndAssign and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression AndAssign(Expression left, Expression right) =>
            new AssignBinaryExpression(ExpressionType.AndAssign, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a bitwise XOR assignment operation.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to ExclusiveOrAssign and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression ExclusiveOrAssign(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.ExclusiveOrAssign, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a bitwise left-shift assignment operation.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to LeftShiftAssign and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression LeftShiftAssign(Expression left, Expression right) =>
            new AssignBinaryExpression(ExpressionType.LeftShiftAssign, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a remainder assignment operation.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to ModuloAssign and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression ModuloAssign(Expression left, Expression right) =>
            new AssignBinaryExpression(ExpressionType.ModuloAssign, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a bitwise OR assignment operation.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to OrAssign and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression OrAssign(Expression left, Expression right) =>
            new AssignBinaryExpression(ExpressionType.OrAssign, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a bitwise right-shift assignment operation.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to RightShiftAssign and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression RightShiftAssign(Expression left, Expression right) =>
            new AssignBinaryExpression(ExpressionType.RightShiftAssign, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a subtraction assignment operation that does not have overflow checking.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to SubtractAssign and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression SubtractAssign(Expression left, Expression right) =>
            new AssignBinaryExpression(ExpressionType.SubtractAssign, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a subtraction assignment operation that has overflow checking.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to SubtractAssignChecked and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression SubtractAssignChecked(Expression left, Expression right) =>
            new AssignBinaryExpression(ExpressionType.SubtractAssignChecked, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a multiplication assignment operation that does not have overflow checking.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to MultiplyAssign and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression MultiplyAssign(Expression left, Expression right) =>
            new AssignBinaryExpression(ExpressionType.MultiplyAssign, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a multiplication assignment operation that has overflow checking.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to MultiplyAssignChecked and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression MultiplyAssignChecked(Expression left, Expression right) =>
            new AssignBinaryExpression(ExpressionType.MultiplyAssignChecked, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a division assignment operation that does not have overflow checking.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to DivideAssign and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression DivideAssign(Expression left, Expression right) =>
            new AssignBinaryExpression(ExpressionType.DivideAssign, left, right, left.Type);

        public static InvocationExpression Invoke(Expression lambda, IEnumerable<Expression> args) =>
            new InvocationExpression(lambda, args.AsReadOnlyList(),
                (lambda as LambdaExpression)?.ReturnType ?? lambda.Type.FindDelegateInvokeMethod().ReturnType);

        public static InvocationExpression Invoke(Expression lambda, params Expression[] args) =>
            Invoke(lambda, (IEnumerable<Expression>)args);

        public static ConditionalExpression Condition(Expression test, Expression ifTrue, Expression ifFalse) =>
            new ConditionalExpression(test, ifTrue, ifFalse);

        public static ConditionalExpression Condition(Expression test, Expression ifTrue, Expression ifFalse, Type type) =>
            new ConditionalExpression(test, ifTrue, ifFalse, type);

        public static ConditionalExpression IfThen(Expression test, Expression ifTrue) =>
            Condition(test, ifTrue, Empty(), typeof(void));

        public static DefaultExpression Empty() => new DefaultExpression(typeof(void));

        public static DefaultExpression Default(Type type) =>
            type == typeof(void) ? Empty() : new DefaultExpression(type);

        public static ConditionalExpression IfThenElse(Expression test, Expression ifTrue, Expression ifFalse) =>
            Condition(test, ifTrue, ifFalse, typeof(void));

        /// <summary>Creates a BinaryExpression that represents an arithmetic addition operation that does not have overflow checking.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to Add and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression Add(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.Add, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents an arithmetic addition operation that has overflow checking.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to AddChecked and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression AddChecked(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.AddChecked, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a bitwise XOR operation.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to ExclusiveOr and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression ExclusiveOr(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.ExclusiveOr, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a bitwise left-shift operation.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to LeftShift and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression LeftShift(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.LeftShift, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents an arithmetic remainder operation.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to Modulo and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression Modulo(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.Modulo, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a bitwise OR operation.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to Or and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression Or(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.Or, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a bitwise right-shift operation.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to RightShift and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression RightShift(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.RightShift, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents an arithmetic subtraction operation that does not have overflow checking.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to Subtract and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression Subtract(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.Subtract, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents an arithmetic subtraction operation that has overflow checking.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to SubtractChecked and the Left, Right, and Method properties set to the specified values.</returns>
        public static BinaryExpression SubtractChecked(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.SubtractChecked, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents an arithmetic multiplication operation that does not have overflow checking.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to Multiply and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression Multiply(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.Multiply, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents an arithmetic multiplication operation that has overflow checking.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to MultiplyChecked and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression MultiplyChecked(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.MultiplyChecked, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents an arithmetic division operation.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to Divide and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression Divide(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.Divide, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents raising a number to a power.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to Power and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression Power(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.Power, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a bitwise AND operation.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to And, and the Left and Right properties are set to the specified values.</returns>
        public static BinaryExpression And(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.And, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a conditional AND operation that evaluates the second operand only if the first operand evaluates to true.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to AndAlso and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression AndAlso(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.AndAlso, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a conditional OR operation that evaluates the second operand only if the first operand evaluates to false.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to OrElse and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression OrElse(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.OrElse, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents an equality comparison.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to Equal and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression Equal(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.Equal, left, right, typeof(bool));

        /// <summary>Creates a BinaryExpression that represents a "greater than" numeric comparison.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to GreaterThan and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression GreaterThan(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.GreaterThan, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a "greater than or equal" numeric comparison.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to GreaterThanOrEqual and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression GreaterThanOrEqual(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.GreaterThanOrEqual, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a "less than" numeric comparison.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to LessThan and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression LessThan(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.LessThan, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents a " less than or equal" numeric comparison.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to LessThanOrEqual and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression LessThanOrEqual(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.LessThanOrEqual, left, right, left.Type);

        /// <summary>Creates a BinaryExpression that represents an inequality comparison.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to NotEqual and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression NotEqual(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.NotEqual, left, right, typeof(bool));

        public static BlockExpression Block(params Expression[] expressions) =>
            Block(expressions[expressions.Length - 1].Type, Tools.Empty<ParameterExpression>(), expressions);

        public static BlockExpression Block(IReadOnlyList<Expression> expressions) =>
            Block(Tools.Empty<ParameterExpression>(), expressions);

        public static BlockExpression Block(IEnumerable<ParameterExpression> variables, params Expression[] expressions) =>
            Block(variables.AsReadOnlyList(), new List<Expression>(expressions));

        public static BlockExpression Block(IReadOnlyList<ParameterExpression> variables, IReadOnlyList<Expression> expressions) =>
            Block(expressions[expressions.Count - 1].Type, variables, expressions);

        public static BlockExpression Block(Type type, IEnumerable<ParameterExpression> variables, params Expression[] expressions) =>
            new BlockExpression(type, variables.AsReadOnlyList(), expressions.AsReadOnlyList());

        public static BlockExpression Block(Type type, IEnumerable<ParameterExpression> variables, IEnumerable<Expression> expressions) =>
            new BlockExpression(type, variables.AsReadOnlyList(), expressions.AsReadOnlyList());

        public static Expression Block(ParameterExpression variable, Expression expression1, Expression expression2) =>
            expression2.NodeType == ExpressionType.Throw || expression1.NodeType == ExpressionType.Throw
                ? (Expression)Block(new[] {variable}, expression1, expression2)
                : new OneVariableTwoExpressionBlockExpression(variable, expression1, expression2);

        /// <summary>
        /// Creates a LoopExpression with the given body and (optional) break target.
        /// </summary>
        /// <param name="body">The body of the loop.</param>
        /// <param name="break">The break target used by the loop body, if required.</param>
        /// <returns>The created LoopExpression.</returns>
        public static LoopExpression Loop(Expression body, LabelTarget @break = null) =>
            @break == null ? new LoopExpression(body, null, null) : new LoopExpression(body, @break, null);

        /// <summary>
        /// Creates a LoopExpression with the given body.
        /// </summary>
        /// <param name="body">The body of the loop.</param>
        /// <param name="break">The break target used by the loop body.</param>
        /// <param name="continue">The continue target used by the loop body.</param>
        /// <returns>The created LoopExpression.</returns>
        public static LoopExpression Loop(Expression body, LabelTarget @break, LabelTarget @continue) =>
            new LoopExpression(body, @break, @continue);

        public static TryExpression TryCatch(Expression body, params CatchBlock[] handlers) =>
            new TryExpression(body, null, handlers);

        public static TryExpression TryCatchFinally(Expression body, Expression @finally, params CatchBlock[] handlers) =>
            new TryExpression(body, @finally, handlers);

        public static TryExpression TryFinally(Expression body, Expression @finally) =>
            new TryExpression(body, @finally, null);

        public static CatchBlock Catch(ParameterExpression variable, Expression body) =>
            new CatchBlock(variable, body, null, variable.Type);

        public static CatchBlock Catch(Type test, Expression body) =>
            new CatchBlock(null, body, null, test);

        /// <summary>Creates a UnaryExpression that represents a throwing of an exception.</summary>
        /// <param name="value">An Expression to set the Operand property equal to.</param>
        /// <returns>A UnaryExpression that represents the exception.</returns>
        public static UnaryExpression Throw(Expression value) => Throw(value, typeof(void));

        /// <summary>Creates a UnaryExpression that represents a throwing of an exception with a given type.</summary>
        /// <param name="value">An Expression to set the Operand property equal to.</param>
        /// <param name="type">The Type of the expression.</param>
        /// <returns>A UnaryExpression that represents the exception.</returns>
        public static UnaryExpression Throw(Expression value, Type type) =>
            new TypedUnaryExpression(ExpressionType.Throw, value, type);

        public static LabelExpression Label(LabelTarget target, Expression defaultValue = null) =>
            new LabelExpression(target, defaultValue);

        public static LabelTarget Label(Type type = null, string name = null) =>
            SysExpr.Label(type ?? typeof(void), name);

        public static LabelTarget Label(string name) =>
            SysExpr.Label(typeof(void), name);

        /// <summary>Creates a BinaryExpression, given the left and right operands, by calling an appropriate factory method.</summary>
        /// <param name="binaryType">The ExpressionType that specifies the type of binary operation.</param>
        /// <param name="left">An Expression that represents the left operand.</param>
        /// <param name="right">An Expression that represents the right operand.</param>
        /// <returns>The BinaryExpression that results from calling the appropriate factory method.</returns>
        public static BinaryExpression MakeBinary(ExpressionType binaryType, Expression left, Expression right)
        {
            switch (binaryType)
            {
                case ExpressionType.AddAssign:
                case ExpressionType.AddAssignChecked:
                case ExpressionType.AndAssign:
                case ExpressionType.Assign:
                case ExpressionType.DivideAssign:
                case ExpressionType.ExclusiveOrAssign:
                case ExpressionType.LeftShiftAssign:
                case ExpressionType.ModuloAssign:
                case ExpressionType.MultiplyAssign:
                case ExpressionType.MultiplyAssignChecked:
                case ExpressionType.OrAssign:
                case ExpressionType.PowerAssign:
                case ExpressionType.RightShiftAssign:
                case ExpressionType.SubtractAssign:
                case ExpressionType.SubtractAssignChecked:
                    return new AssignBinaryExpression(binaryType, left, right, left.Type);
                case ExpressionType.ArrayIndex:
                    return ArrayIndex(left, right);
                case ExpressionType.Coalesce:
                    return Coalesce(left, right);
                default:
                    return new SimpleBinaryExpression(binaryType, left, right, left.Type);
            }
        }

        public static GotoExpression MakeGoto(GotoExpressionKind kind, LabelTarget target, Expression value, Type type = null) =>
            new GotoExpression(kind, target, value, type ?? typeof(void));

        public static GotoExpression Break(LabelTarget target, Expression value = null, Type type = null) =>
            MakeGoto(GotoExpressionKind.Break, target, value, type);

        public static GotoExpression Continue(LabelTarget target, Type type = null) =>
            MakeGoto(GotoExpressionKind.Continue, target, null, type);

        /// <summary>Creates a BinaryExpression that represents a reference equality comparison.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to Equal and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression ReferenceEqual(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.Equal, left, right, typeof(bool));

        /// <summary>Creates a BinaryExpression that represents a reference inequality comparison.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to NotEqual and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression ReferenceNotEqual(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.NotEqual, left, right, typeof(bool));

        public static GotoExpression Return(LabelTarget target, Expression value = null, Type type = null) =>
            MakeGoto(GotoExpressionKind.Return, target, value, type);

        public static GotoExpression Goto(LabelTarget target, Expression value = null, Type type = null) =>
            MakeGoto(GotoExpressionKind.Goto, target, value, type);

        public static SwitchExpression Switch(Expression switchValue, Expression defaultBody, params SwitchCase[] cases) =>
            new SwitchExpression(defaultBody.Type, switchValue, defaultBody, null, cases);

        public static SwitchExpression Switch(Expression switchValue, Expression defaultBody, MethodInfo comparison, params SwitchCase[] cases) =>
            new SwitchExpression(defaultBody.Type, switchValue, defaultBody, comparison, cases);

        public static SwitchExpression Switch(Type type, Expression switchValue, Expression defaultBody, MethodInfo comparison, params SwitchCase[] cases) =>
            new SwitchExpression(type, switchValue, defaultBody, comparison, cases);

        public static SwitchExpression Switch(Type type, Expression switchValue, Expression defaultBody, MethodInfo comparison, IEnumerable<SwitchCase> cases) =>
            new SwitchExpression(type, switchValue, defaultBody, comparison, cases.AsArray());

        public static SwitchExpression Switch(Expression switchValue, params SwitchCase[] cases) =>
            new SwitchExpression(null, switchValue, null, null, cases);

        public static SwitchCase SwitchCase(Expression body, IEnumerable<Expression> testValues) =>
            new SwitchCase(body, testValues);

        public static SwitchCase SwitchCase(Expression body, params Expression[] testValues) =>
            new SwitchCase(body, testValues);

        /// <summary>Creates a BinaryExpression that represents a coalescing operation.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to Coalesce and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression Coalesce(Expression left, Expression right) =>
            new SimpleBinaryExpression(ExpressionType.Coalesce, left, right, GetCoalesceType(left.Type, right.Type));

        /// <summary>Creates a BinaryExpression that represents a coalescing operation.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <param name="type">Result type</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to Coalesce and the Left and Right properties set to the specified values.</returns>
        public static BinaryExpression Coalesce(Expression left, Expression right, Type type) =>
            new SimpleBinaryExpression(ExpressionType.Coalesce, left, right, type);

        /// <summary>Creates a BinaryExpression that represents a coalescing operation, given a conversion function.</summary>
        /// <param name="left">An Expression to set the Left property equal to.</param>
        /// <param name="right">An Expression to set the Right property equal to.</param>
        /// <param name="conversion">A LambdaExpression to set the Conversion property equal to.</param>
        /// <returns>A BinaryExpression that has the NodeType property equal to Coalesce and the Left, Right and Conversion properties set to the specified values.</returns>
        public static BinaryExpression Coalesce(Expression left, Expression right, LambdaExpression conversion) =>
            conversion == null ?
                new SimpleBinaryExpression(ExpressionType.Coalesce, left, right, GetCoalesceType(left.Type, right.Type)) :
                (BinaryExpression)new CoalesceConversionBinaryExpression(left, right, conversion);

        private static Type GetCoalesceType(Type left, Type right)
        {
            var leftTypeInfo = left.GetTypeInfo();
            if (leftTypeInfo.IsGenericType && leftTypeInfo.GetGenericTypeDefinition() == typeof(Nullable<>))
                left = leftTypeInfo.GenericTypeArguments[0];

            if (right == left)
                return left;

            if (leftTypeInfo.IsAssignableFrom(right.GetTypeInfo()) ||
                right.IsImplicitlyBoxingConvertibleTo(left) ||
                right.IsImplicitlyNumericConvertibleTo(left))
                return left;

            if (right.GetTypeInfo().IsAssignableFrom(leftTypeInfo) ||
                left.IsImplicitlyBoxingConvertibleTo(right) ||
                left.IsImplicitlyNumericConvertibleTo(right))
                return right;

            throw new ArgumentException($"Unable to coalesce arguments of left type of {left} and right type of {right}.");
        }
    }

    internal static class TypeTools
    {
        internal static int GetFirstIndex<T>(this IReadOnlyList<T> source, T item)
        {
            if (source.Count != 0)
                for (var i = 0; i < source.Count; ++i)
                    if (ReferenceEquals(source[i], item))
                        return i;
            return -1;
        }

        internal static bool IsImplicitlyBoxingConvertibleTo(this Type source, Type target) =>
            source.GetTypeInfo().IsValueType &&
            (target == typeof(object) ||
             target == typeof(ValueType)) ||
             source.GetTypeInfo().IsEnum && target == typeof(Enum);

        internal static PropertyInfo FindProperty(this Type type, string propertyName)
        {
            var properties = type.GetTypeInfo().DeclaredProperties.AsArray();
            for (var i = 0; i < properties.Length; i++)
                if (properties[i].Name == propertyName)
                    return properties[i];

            return type.GetTypeInfo().BaseType?.FindProperty(propertyName);
        }

        internal static FieldInfo FindField(this Type type, string fieldName)
        {
            var fields = type.GetTypeInfo().DeclaredFields.AsArray();
            for (var i = 0; i < fields.Length; i++)
                if (fields[i].Name == fieldName)
                    return fields[i];

            return type.GetTypeInfo().BaseType?.FindField(fieldName);
        }

        internal static MethodInfo FindMethod(this Type type,
            string methodName, Type[] typeArgs, IReadOnlyList<Expression> args, bool isStatic = false)
        {
            var methods = type.GetTypeInfo().DeclaredMethods.AsArray();
            for (var i = 0; i < methods.Length; i++)
            {
                var m = methods[i];
                if (isStatic == m.IsStatic && methodName == m.Name)
                {
                    typeArgs = typeArgs ?? Type.EmptyTypes;
                    var mTypeArgs = m.GetGenericArguments();

                    if (typeArgs.Length == mTypeArgs.Length &&
                        (typeArgs.Length == 0 || AreTypesTheSame(typeArgs, mTypeArgs)))
                    {
                        args = args ?? Tools.Empty<Expression>();
                        var pars = m.GetParameters();
                        if (args.Count == pars.Length &&
                            (args.Count == 0 || AreArgExpressionsAndParamsOfTheSameType(args, pars)))
                            return m;
                    }
                }
            }

            return type.GetTypeInfo().BaseType?.FindMethod(methodName, typeArgs, args, isStatic);
        }

        private static bool AreTypesTheSame(Type[] source, Type[] target)
        {
            for (var i = 0; i < source.Length; i++)
                if (source[i] != target[i])
                    return false;
            return true;
        }

        private static bool AreArgExpressionsAndParamsOfTheSameType(IReadOnlyList<Expression> args, ParameterInfo[] pars)
        {
            for (var i = 0; i < pars.Length; i++)
                if (pars[i].ParameterType != args[i].Type)
                    return false;
            return true;
        }

        public static IReadOnlyList<T> AsReadOnlyList<T>(this IEnumerable<T> xs)
        {
            if (xs is IReadOnlyList<T> list)
                return list;
            return xs == null ? null : new List<T>(xs);
        }

        internal static bool IsImplicitlyNumericConvertibleTo(this Type source, Type target)
        {
            if (source == typeof(Char))
                return
                    target == typeof(UInt16) ||
                    target == typeof(Int32) ||
                    target == typeof(UInt32) ||
                    target == typeof(Int64) ||
                    target == typeof(UInt64) ||
                    target == typeof(Single) ||
                    target == typeof(Double) ||
                    target == typeof(Decimal);

            if (source == typeof(SByte))
                return
                    target == typeof(Int16) ||
                    target == typeof(Int32) ||
                    target == typeof(Int64) ||
                    target == typeof(Single) ||
                    target == typeof(Double) ||
                    target == typeof(Decimal);

            if (source == typeof(Byte))
                return
                    target == typeof(Int16) ||
                    target == typeof(UInt16) ||
                    target == typeof(Int32) ||
                    target == typeof(UInt32) ||
                    target == typeof(Int64) ||
                    target == typeof(UInt64) ||
                    target == typeof(Single) ||
                    target == typeof(Double) ||
                    target == typeof(Decimal);

            if (source == typeof(Int16))
                return
                    target == typeof(Int32) ||
                    target == typeof(Int64) ||
                    target == typeof(Single) ||
                    target == typeof(Double) ||
                    target == typeof(Decimal);

            if (source == typeof(UInt16))
                return
                    target == typeof(Int32) ||
                    target == typeof(UInt32) ||
                    target == typeof(Int64) ||
                    target == typeof(UInt64) ||
                    target == typeof(Single) ||
                    target == typeof(Double) ||
                    target == typeof(Decimal);

            if (source == typeof(Int32))
                return
                    target == typeof(Int64) ||
                    target == typeof(Single) ||
                    target == typeof(Double) ||
                    target == typeof(Decimal);

            if (source == typeof(UInt32))
                return
                    target == typeof(UInt32) ||
                    target == typeof(UInt64) ||
                    target == typeof(Single) ||
                    target == typeof(Double) ||
                    target == typeof(Decimal);

            if (source == typeof(Int64) ||
                source == typeof(UInt64))
                return
                    target == typeof(Single) ||
                    target == typeof(Double) ||
                    target == typeof(Decimal);

            if (source == typeof(Single))
                return target == typeof(Double);

            return false;
        }
    }

    /// Converts the object of known type into the valid C# code representation
    public static class CodePrinter
    {
        /// Replace with yours if needed
        public static Func<Type, string> GetTypeNameDefault = t => t.Name;

        /// Converts the `typeof(<paramref name="type"/>)` into the proper C# representation.
        public static string ToCode(this Type type, Func<Type, string> getTypeName = null) =>
            type == null ? "null" : $"typeof({type.ToTypeCode()})";

        /// Converts the <paramref name="type"/> into the proper C# representation.
        public static string ToTypeCode(this Type type, Func<Type, string> getTypeName = null)
        {
            var isArray = type.IsArray;
            if (isArray)
                type = type.GetElementType();

            var typeName = (getTypeName ?? GetTypeNameDefault)(type);

            var typeInfo = type.GetTypeInfo();
            if (!typeInfo.IsGenericType)
                return typeName.Replace('+', '.');

            var s = new StringBuilder(typeName.Substring(0, typeName.IndexOf('`')).Replace('+', '.'));
            s.Append('<');

            var genericArgs = typeInfo.GetGenericTypeParametersOrArguments();
            if (typeInfo.IsGenericTypeDefinition)
                s.Append(',', genericArgs.Length - 1);
            else
            {
                for (var i = 0; i < genericArgs.Length; i++)
                {
                    if (i > 0)
                        s.Append(", ");
                    s.Append(genericArgs[i].ToTypeCode(getTypeName));
                }
            }

            s.Append('>');

            if (isArray)
                s.Append("[]");

            return s.ToString();
        }

        /// Prints valid C# Boolean
        public static string ToCode(this bool x) => x ? "true" : "false";

        /// Prints valid C# String escaping the things
        public static string ToCode(this string x) => 
            x == null ? "null" 
                : $"\"{x.Replace("\"", "\\\"").Replace("\r", "\\r").Replace("\n", "\\n")}\"";


        /// Prints valid c# Enum literal
        public static string ToEnumValueCode(this Type enumType, object x)
        {
            var enumTypeInfo = enumType.GetTypeInfo();
            if (enumTypeInfo.IsGenericType && enumTypeInfo.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                if (x == null)
                    return "null";
                enumType = GetGenericTypeParametersOrArguments(enumTypeInfo)[0];
            }

            return $"{enumType.ToTypeCode()}.{Enum.GetName(enumType, x)}";
        }

        private static Type[] GetGenericTypeParametersOrArguments(this TypeInfo typeInfo) => 
            typeInfo.IsGenericTypeDefinition ? typeInfo.GenericTypeParameters : typeInfo.GenericTypeArguments;

        /// Prints many code items as array initializer.
        public static string ToCommaSeparatedCode(this IEnumerable items, Func<object, string> notRecognizedToCode)
        {
            var s = new StringBuilder();
            var count = 0;
            foreach (var item in items)
            {
                if (count++ != 0)
                    s.Append(", ");
                s.Append(item.ToCode(notRecognizedToCode));
            }
            return s.ToString();
        }

        /// Prints many code items as array initializer.
        public static string ToArrayInitializerCode(this IEnumerable items, Type itemType, Func<object, string> notRecognizedToCode) => 
            $"new {itemType.ToTypeCode()}[]{{{items.ToCommaSeparatedCode(notRecognizedToCode)}}}";

        /// Prints valid C# for known <paramref name="x"/> type,
        /// otherwise uses <paramref name="notRecognizedToCode"/>,
        /// otherwise falls back to `ToString()`
        public static string ToCode(this object x, Func<object, string> notRecognizedToCode)
        {
            if (x == null)
                return "null";

            if (x is bool b)
                return b.ToCode();

            if (x is string s)
                return s.ToCode();

            if (x is Type t)
                return t.ToCode();

            var xTypeInfo = x.GetType().GetTypeInfo();
            if (xTypeInfo.IsEnum)
                return x.GetType().ToEnumValueCode(x);

            if (x is IEnumerable e)
            {
                var elemType = xTypeInfo.IsArray
                    ? xTypeInfo.GetElementType()
                    : xTypeInfo.GetGenericTypeParametersOrArguments().GetFirst();
                if (elemType != null)
                    return e.ToArrayInitializerCode(elemType, notRecognizedToCode);
            }

            if (xTypeInfo.IsPrimitive)
                return x.ToString();

            if (notRecognizedToCode != null)
                return notRecognizedToCode(x);
            
            return x.ToString();
        }
    }

    public class UnaryExpression : Expression
    {
        public override ExpressionType NodeType { get; }

        public override Type Type => Operand.Type;
        public readonly Expression Operand;

        public virtual MethodInfo Method => null;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted)
        {
            switch (NodeType)
            {
                case ExpressionType.ArrayLength:
                    return SysExpr.ArrayLength(Operand.ToExpression(ref exprsConverted));
                case ExpressionType.Convert:
                    return SysExpr.Convert(Operand.ToExpression(ref exprsConverted), Type, Method);
                case ExpressionType.Decrement:
                    return SysExpr.Decrement(Operand.ToExpression(ref exprsConverted));
                case ExpressionType.Increment:
                    return SysExpr.Increment(Operand.ToExpression(ref exprsConverted));
                case ExpressionType.IsFalse:
                    return SysExpr.IsFalse(Operand.ToExpression(ref exprsConverted));
                case ExpressionType.IsTrue:
                    return SysExpr.IsTrue(Operand.ToExpression(ref exprsConverted));
                case ExpressionType.Negate:
                    return SysExpr.Negate(Operand.ToExpression(ref exprsConverted));
                case ExpressionType.NegateChecked:
                    return SysExpr.NegateChecked(Operand.ToExpression(ref exprsConverted));
                case ExpressionType.OnesComplement:
                    return SysExpr.OnesComplement(Operand.ToExpression(ref exprsConverted));
                case ExpressionType.PostDecrementAssign:
                    return SysExpr.PostDecrementAssign(Operand.ToExpression(ref exprsConverted));
                case ExpressionType.PostIncrementAssign:
                    return SysExpr.PostIncrementAssign(Operand.ToExpression(ref exprsConverted));
                case ExpressionType.PreDecrementAssign:
                    return SysExpr.PreDecrementAssign(Operand.ToExpression(ref exprsConverted));
                case ExpressionType.PreIncrementAssign:
                    return SysExpr.PreIncrementAssign(Operand.ToExpression(ref exprsConverted));
                case ExpressionType.Quote:
                    return SysExpr.Quote(Operand.ToExpression(ref exprsConverted));
                case ExpressionType.UnaryPlus:
                    return SysExpr.UnaryPlus(Operand.ToExpression(ref exprsConverted));
                case ExpressionType.Unbox:
                    return SysExpr.Unbox(Operand.ToExpression(ref exprsConverted), Type);
                case ExpressionType.Throw:
                    return SysExpr.Throw(Operand.ToExpression(ref exprsConverted), Type);
                default:
                    throw new NotSupportedException("Cannot convert Expression to Expression of type " + NodeType);
            }
        }

        /// <inheritdoc />
        public override string CodeString
        {
            get
            {
                switch (NodeType)
                {
                    case ExpressionType.ArrayLength:
                        return $"ArrayLength({Operand.CodeString})";
                    case ExpressionType.Convert:
                        if (Method == null)
                            return $"Convert({Operand.CodeString}, {Type.ToCode()})";
                        var methodIndex = Method.DeclaringType.GetTypeInfo().DeclaredMethods.AsArray().GetFirstIndex(Method);
                        return $"Convert({Operand.CodeString}, {Type.ToCode()}, {Method.DeclaringType.ToCode()}.GetTypeInfo().DeclaredMethods.ToArray()[{methodIndex}])";
                    case ExpressionType.Decrement:
                        return $"Decrement({Operand.CodeString})";
                    case ExpressionType.Increment:
                        return $"Increment({Operand.CodeString})";
                    case ExpressionType.IsFalse:
                        return $"IsFalse({Operand.CodeString})";
                    case ExpressionType.IsTrue:
                        return $"IsTrue({Operand.CodeString})";
                    case ExpressionType.Negate:
                        return $"Negate({Operand.CodeString})";
                    case ExpressionType.NegateChecked:
                        return $"NegateChecked({Operand.CodeString})";
                    case ExpressionType.OnesComplement:
                        return $"OnesComplement({Operand.CodeString})";
                    case ExpressionType.PostDecrementAssign:
                        return $"PostDecrementAssign({Operand.CodeString})";
                    case ExpressionType.PostIncrementAssign:
                        return $"PostIncrementAssign({Operand.CodeString})";
                    case ExpressionType.PreDecrementAssign:
                        return $"PreDecrementAssign({Operand.CodeString})";
                    case ExpressionType.PreIncrementAssign:
                        return $"PreIncrementAssign({Operand.CodeString})";
                    case ExpressionType.Quote:
                        return $"Quote({Operand.CodeString})";
                    case ExpressionType.UnaryPlus:
                        return $"UnaryPlus({Operand.CodeString})";
                    case ExpressionType.Unbox:
                        return $"Unbox({Operand.CodeString}, {Type.ToCode()})";
                    case ExpressionType.Throw:
                        return $"Throw({Operand.CodeString}, {Type.ToCode()})";
                    default:
                        throw new NotSupportedException("Cannot convert Expression to Expression of type " + NodeType);
                }
            }
        }

        public UnaryExpression(ExpressionType nodeType, Expression operand)
        {
            NodeType = nodeType;
            Operand = operand;
        }
    }

    public class TypedUnaryExpression : UnaryExpression
    {
        public override Type Type { get; }

        public TypedUnaryExpression(ExpressionType nodeType, Expression operand, Type type) : base(nodeType, operand) =>
            Type = type;
    }

    public sealed class TypedUnaryExpression<T> : UnaryExpression
    {
        public override Type Type => typeof(T);

        public TypedUnaryExpression(ExpressionType nodeType, Expression operand) : base(nodeType, operand) { }
    }

    public sealed class ConvertWithMethodUnaryExpression : TypedUnaryExpression
    {
        public override MethodInfo Method { get; }
        public override Type Type => Method.ReturnType;

        public ConvertWithMethodUnaryExpression(ExpressionType nodeType, Expression operand, MethodInfo method) 
            : base(nodeType, operand, method.ReturnType) =>
            Method = method;

        public ConvertWithMethodUnaryExpression(ExpressionType nodeType, Expression operand, Type type, MethodInfo method)
            : base(nodeType, operand, type) =>
            Method = method;
    }

    public abstract class BinaryExpression : Expression
    {
        public override ExpressionType NodeType { get; }
        public override Type Type { get; }

        public readonly Expression Left, Right;

        public override string CodeString =>
            $"{Enum.GetName(typeof(ExpressionType), NodeType)}(" + NewLine + 
            $"{Left.CodeString}," + NewLine + 
            $"{Right.CodeString})";

        protected BinaryExpression(ExpressionType nodeType, Expression left, Expression right, Type type)
        {
            NodeType = nodeType;

            Left = left;
            Right = right;

            if (nodeType == ExpressionType.Equal ||
                nodeType == ExpressionType.NotEqual ||
                nodeType == ExpressionType.GreaterThan ||
                nodeType == ExpressionType.GreaterThanOrEqual ||
                nodeType == ExpressionType.LessThan ||
                nodeType == ExpressionType.LessThanOrEqual ||
                nodeType == ExpressionType.And ||
                nodeType == ExpressionType.AndAlso ||
                nodeType == ExpressionType.Or ||
                nodeType == ExpressionType.OrElse)
            {
                Type = typeof(bool);
            }
            else
                Type = type;
        }
    }

    public class TypeBinaryExpression : Expression
    {
        public override ExpressionType NodeType { get; }
        public override Type Type { get; }

        public Type TypeOperand { get; }

        public readonly Expression Expression;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) => 
            SysExpr.TypeIs(Expression.ToExpression(ref exprsConverted), TypeOperand);

        public override string CodeString => $"TypeIs({Expression.CodeString}, {TypeOperand.ToCode()})";

        internal TypeBinaryExpression(ExpressionType nodeType, Expression expression, Type typeOperand)
        {
            NodeType = nodeType;
            Expression = expression;
            Type = typeof(bool);
            TypeOperand = typeOperand;
        }
    }

    public sealed class SimpleBinaryExpression : BinaryExpression
    {
        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted)
        {
            switch (NodeType)
            {
                case ExpressionType.Add:
                    return SysExpr.Add(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.Subtract:
                    return SysExpr.Subtract(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.Multiply:
                    return SysExpr.Multiply(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.Divide:
                    return SysExpr.Divide(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.Power:
                    return SysExpr.Power(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.Coalesce:
                    return SysExpr.Coalesce(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.And:
                    return SysExpr.And(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.AndAlso:
                    return SysExpr.AndAlso(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.Or:
                    return SysExpr.Or(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.OrElse:
                    return SysExpr.OrElse(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.Equal:
                    return SysExpr.Equal(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.NotEqual:
                    return SysExpr.NotEqual(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.GreaterThan:
                    return SysExpr.GreaterThan(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.GreaterThanOrEqual:
                    return SysExpr.GreaterThanOrEqual(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.LessThan:
                    return SysExpr.LessThan(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.LessThanOrEqual:
                    return SysExpr.LessThanOrEqual(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                default:
                    throw new NotSupportedException($"Not a valid {NodeType} for arithmetic or boolean binary expression.");
            }
        }

        internal SimpleBinaryExpression(ExpressionType nodeType, Expression left, Expression right, Type type)
            : base(nodeType, left, right, type) { }
    }

    public class CoalesceConversionBinaryExpression : BinaryExpression
    {
        public readonly LambdaExpression Conversion;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            SysExpr.Coalesce(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted), Conversion.ToLambdaExpression());

        public override string CodeString =>
            $"Coalesce({Left.CodeString}, {Right.CodeString}, {Conversion.CodeString})";

        internal CoalesceConversionBinaryExpression(Expression left, Expression right, LambdaExpression conversion)
            : base(ExpressionType.Coalesce, left, right, null)
        {
            Conversion = conversion;
        }
    }

    public sealed class ArrayIndexExpression : BinaryExpression
    {
        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            SysExpr.ArrayIndex(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));

        internal ArrayIndexExpression(Expression left, Expression right, Type type)
            : base(ExpressionType.ArrayIndex, left, right, type) { }
    }

    public sealed class AssignBinaryExpression : BinaryExpression
    {
        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted)
        {
            switch (NodeType)
            {
                case ExpressionType.Assign:
                    return SysExpr.Assign(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.AddAssign:
                    return SysExpr.AddAssign(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.AddAssignChecked:
                    return SysExpr.AddAssignChecked(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.SubtractAssign:
                    return SysExpr.SubtractAssign(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.SubtractAssignChecked:
                    return SysExpr.SubtractAssignChecked(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.MultiplyAssign:
                    return SysExpr.MultiplyAssign(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.MultiplyAssignChecked:
                    return SysExpr.MultiplyAssignChecked(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.DivideAssign:
                    return SysExpr.DivideAssign(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.PowerAssign:
                    return SysExpr.PowerAssign(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.AndAssign:
                    return SysExpr.AndAssign(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                case ExpressionType.OrAssign:
                    return SysExpr.OrAssign(Left.ToExpression(ref exprsConverted), Right.ToExpression(ref exprsConverted));
                default:
                    throw new NotSupportedException($"Not a valid {NodeType} for Assign binary expression.");
            }
        }

        public override string CodeString =>
            $"{Enum.GetName(typeof(ExpressionType), NodeType)}({Left.CodeString}, {Right.CodeString})";

        internal AssignBinaryExpression(Expression left, Expression right, Type type)
            : base(ExpressionType.Assign, left, right, type) { }

        internal AssignBinaryExpression(ExpressionType expressionType, Expression left, Expression right, Type type)
            : base(expressionType, left, right, type) { }
    }

    public sealed class MemberInitExpression : Expression
    {
        public override ExpressionType NodeType => ExpressionType.MemberInit;
        public override Type Type => Expression.Type;

        public NewExpression NewExpression => Expression as NewExpression;

        public readonly Expression Expression;
        public readonly IReadOnlyList<MemberBinding> Bindings;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            SysExpr.MemberInit((System.Linq.Expressions.NewExpression)NewExpression.ToExpression(ref exprsConverted), 
                BindingsToExpressions(Bindings, ref exprsConverted));

        internal static System.Linq.Expressions.MemberBinding[] BindingsToExpressions(
            IReadOnlyList<MemberBinding> ms, ref LiveCountArray<LightAndSysExpr> exprsConverted)
        {
            if (ms.Count == 0)
                return Tools.Empty<System.Linq.Expressions.MemberBinding>();

            if (ms.Count == 1)
                return new[] { ms[0].ToMemberBinding(ref exprsConverted) };

            var result = new System.Linq.Expressions.MemberBinding[ms.Count];
            for (var i = 0; i < result.Length; ++i)
                result[i] = ms[i].ToMemberBinding(ref exprsConverted);
            return result;
        }

        public override string CodeString
        {
            get
            {
                var bindings = "";
                for (var i = 0; i < Bindings.Count; i++)
                {
                    if (i > 0)
                        bindings += "," + NewLine;
                    bindings += Bindings[i].CodeString;
                }
                return $"MemberInit({Expression.CodeString}," + NewLine +
                       $"{bindings})";
            }
        }

        internal MemberInitExpression(NewExpression newExpression, MemberBinding[] bindings)
            : this((Expression)newExpression, bindings) { }

        internal MemberInitExpression(Expression expression, MemberBinding[] bindings)
        {
            Expression = expression;
            Bindings = bindings ?? Tools.Empty<MemberBinding>();
        }
    }

    public sealed class ParameterExpression : Expression
    {
        public override ExpressionType NodeType => ExpressionType.Parameter;
        public override Type Type { get; }

        // todo: we need the version without this members
        public readonly string Name;
        public readonly bool IsByRef;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            SysExpr.Parameter(IsByRef ? Type.MakeByRefType() : Type, Name);

        public override string CodeString =>
            Name != null
                ? $"Parameter({Type.ToCode()}{(IsByRef ? ".MakeByRefType()" : "")}, \"{Name}\")"
                : $"Parameter({Type.ToCode()}{(IsByRef ? ".MakeByRefType()" : "")})";

        internal static System.Linq.Expressions.ParameterExpression[] ToParameterExpressions(
            IReadOnlyList<ParameterExpression> ps, ref LiveCountArray<LightAndSysExpr> exprsConverted)
        {
            if (ps.Count == 0)
                return Tools.Empty<System.Linq.Expressions.ParameterExpression>();

            if (ps.Count == 1)
                return new[] { (System.Linq.Expressions.ParameterExpression)ps[0].ToExpression(ref exprsConverted) };

            var result = new System.Linq.Expressions.ParameterExpression[ps.Count];
            for (var i = 0; i < result.Length; ++i)
                result[i] = (System.Linq.Expressions.ParameterExpression)ps[i].ToExpression(ref exprsConverted);
            return result;
        }

        internal ParameterExpression(Type type, string name, bool isByRef)
        {
            Type = type;
            Name = name;
            IsByRef = isByRef;
        }
    }

    public class ConstantExpression : Expression
    {
        public override ExpressionType NodeType => ExpressionType.Constant;
        public override Type Type => Value.GetType();

        public readonly object Value;

        internal ConstantExpression(object value) => Value = value;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> _) =>
            SysExpr.Constant(Value, Type);

        /// note: Change to your method to use in <see cref="CodeString"/> for spitting the C# code for the <see cref="Value"/>
        /// You may try to use `ObjectToCode` from `https://www.nuget.org/packages/ExpressionToCodeLib`
        public static Func<object, string> NotRecognizedValueToCode = x => x.ToString();

        public override string CodeString =>
            $"Constant({Value.ToCode(NotRecognizedValueToCode)}, {Type.ToCode()})";
    }

    public sealed class TypedConstantExpression : ConstantExpression
    {
        public override Type Type { get; }

        internal TypedConstantExpression(object value, Type type) : base(value) => Type = type;
    }

    public sealed class TypedConstantExpression<T> : ConstantExpression
    {
        public override Type Type => typeof(T);

        internal TypedConstantExpression(T value) : base(value) { }
    }

    public abstract class ArgumentsExpression : Expression
    {
        public readonly IReadOnlyList<Expression> Arguments;

        protected ArgumentsExpression(IReadOnlyList<Expression> arguments) => Arguments = arguments ?? Tools.Empty<Expression>();
    }

    public class NewExpression : Expression
    {
        public override ExpressionType NodeType => ExpressionType.New;
        public override Type Type => Constructor.DeclaringType;

        public readonly ConstructorInfo Constructor;

        public virtual int FewArgumentCount => 0;
        public virtual IReadOnlyList<Expression> Arguments => Tools.Empty<Expression>();

        internal NewExpression(ConstructorInfo constructor) => Constructor = constructor;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            SysExpr.New(Constructor, ToExpressions(Arguments, ref exprsConverted));

        public override string CodeString
        {
            get
            {
                var ctorIndex = Constructor.DeclaringType.GetTypeInfo().DeclaredConstructors.ToArray().GetFirstIndex(Constructor);
                return $"New({Type.ToCode()}.GetTypeInfo().DeclaredConstructors.ToArray()[{ctorIndex}]," + NewLine +
                       $"{ToParamsCode(Arguments)})";
            }
        }
    }

    public sealed class NewValueTypeExpression : NewExpression
    {
        public override Type Type { get; }

        internal NewValueTypeExpression(Type type) : base(null) => Type = type;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) => SysExpr.New(Type);

        public override string CodeString => $"New({Type.ToCode()})";
    }

    public sealed class OneArgumentNewExpression : NewExpression
    {
        public readonly Expression Argument;
        public override IReadOnlyList<Expression> Arguments => new[] { Argument };
        public override int FewArgumentCount => 1;

        internal OneArgumentNewExpression(ConstructorInfo constructor, Expression argument) : base(constructor) =>
            Argument = argument;
    }

    public sealed class TwoArgumentsNewExpression : NewExpression
    {
        public readonly Expression Argument0;
        public readonly Expression Argument1;

        public override IReadOnlyList<Expression> Arguments => new[] { Argument0, Argument1 };
        public override int FewArgumentCount => 2;

        internal TwoArgumentsNewExpression(ConstructorInfo constructor,
            Expression argument0, Expression argument1) : base(constructor)
        {
            Argument0 = argument0;
            Argument1 = argument1;
        }
    }

    public sealed class ThreeArgumentsNewExpression : NewExpression
    {
        public readonly Expression Argument0;
        public readonly Expression Argument1;
        public readonly Expression Argument2;

        public override IReadOnlyList<Expression> Arguments => new[] { Argument0, Argument1, Argument2 };
        public override int FewArgumentCount => 3;

        internal ThreeArgumentsNewExpression(ConstructorInfo constructor,
            Expression argument0, Expression argument1, Expression argument2) : base(constructor)
        {
            Argument0 = argument0;
            Argument1 = argument1;
            Argument2 = argument2;
        }
    }

    public sealed class FourArgumentsNewExpression : NewExpression
    {
        public readonly Expression Argument0;
        public readonly Expression Argument1;
        public readonly Expression Argument2;
        public readonly Expression Argument3;

        public override IReadOnlyList<Expression> Arguments => new[] { Argument0, Argument1, Argument2, Argument3 };
        public override int FewArgumentCount => 4;

        internal FourArgumentsNewExpression(ConstructorInfo constructor,
            Expression argument0, Expression argument1, Expression argument2, Expression argument3) : base(constructor)
        {
            Argument0 = argument0;
            Argument1 = argument1;
            Argument2 = argument2;
            Argument3 = argument3;
        }
    }

    public sealed class FiveArgumentsNewExpression : NewExpression
    {
        public readonly Expression Argument0;
        public readonly Expression Argument1;
        public readonly Expression Argument2;
        public readonly Expression Argument3;
        public readonly Expression Argument4;

        public override IReadOnlyList<Expression> Arguments => new[] { Argument0, Argument1, Argument2, Argument3, Argument4 };
        public override int FewArgumentCount => 5;

        internal FiveArgumentsNewExpression(ConstructorInfo constructor,
            Expression argument0, Expression argument1, Expression argument2, Expression argument3, Expression argument4) : base(constructor)
        {
            Argument0 = argument0;
            Argument1 = argument1;
            Argument2 = argument2;
            Argument3 = argument3;
            Argument4 = argument4;
        }
    }

    public sealed class ManyArgumentsNewExpression : NewExpression
    {
        public override IReadOnlyList<Expression> Arguments { get; }
        public override int FewArgumentCount => -1;

        internal ManyArgumentsNewExpression(ConstructorInfo constructor, IReadOnlyList<Expression> arguments) : base(constructor) =>
            Arguments = arguments;
    }

    public sealed class NewArrayExpression : ArgumentsExpression
    {
        public override ExpressionType NodeType { get; }
        public override Type Type { get; }

        // I made it a ICollection for now to use Arguments as input, without changing Arguments type
        public IReadOnlyList<Expression> Expressions => Arguments;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            NodeType == ExpressionType.NewArrayInit
                // ReSharper disable once AssignNullToNotNullAttribute
                ? SysExpr.NewArrayInit(Type.GetElementType(), ToExpressions(Arguments, ref exprsConverted))
                // ReSharper disable once AssignNullToNotNullAttribute
                : SysExpr.NewArrayBounds(Type.GetElementType(), ToExpressions(Arguments, ref exprsConverted));

        public override string CodeString =>
            NodeType == ExpressionType.NewArrayInit
                // ReSharper disable once AssignNullToNotNullAttribute
                ? $"NewArrayInit({Type.GetElementType().ToCode()}," + NewLine + $"{ToParamsCode(Arguments)})"
                // ReSharper disable once AssignNullToNotNullAttribute
                : $"NewArrayBounds({Type.GetElementType().ToCode()}," + NewLine + $"{ToParamsCode(Arguments)})";

        internal NewArrayExpression(ExpressionType expressionType, Type arrayType, IReadOnlyList<Expression> elements) : base(elements)
        {
            NodeType = expressionType;
            Type = arrayType;
        }
    }

    public class MethodCallExpression : Expression
    {
        public override ExpressionType NodeType => ExpressionType.Call;
        public override Type Type => Method.ReturnType;

        public virtual Expression Object => null;
        public virtual IReadOnlyList<Expression> Arguments => Tools.Empty<Expression>();
        public virtual int FewArgumentCount => 0;

        public readonly MethodInfo Method;

        internal MethodCallExpression(MethodInfo method) => Method = method;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            SysExpr.Call(Object?.ToExpression(ref exprsConverted), Method, 
                ToExpressions(Arguments, ref exprsConverted));

        public override string CodeString
        {
            get
            {
                var methodIndex = Method.DeclaringType.GetTypeInfo().GetDeclaredMethods(Method.Name).AsArray().GetFirstIndex(Method);
                return $"Call({Object?.CodeString ?? "null"}," + NewLine +
                       $"{Method.DeclaringType.ToCode()}.GetTypeInfo().GetDeclaredMethods(\"{Method.Name}\").ToArray()[{methodIndex}]," + NewLine + 
                       $"{ToParamsCode(Arguments)})";
            }
        }
    }

    public sealed class InstanceMethodCallExpression : MethodCallExpression
    {
        public override Expression Object { get; }

        internal InstanceMethodCallExpression(Expression instance, MethodInfo method) : base(method) =>
            Object = instance;
    }

    public class ManyArgumentsMethodCallExpression : MethodCallExpression
    {
        public override IReadOnlyList<Expression> Arguments { get; }
        public override int FewArgumentCount => -1;

        internal ManyArgumentsMethodCallExpression(MethodInfo method, IReadOnlyList<Expression> arguments) : base(method) =>
            Arguments = arguments;
    }

    public sealed class InstanceManyArgumentsMethodCallExpression : ManyArgumentsMethodCallExpression
    {
        public override Expression Object { get; }

        internal InstanceManyArgumentsMethodCallExpression(Expression instance, MethodInfo method, IReadOnlyList<Expression> arguments) 
            : base(method, arguments) => Object = instance;
    }

    public class OneArgumentMethodCallExpression : MethodCallExpression
    {
        public override IReadOnlyList<Expression> Arguments => new[] { Argument };
        public override int FewArgumentCount => 1;

        public readonly Expression Argument;

        internal OneArgumentMethodCallExpression(MethodInfo method, Expression argument) : base(method) => 
            Argument = argument;
    }

    public sealed class InstanceOneArgumentMethodCallExpression : OneArgumentMethodCallExpression
    {
        public override Expression Object { get; }

        internal InstanceOneArgumentMethodCallExpression(Expression instance, MethodInfo method, Expression argument) 
            : base(method, argument) => Object = instance;
    }

    public class TwoArgumentsMethodCallExpression : MethodCallExpression
    {
        public override IReadOnlyList<Expression> Arguments => new[] { Argument0, Argument1 };
        public override int FewArgumentCount => 2;

        public readonly Expression Argument0;
        public readonly Expression Argument1;

        internal TwoArgumentsMethodCallExpression(MethodInfo method, Expression argument0, Expression argument1) : base(method)
        {
            Argument0 = argument0;
            Argument1 = argument1;
        }
    }

    public sealed class InstanceTwoArgumentsMethodCallExpression : TwoArgumentsMethodCallExpression
    {
        public override Expression Object { get; }

        internal InstanceTwoArgumentsMethodCallExpression(Expression instance, MethodInfo method,
            Expression argument0, Expression argument1) : base(method, argument0, argument1) => Object = instance;
    }

    public class ThreeArgumentsMethodCallExpression : MethodCallExpression
    {
        public override IReadOnlyList<Expression> Arguments => new[] { Argument0, Argument1, Argument2 };
        public override int FewArgumentCount => 3;

        public readonly Expression Argument0;
        public readonly Expression Argument1;
        public readonly Expression Argument2;

        internal ThreeArgumentsMethodCallExpression(MethodInfo method, 
            Expression argument0, Expression argument1, Expression argument2) : base(method)
        {
            Argument0 = argument0;
            Argument1 = argument1;
            Argument2 = argument2;
        }
    }

    public sealed class InstanceThreeArgumentsMethodCallExpression : ThreeArgumentsMethodCallExpression
    {
        public override Expression Object { get; }

        internal InstanceThreeArgumentsMethodCallExpression(Expression instance, MethodInfo method,
            Expression argument0, Expression argument1, Expression argument2)
            : base(method, argument0, argument1, argument2) => Object = instance;
    }

    public class FourArgumentsMethodCallExpression : MethodCallExpression
    {
        public override IReadOnlyList<Expression> Arguments => new[] { Argument0, Argument1, Argument2, Argument3 };
        public override int FewArgumentCount => 4;

        public readonly Expression Argument0;
        public readonly Expression Argument1;
        public readonly Expression Argument2;
        public readonly Expression Argument3;

        internal FourArgumentsMethodCallExpression(MethodInfo method,
            Expression argument0, Expression argument1, Expression argument2, Expression argument3) : base(method)
        {
            Argument0 = argument0;
            Argument1 = argument1;
            Argument2 = argument2;
            Argument3 = argument3;
        }
    }

    public sealed class InstanceFourArgumentsMethodCallExpression : FourArgumentsMethodCallExpression
    {
        public override Expression Object { get; }

        internal InstanceFourArgumentsMethodCallExpression(Expression instance, MethodInfo method,
            Expression argument0, Expression argument1, Expression argument2, Expression argument3)
            : base(method, argument0, argument1, argument2, argument3) => Object = instance;
    }

    public class FiveArgumentsMethodCallExpression : MethodCallExpression
    {
        public override IReadOnlyList<Expression> Arguments => new[] { Argument0, Argument1, Argument2, Argument3, Argument4 };
        public override int FewArgumentCount => 5;

        public readonly Expression Argument0;
        public readonly Expression Argument1;
        public readonly Expression Argument2;
        public readonly Expression Argument3;
        public readonly Expression Argument4;

        internal FiveArgumentsMethodCallExpression(MethodInfo method,
            Expression argument0, Expression argument1, Expression argument2, Expression argument3, Expression argument4) 
            : base(method)
        {
            Argument0 = argument0;
            Argument1 = argument1;
            Argument2 = argument2;
            Argument3 = argument3;
            Argument4 = argument4;
        }
    }

    public sealed class InstanceFiveArgumentsMethodCallExpression : FiveArgumentsMethodCallExpression
    {
        public override Expression Object { get; }

        internal InstanceFiveArgumentsMethodCallExpression(Expression instance, MethodInfo method,
            Expression argument0, Expression argument1, Expression argument2, Expression argument3, Expression argument4) 
            : base(method, argument0, argument1, argument2, argument3, argument4) => Object = instance;
    }

    public abstract class MemberExpression : Expression
    {
        public override ExpressionType NodeType => ExpressionType.MemberAccess;
        public readonly MemberInfo Member;
        public readonly Expression Expression;

        protected MemberExpression(Expression expression, MemberInfo member)
        {
            Expression = expression;
            Member = member;
        }
    }

    // todo: specialize to 2 class - with and without object expression
    public sealed class PropertyExpression : MemberExpression
    {
        public override Type Type => PropertyInfo.PropertyType;
        public PropertyInfo PropertyInfo => (PropertyInfo)Member;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            SysExpr.Property(Expression?.ToExpression(ref exprsConverted), PropertyInfo);

        public override string CodeString
        {
            get
            {
                var propIndex = PropertyInfo.DeclaringType.GetTypeInfo().DeclaredProperties.AsArray().GetFirstIndex(PropertyInfo);
                return $"Property({Expression?.CodeString ?? "null"}," + NewLine + 
                       $"{PropertyInfo.DeclaringType.ToCode()}.GetTypeInfo().DeclaredProperties.ToArray()[{propIndex}])";
            }
        }

        internal PropertyExpression(Expression instance, PropertyInfo property) :
            base(instance, property) { }
    }

    // todo: specialize to 2 class - with and without object expression
    public sealed class FieldExpression : MemberExpression
    {
        public override Type Type => FieldInfo.FieldType;
        public FieldInfo FieldInfo => (FieldInfo)Member;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            SysExpr.Field(Expression?.ToExpression(ref exprsConverted), FieldInfo);

        public override string CodeString
        {
            get
            {
                var fieldIndex = FieldInfo.DeclaringType.GetTypeInfo().DeclaredFields.AsArray().GetFirstIndex(FieldInfo);
                return $"Field({Expression?.CodeString ?? "null"}," + NewLine +
                       $"{FieldInfo.DeclaringType.ToCode()}.GetTypeInfo().DeclaredProperties.ToArray()[{fieldIndex}])";
            }
        }

        internal FieldExpression(Expression instance, FieldInfo field)
            : base(instance, field) { }
    }

    public abstract class MemberBinding
    {
        public readonly MemberInfo Member;

        public abstract MemberBindingType BindingType { get; }
        public abstract string CodeString { get; }

        internal abstract System.Linq.Expressions.MemberBinding ToMemberBinding(ref LiveCountArray<Expression.LightAndSysExpr> exprsConverted);

        internal MemberBinding(MemberInfo member)
        {
            Member = member;
        }
    }

    public sealed class MemberAssignment : MemberBinding
    {
        public readonly Expression Expression;

        public override MemberBindingType BindingType => MemberBindingType.Assignment;

        internal override System.Linq.Expressions.MemberBinding ToMemberBinding(ref LiveCountArray<Expression.LightAndSysExpr> exprsConverted) =>
            SysExpr.Bind(Member, Expression.ToExpression(ref exprsConverted));

        public override string CodeString
        {
            get
            {
                var memberIndex = Member.DeclaringType.GetTypeInfo().DeclaredMembers.AsArray().GetFirstIndex(Member);
                return $"Bind({Member.DeclaringType.ToCode()}.GetTypeInfo().DeclaredMembers.ToArray()[{memberIndex}]," + NewLine + 
                       $"{Expression.CodeString})";
            }
        }

        internal MemberAssignment(MemberInfo member, Expression expression) : base(member)
        {
            Expression = expression;
        }
    }

    public sealed class InvocationExpression : ArgumentsExpression
    {
        public override ExpressionType NodeType => ExpressionType.Invoke;
        public override Type Type { get; }

        public readonly Expression Expression;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            SysExpr.Invoke(Expression.ToExpression(ref exprsConverted), ToExpressions(Arguments, ref exprsConverted));

        public override string CodeString => 
            $"Invoke({Expression.CodeString}," + NewLine + 
            $"{ToParamsCode(Arguments)})";

        internal InvocationExpression(Expression expression, IReadOnlyList<Expression> arguments, Type type) : base(arguments)
        {
            Expression = expression;
            Type = type;
        }
    }

    public sealed class DefaultExpression : Expression
    {
        public override ExpressionType NodeType => ExpressionType.Default;
        public override Type Type { get; }

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            Type == typeof(void)? SysExpr.Empty() : SysExpr.Default(Type);

        public override string CodeString =>
            Type == typeof(void) ? "Empty()" : $"Default({Type.ToCode()})";

        internal DefaultExpression(Type type)
        {
            Type = type;
        }
    }

    public sealed class ConditionalExpression : Expression
    {
        public override ExpressionType NodeType => ExpressionType.Conditional;
        public override Type Type => _type ?? IfTrue.Type;

        public readonly Expression Test;
        public readonly Expression IfTrue;
        public readonly Expression IfFalse;
        private readonly Type _type;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            _type == null
                ? SysExpr.Condition(Test.ToExpression(ref exprsConverted), IfTrue.ToExpression(ref exprsConverted), IfFalse.ToExpression(ref exprsConverted))
                : SysExpr.Condition(Test.ToExpression(ref exprsConverted), IfTrue.ToExpression(ref exprsConverted), IfFalse.ToExpression(ref exprsConverted), _type);

        public override string CodeString =>
            _type == null
                ? $"Condition({Test.CodeString}," + NewLine + $"{IfTrue.CodeString}," + NewLine + $"{IfFalse.CodeString})"
                : $"Condition({Test.CodeString}," + NewLine + $"{IfTrue.CodeString}," + NewLine + $"{IfFalse.CodeString}," + NewLine + $"{_type.ToCode()})";

        internal ConditionalExpression(Expression test, Expression ifTrue, Expression ifFalse, Type type = null)
        {
            Test = test;
            IfTrue = ifTrue;
            IfFalse = ifFalse;
            _type = type;
        }
    }

    /// <summary>For indexer property or array access.</summary>
    public sealed class IndexExpression : ArgumentsExpression
    {
        public override ExpressionType NodeType => ExpressionType.Index;
        public override Type Type => Indexer != null ? Indexer.PropertyType : Object.Type.GetElementType();

        public readonly Expression Object;
        public readonly PropertyInfo Indexer;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            SysExpr.MakeIndex(Object.ToExpression(ref exprsConverted), Indexer, ToExpressions(Arguments, ref exprsConverted));

        public override string CodeString
        {
            get
            {
                var propIndex = Indexer.DeclaringType.GetTypeInfo().DeclaredProperties.AsArray().GetFirstIndex(Indexer);
                return $"MakeIndex({Object.CodeString}," + NewLine + 
                       $"{Indexer.DeclaringType.ToCode()}.GetTypeInfo().DeclaredProperties.ToArray()[{propIndex}], {ToParamsCode(Arguments)})";
            }
        }

        internal IndexExpression(Expression @object, PropertyInfo indexer, IReadOnlyList<Expression> arguments)
            : base(arguments)
        {
            Object = @object;
            Indexer = indexer;
        }
    }

    /// <summary>Optimized version for the specific block structure</summary> 
    public sealed class OneVariableTwoExpressionBlockExpression : Expression
    {
        public static explicit operator BlockExpression(OneVariableTwoExpressionBlockExpression x) =>
            Block(new[] { x.Variable }, x.Expression1, x.Expression2);

        public override ExpressionType NodeType => ExpressionType.Block;
        public override Type Type => Expression2.Type;

        public new readonly ParameterExpression Variable;
        public readonly Expression Expression1;
        public readonly Expression Expression2;
        public Expression Result => Expression2;

        internal OneVariableTwoExpressionBlockExpression(ParameterExpression variable, Expression expression1, Expression expression2)
        {
            Variable = variable;
            Expression1 = expression1;
            Expression2 = expression2;
        }

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            ((BlockExpression)this).CreateSysExpression(ref exprsConverted);

        public override string CodeString => ((BlockExpression)this).CodeString;
    }

    public sealed class BlockExpression : Expression
    {
        public override ExpressionType NodeType => ExpressionType.Block;
        public override Type Type { get; }

        public readonly IReadOnlyList<ParameterExpression> Variables;
        public readonly IReadOnlyList<Expression> Expressions;
        public Expression Result => Expressions[Expressions.Count - 1];

        internal BlockExpression(Type type, IReadOnlyList<ParameterExpression> variables, IReadOnlyList<Expression> expressions)
        {
            Variables = variables ?? Tools.Empty<ParameterExpression>();
            Expressions = expressions ?? Tools.Empty<Expression>();
            Type = type;
        }

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            SysExpr.Block(Type,
                ParameterExpression.ToParameterExpressions(Variables, ref exprsConverted),
                ToExpressions(Expressions, ref exprsConverted));

        public override string CodeString =>
            "Block(" + NewLine +
                $"{Type.ToCode()}," + NewLine +
                $"new ParameterExpression[]{{ {(Variables.Count == 0 ? "" : ToParamsCode(Variables))} }}," + NewLine +
                $"{ToParamsCode(Expressions)})";
    }

    public sealed class LoopExpression : Expression
    {
        public override ExpressionType NodeType => ExpressionType.Loop;

        public override Type Type => typeof(void);

        public readonly Expression Body;
        public readonly LabelTarget BreakLabel;
        public readonly LabelTarget ContinueLabel;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            BreakLabel == null ? SysExpr.Loop(Body.ToExpression(ref exprsConverted)) :
            ContinueLabel == null ? SysExpr.Loop(Body.ToExpression(ref exprsConverted), BreakLabel) :
            SysExpr.Loop(Body.ToExpression(ref exprsConverted), BreakLabel, ContinueLabel);

        public override string CodeString =>
            BreakLabel == null ? $"Loop({Body.CodeString})" :
            ContinueLabel == null ? $"Loop({Body.CodeString}," + NewLine + "Label(\"break\"))" :
            $"Loop({Body.CodeString}," + NewLine + "Label(\"break\"), Label(\"continue\"))";

        internal LoopExpression(Expression body, LabelTarget breakLabel, LabelTarget continueLabel)
        {
            Body = body;
            BreakLabel = breakLabel;
            ContinueLabel = continueLabel;
        }
    }

    public sealed class TryExpression : Expression
    {
        public override ExpressionType NodeType => ExpressionType.Try;
        public override Type Type => Body.Type;

        public readonly Expression Body;
        public IReadOnlyList<CatchBlock> Handlers => _handlers;
        private readonly CatchBlock[] _handlers;
        public readonly Expression Finally;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            Finally == null ? 
                SysExpr.TryCatch(Body.ToExpression(ref exprsConverted), 
                    ToCatchBlocks(_handlers, ref exprsConverted)) :
            Handlers == null ? 
                SysExpr.TryFinally(Body.ToExpression(ref exprsConverted), 
                    Finally.ToExpression(ref exprsConverted)) :
                SysExpr.TryCatchFinally(Body.ToExpression(ref exprsConverted), 
                    Finally.ToExpression(ref exprsConverted), ToCatchBlocks(_handlers, ref exprsConverted));

        private static System.Linq.Expressions.CatchBlock ToCatchBlock(
            ref CatchBlock cb, ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            SysExpr.MakeCatchBlock(cb.Test, 
                (System.Linq.Expressions.ParameterExpression)cb.Variable?.ToExpression(ref exprsConverted), 
                cb.Body.ToExpression(ref exprsConverted),
                cb.Filter?.ToExpression(ref exprsConverted));

        private static System.Linq.Expressions.CatchBlock[] ToCatchBlocks(
            CatchBlock[] hs, ref LiveCountArray<LightAndSysExpr> exprsConverted)
        {
            if (hs == null)
                return Tools.Empty<System.Linq.Expressions.CatchBlock>();
            var catchBlocks = new System.Linq.Expressions.CatchBlock[hs.Length];
            for (var i = 0; i < hs.Length; ++i)
                catchBlocks[i] = ToCatchBlock(ref hs[i], ref exprsConverted);
            return catchBlocks;
        }

        public override string CodeString =>
            Finally == null ? $"TryCatch({Body.CodeString}," + NewLine + $"{ToCatchBlocksCode(Handlers)})" :
            Handlers == null ? $"TryFinally({Body.CodeString}, " + NewLine + $"{Finally.CodeString})" :
            $"TryCatchFinally({Body.CodeString}," + NewLine + $"{Finally.CodeString}," + NewLine + $"{ToCatchBlocksCode(Handlers)})";

        private static string ToCatchBlocksCode(IReadOnlyList<CatchBlock> hs)
        {
            if (hs.Count == 0)
                return "new CatchBlock[0]";

            var s = "";
            for (var i = 0; i < hs.Count; i++)
            {
                if (i > 0)
                    s += ", " + NewLine;
                s += hs[i].CodeString;
            }

            return s;
        }

        internal TryExpression(Expression body, Expression @finally, CatchBlock[] handlers)
        {
            Body = body;
            _handlers = handlers;
            Finally = @finally;
        }
    }

    public struct CatchBlock
    {
        public readonly ParameterExpression Variable;
        public readonly Expression Body;
        public readonly Expression Filter;
        public readonly Type Test;

        internal CatchBlock(ParameterExpression variable, Expression body, Expression filter, Type test)
        {
            Variable = variable;
            Body = body;
            Filter = filter;
            Test = test;
        }

        internal string CodeString =>
            $"MakeCatchBlock({Test.ToCode()}, {Variable?.CodeString ?? "null"}," + NewLine + 
            $"{Body.CodeString}," + NewLine + 
            $"{Filter?.CodeString ?? "null"})";
    }

    public sealed class LabelExpression : Expression
    {
        public override ExpressionType NodeType => ExpressionType.Label;
        public override Type Type => Target.Type;

        public readonly LabelTarget Target;
        public readonly Expression DefaultValue;

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            DefaultValue == null ? SysExpr.Label(Target) : SysExpr.Label(Target, DefaultValue.ToExpression(ref exprsConverted));

        // todo: Introduce proper LabelTarget instead of system one
        public override string CodeString =>
            DefaultValue == null ? $"Label({Target})" : $"Label({Target}, {DefaultValue.CodeString})";

        internal LabelExpression(LabelTarget target, Expression defaultValue)
        {
            Target = target;
            DefaultValue = defaultValue;
        }
    }

    public sealed class GotoExpression : Expression
    {
        public override ExpressionType NodeType => ExpressionType.Goto;
        public override Type Type { get; }

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            Value == null 
            ? SysExpr.Goto(Target, Type) 
            : SysExpr.Goto(Target, Value.ToExpression(ref exprsConverted), Type);

        public override string CodeString =>
            Value == null 
                ? $"Goto({Target}, {Type.ToCode()})" 
                : $"Goto({Target}, {Value.CodeString}, {Type.ToCode()})";

        public readonly Expression Value;
        public readonly LabelTarget Target;
        public readonly GotoExpressionKind Kind;

        internal GotoExpression(GotoExpressionKind kind, LabelTarget target, Expression value, Type type)
        {
            Type = type;
            Kind = kind;
            Value = value;
            Target = target;
        }
    }

    public struct SwitchCase
    {
        public readonly IReadOnlyList<Expression> TestValues;
        public readonly Expression Body;

        public string CodeString =>
            $"SwitchCase({Body.CodeString}, {Expression.ToParamsCode(TestValues)})";

        public SwitchCase(Expression body, IEnumerable<Expression> testValues)
        {
            Body = body;
            TestValues = testValues.AsReadOnlyList();
        }
    }

    public class SwitchExpression : Expression
    {
        public override ExpressionType NodeType { get; }
        public override Type Type { get; }

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) => 
            SysExpr.Switch(SwitchValue.ToExpression(ref exprsConverted), 
                DefaultBody.ToExpression(ref exprsConverted), Comparison, 
                ToSwitchCaseExpressions(_cases, ref exprsConverted));

        public override string CodeString
        {
            get
            {
                var methodIndex = Comparison.DeclaringType.GetTypeInfo().DeclaredMethods.AsArray().GetFirstIndex(Comparison);
                return $"Switch({SwitchValue.CodeString}," + NewLine + 
                       $"{DefaultBody.CodeString}," + NewLine + 
                       $"{Comparison.DeclaringType.ToCode()}.GetTypeInfo().DeclaredMethods.ToArray()[{methodIndex}]," + NewLine + 
                       $"{ToSwitchCasesCode(_cases)})";
            }
        }

        internal static System.Linq.Expressions.SwitchCase ToSwitchCase(ref SwitchCase sw, ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            SysExpr.SwitchCase(sw.Body.ToExpression(ref exprsConverted), ToExpressions(sw.TestValues, ref exprsConverted));

        internal static System.Linq.Expressions.SwitchCase[] ToSwitchCaseExpressions(
            SwitchCase[] sw, ref LiveCountArray<LightAndSysExpr> exprsConverted)
        {
            if (sw.Length == 0)
                return Tools.Empty<System.Linq.Expressions.SwitchCase>();

            var result = new System.Linq.Expressions.SwitchCase[sw.Length];
            for (var i = 0; i < result.Length; ++i)
                result[i] = ToSwitchCase(ref sw[i], ref exprsConverted);
            return result;
        }

        internal static string ToSwitchCasesCode(IReadOnlyList<SwitchCase> items)
        {
            if (items.Count == 0)
                return "new SwitchCase[0]";

            var s = "";
            for (var i = 0; i < items.Count; i++)
            {
                if (i > 0)
                    s += "," + NewLine;
                s += items[i].CodeString;
            }

            return s;
        }

        public readonly Expression SwitchValue;
        public IReadOnlyList<SwitchCase> Cases => _cases;
        private readonly SwitchCase[] _cases;
        public readonly Expression DefaultBody;
        public readonly MethodInfo Comparison;

        public SwitchExpression(Type type, Expression switchValue, Expression defaultBody, MethodInfo comparison, SwitchCase[] cases)
        {
            NodeType = ExpressionType.Switch;
            Type = type;
            SwitchValue = switchValue;
            DefaultBody = defaultBody;
            Comparison = comparison;
            _cases = cases;
        }
    }

    public class LambdaExpression : Expression
    {
        public override ExpressionType NodeType => ExpressionType.Lambda;
        public override Type Type { get; }

        public readonly Type ReturnType;
        public readonly Expression Body;
        public virtual IReadOnlyList<ParameterExpression> Parameters => Tools.Empty<ParameterExpression>();

        public System.Linq.Expressions.LambdaExpression ToLambdaExpression() =>
            (System.Linq.Expressions.LambdaExpression)ToExpression();

        internal override SysExpr CreateSysExpression(ref LiveCountArray<LightAndSysExpr> exprsConverted) =>
            SysExpr.Lambda(Type, Body.ToExpression(ref exprsConverted), ParameterExpression.ToParameterExpressions(Parameters, ref exprsConverted));

        public override string CodeString =>
            $"Lambda({Type.ToCode()}," + NewLine +
            $"{Body.CodeString}," + NewLine +
            $"{ToParamsCode(Parameters)})";

        internal LambdaExpression(Type delegateType, Expression body, Type returnType)
        {
            Body = body;
            ReturnType = returnType;

            if (delegateType != null && delegateType != typeof(Delegate))
                Type = delegateType;
            else
                Type = Tools.GetFuncOrActionType(Tools.Empty<Type>(), ReturnType);
        }
    }

    public sealed class ManyParametersLambdaExpression : LambdaExpression
    {
        public override IReadOnlyList<ParameterExpression> Parameters { get; }

        internal ManyParametersLambdaExpression(Type delegateType, Expression body, IReadOnlyList<ParameterExpression> parameters, Type returnType)
         : base(delegateType, body, returnType) => Parameters = parameters;
    }

    public class Expression<TDelegate> : LambdaExpression
    {
        public new System.Linq.Expressions.Expression<TDelegate> ToLambdaExpression()
        {
            var exprsConverted = new LiveCountArray<LightAndSysExpr>(Tools.Empty<LightAndSysExpr>());
            return SysExpr.Lambda<TDelegate>(Body.ToExpression(ref exprsConverted),
                ParameterExpression.ToParameterExpressions(Parameters, ref exprsConverted));
        }

        internal Expression(Expression body, Type returnType)
            : base(typeof(TDelegate), body, returnType) { }
    }

    public sealed class ManyParametersExpression<TDelegate> : Expression<TDelegate>
    {
        public override IReadOnlyList<ParameterExpression> Parameters { get; }

        internal ManyParametersExpression(Expression body, IReadOnlyList<ParameterExpression> parameters, Type returnType)
            : base(body, returnType) => Parameters = parameters;
    }
}

#endif
