/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, 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 Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Dynamic;
using System.Diagnostics;
using Microsoft.Scripting.Utils;
using System.Collections;
using Microsoft.Scripting.Runtime;

namespace Microsoft.Scripting.Actions.Calls {
    public sealed class ActualArguments {
        private readonly IList<DynamicMetaObject> _args;     
        private readonly IList<DynamicMetaObject> _namedArgs;
        private readonly IList<string> _names;             
        
        // Index into _args array indicating the first post-splat argument or -1 of there are no splatted arguments.
        // For call site f(a,b,*c,d) and preSplatLimit == 1 and postSplatLimit == 2
        // args would be (a,b,c[0],c[n-2],c[n-1],d) with splat index 3, where n = c.Count.
        private readonly int _splatIndex;
        private readonly int _firstSplattedArg;
        private readonly int _collapsedCount;

        // The number of hidden arguments (used for error reporting).
        private readonly int _hiddenCount;

        public ActualArguments(IList<DynamicMetaObject> args, IList<DynamicMetaObject> namedArgs, IList<string> argNames,
            int hiddenCount, int collapsedCount, int firstSplattedArg, int splatIndex) {

            ContractUtils.RequiresNotNullItems(args, "args");
            ContractUtils.RequiresNotNullItems(namedArgs, "namedArgs");
            ContractUtils.RequiresNotNullItems(argNames, "argNames");
            ContractUtils.Requires(namedArgs.Count == argNames.Count);

            ContractUtils.Requires(splatIndex == -1 || firstSplattedArg == -1 || firstSplattedArg >= 0 && firstSplattedArg <= splatIndex);
            ContractUtils.Requires(splatIndex == -1 || splatIndex >= 0);
            ContractUtils.Requires(collapsedCount >= 0);
            ContractUtils.Requires(hiddenCount >= 0);

            _args = args;
            _namedArgs = namedArgs;
            _names = argNames;
            _collapsedCount = collapsedCount;
            _splatIndex = collapsedCount > 0 ? splatIndex : -1;
            _firstSplattedArg = firstSplattedArg;
            _hiddenCount = hiddenCount;
        }

        public int CollapsedCount {
            get { return _collapsedCount; }
        }

        public int SplatIndex {
            get { return _splatIndex; }
        }

        public int FirstSplattedArg {
            get { return _firstSplattedArg; }
        }

        public IList<string> ArgNames {
            get { return _names; }
        }

        public IList<DynamicMetaObject> NamedArguments {
            get { return _namedArgs; }
        }

        public IList<DynamicMetaObject> Arguments {
            get { return _args; }
        }

        internal int ToSplattedItemIndex(int collapsedArgIndex) {
            return _splatIndex - _firstSplattedArg + collapsedArgIndex;
        }

        /// <summary>
        /// The number of arguments not counting the collapsed ones.
        /// </summary>
        public int Count {
            get { return _args.Count + _namedArgs.Count; }
        }

        public int HiddenCount {
            get { return _hiddenCount; }
        }

        /// <summary>
        /// Gets the total number of visible arguments passed to the call site including collapsed ones.
        /// </summary>
        public int VisibleCount {
            get { return Count + _collapsedCount - _hiddenCount; }
        }

        public DynamicMetaObject this[int index] {
            get {
                return (index < _args.Count) ? _args[index] : _namedArgs[index - _args.Count];
            }
        }

        /// <summary>
        /// Binds named arguments to the parameters. Returns a permutation of indices that captures the relationship between 
        /// named arguments and their corresponding parameters. Checks for duplicate and unbound named arguments.
        /// 
        /// Ensures that for all i: namedArgs[i] binds to parameters[args.Length + bindingPermutation[i]] 
        /// </summary>
        internal bool TryBindNamedArguments(MethodCandidate method, out ArgumentBinding binding, out CallFailure failure) {
            if (_namedArgs.Count == 0) {
                binding = new ArgumentBinding(_args.Count);
                failure = null;
                return true;
            }

            var permutation = new int[_namedArgs.Count];
            var boundParameters = new BitArray(_namedArgs.Count);

            for (int i = 0; i < permutation.Length; i++) {
                permutation[i] = -1;
            }

            List<string> unboundNames = null;
            List<string> duppedNames = null;

            int positionalArgCount = _args.Count;

            for (int i = 0; i < _names.Count; i++) {
                int paramIndex = method.IndexOfParameter(_names[i]);
                if (paramIndex >= 0) {
                    int nameIndex = paramIndex - positionalArgCount;

                    // argument maps to already bound parameter:
                    if (paramIndex < positionalArgCount || boundParameters[nameIndex]) {
                        if (duppedNames == null) {
                            duppedNames = new List<string>();
                        }
                        duppedNames.Add(_names[i]);
                    } else {
                        permutation[i] = nameIndex;
                        boundParameters[nameIndex] = true;
                    }
                } else {
                    if (unboundNames == null) {
                        unboundNames = new List<string>();
                    }
                    unboundNames.Add(_names[i]);
                }
            }

            binding = new ArgumentBinding(positionalArgCount, permutation);

            if (unboundNames != null) {
                failure = new CallFailure(method, unboundNames.ToArray(), true);
                return false;
            } else if (duppedNames != null) {
                failure = new CallFailure(method, duppedNames.ToArray(), false);
                return false;
            }

            failure = null;
            return true;
        }
    }
}
