/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Apache License, Version 2.0, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Apache License, Version 2.0.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Dynamic.Utils;
using System.Reflection;

#if SILVERLIGHT
using System.Core;
#endif

#if CLR2
namespace Microsoft.Scripting.Ast {
#else
namespace System.Linq.Expressions {
#endif
    /// <summary>
    /// Represents an expression that applies a delegate or lambda expression to a list of argument expressions.
    /// </summary>
#if !SILVERLIGHT
    [DebuggerTypeProxy(typeof(Expression.InvocationExpressionProxy))]
#endif
    public sealed class InvocationExpression : Expression, IArgumentProvider {
        private IList<Expression> _arguments;
        private readonly Expression _lambda;
        private readonly Type _returnType;

        internal InvocationExpression(Expression lambda, IList<Expression> arguments, Type returnType) {
            _lambda = lambda;
            _arguments = arguments;
            _returnType = returnType;
        }

        /// <summary>
        /// Gets the static type of the expression that this <see cref="Expression" /> represents.
        /// </summary>
        /// <returns>The <see cref="Type"/> that represents the static type of the expression.</returns>
        public sealed override Type Type {
            get { return _returnType; }
        }

        /// <summary>
        /// Returns the node type of this Expression. Extension nodes should return
        /// ExpressionType.Extension when overriding this method.
        /// </summary>
        /// <returns>The <see cref="ExpressionType"/> of the expression.</returns>
        public sealed override ExpressionType NodeType {
            get { return ExpressionType.Invoke; }
        }

        /// <summary>
        /// Gets the delegate or lambda expression to be applied.
        /// </summary>
        public Expression Expression {
            get { return _lambda; }
        }

        /// <summary>
        /// Gets the arguments that the delegate or lambda expression is applied to.
        /// </summary>
        public ReadOnlyCollection<Expression> Arguments {
            get { return ReturnReadOnly(ref _arguments); }
        }

        /// <summary>
        /// Creates a new expression that is like this one, but using the
        /// supplied children. If all of the children are the same, it will
        /// return this expression.
        /// </summary>
        /// <param name="expression">The <see cref="Expression" /> property of the result.</param>
        /// <param name="arguments">The <see cref="Arguments" /> property of the result.</param>
        /// <returns>This expression if no children changed, or an expression with the updated children.</returns>
        public InvocationExpression Update(Expression expression, IEnumerable<Expression> arguments) {
            if (expression == Expression && arguments == Arguments) {
                return this;
            }

            return Expression.Invoke(expression, arguments);
        }

        Expression IArgumentProvider.GetArgument(int index) {
            return _arguments[index];
        }

        int IArgumentProvider.ArgumentCount {
            get {
                return _arguments.Count;
            }
        }

        /// <summary>
        /// Dispatches to the specific visit method for this node type.
        /// </summary>
        protected internal override Expression Accept(ExpressionVisitor visitor) {
            return visitor.VisitInvocation(this);
        }

        internal InvocationExpression Rewrite(Expression lambda, Expression[] arguments) {
            Debug.Assert(lambda != null);
            Debug.Assert(arguments == null || arguments.Length == _arguments.Count);

            return Expression.Invoke(lambda, arguments ?? _arguments);
        }

        internal LambdaExpression LambdaOperand {
            get {
                return (_lambda.NodeType == ExpressionType.Quote)
                    ? (LambdaExpression)((UnaryExpression)_lambda).Operand
                    : (_lambda as LambdaExpression);
            }
        }
    }

    public partial class Expression {

        ///<summary>
        ///Creates an <see cref="T:System.Linq.Expressions.InvocationExpression" /> that 
        ///applies a delegate or lambda expression to a list of argument expressions.
        ///</summary>
        ///<returns>
        ///An <see cref="T:System.Linq.Expressions.InvocationExpression" /> that 
        ///applies the specified delegate or lambda expression to the provided arguments.
        ///</returns>
        ///<param name="expression">
        ///An <see cref="T:System.Linq.Expressions.Expression" /> that represents the delegate
        ///or lambda expression to be applied.
        ///</param>
        ///<param name="arguments">
        ///An array of <see cref="T:System.Linq.Expressions.Expression" /> objects
        ///that represent the arguments that the delegate or lambda expression is applied to.
        ///</param>
        ///<exception cref="T:System.ArgumentNullException">
        ///<paramref name="expression" /> is null.</exception>
        ///<exception cref="T:System.ArgumentException">
        ///<paramref name="expression" />.Type does not represent a delegate type or an <see cref="T:System.Linq.Expressions.Expression`1" />.-or-The <see cref="P:System.Linq.Expressions.Expression.Type" /> property of an element of <paramref name="arguments" /> is not assignable to the type of the corresponding parameter of the delegate represented by <paramref name="expression" />.</exception>
        ///<exception cref="T:System.InvalidOperationException">
        ///<paramref name="arguments" /> does not contain the same number of elements as the list of parameters for the delegate represented by <paramref name="expression" />.</exception>
        public static InvocationExpression Invoke(Expression expression, params Expression[] arguments) {
            return Invoke(expression, (IEnumerable<Expression>)arguments);
        }

        ///<summary>
        ///Creates an <see cref="T:System.Linq.Expressions.InvocationExpression" /> that 
        ///applies a delegate or lambda expression to a list of argument expressions.
        ///</summary>
        ///<returns>
        ///An <see cref="T:System.Linq.Expressions.InvocationExpression" /> that 
        ///applies the specified delegate or lambda expression to the provided arguments.
        ///</returns>
        ///<param name="expression">
        ///An <see cref="T:System.Linq.Expressions.Expression" /> that represents the delegate
        ///or lambda expression to be applied.
        ///</param>
        ///<param name="arguments">
        ///An <see cref="T:System.Collections.Generic.IEnumerable`1" /> of <see cref="T:System.Linq.Expressions.Expression" /> objects
        ///that represent the arguments that the delegate or lambda expression is applied to.
        ///</param>
        ///<exception cref="T:System.ArgumentNullException">
        ///<paramref name="expression" /> is null.</exception>
        ///<exception cref="T:System.ArgumentException">
        ///<paramref name="expression" />.Type does not represent a delegate type or an <see cref="T:System.Linq.Expressions.Expression`1" />.-or-The <see cref="P:System.Linq.Expressions.Expression.Type" /> property of an element of <paramref name="arguments" /> is not assignable to the type of the corresponding parameter of the delegate represented by <paramref name="expression" />.</exception>
        ///<exception cref="T:System.InvalidOperationException">
        ///<paramref name="arguments" /> does not contain the same number of elements as the list of parameters for the delegate represented by <paramref name="expression" />.</exception>
        public static InvocationExpression Invoke(Expression expression, IEnumerable<Expression> arguments) {
            RequiresCanRead(expression, "expression");

            var args = arguments.ToReadOnly();
            var mi = GetInvokeMethod(expression);
            ValidateArgumentTypes(mi, ExpressionType.Invoke, ref args);
            return new InvocationExpression(expression, args, mi.ReturnType);
        }

        /// <summary>
        /// Gets the delegate's Invoke method; used by InvocationExpression.
        /// </summary>
        /// <param name="expression">The expression to be invoked.</param>
        internal static MethodInfo GetInvokeMethod(Expression expression) {
            Type delegateType = expression.Type;
            if (!expression.Type.IsSubclassOf(typeof(MulticastDelegate))) {
                Type exprType = TypeUtils.FindGenericType(typeof(Expression<>), expression.Type);
                if (exprType == null) {
                    throw Error.ExpressionTypeNotInvocable(expression.Type);
                }
                delegateType = exprType.GetGenericArguments()[0];
            }

            return delegateType.GetMethod("Invoke");
        }
    }
}
