var ParameterBinding =
{
	areParametersBound: function(inParameters,inRequiredParameterCount)
	{
		var i;

		if (inRequiredParameterCount === undefined)
			inRequiredParameterCount = inParameters.callee.length;
		for (i = 0; i < inRequiredParameterCount && i < inParameters.length; i++)
		{
			if (inParameters[i] === undefined)
				return (false);
		}

		return (i == inRequiredParameterCount ? true : false);
	},


	/*
	 * A parameter binder suitable for both creating closures and currying functions.  The function returned will
	 * automatically call the underlying one once all the required parameters are present.
	 *
	 * It is possible to bind any parameters, not just the leading ones.  Passing a parameter with a value of
	 * undefined will leave it unbound.  Note that this means undefined is not a valid parameter value; use
	 * null instead.
	 *
	 * The inRequiredParameterCount is optional.  If omitted the binder will use inFunction.length as a default.
	 *
	 * To use this to make a function curryable (i.e. automatically returning a closure with bound parameters
	 * when invoked with an incomplete parameter list) put the following lines of code at the beginning of the
	 * function:
	 *
	 *	if (!ParameterBinding.areParametersBound(arguments))
	 *		return (ParameterBinding.bindParameters(arguments.callee,arguments,this));
	 */
	bindParameters: function(inFunction,inParameters,inThis,inRequiredParameterCount)
	{
		function bind(inBoundParameters,inUnboundParameters,isAlwaysClosing)
		{
			var theBoundParameters;

			theBoundParameters = inBoundParameters.slice(0);

			// Do the copying in a separate block to keep the temporary variables out of the closure below.
			{
				var theBoundParameterIndex;
				var theUnboundParameterIndex;

				// Copy the unbound parameters into the bound array.
				for (theUnboundParameterIndex = 0,theBoundParameterIndex = 0;
					 theUnboundParameterIndex < inUnboundParameters.length;
					 theUnboundParameterIndex++,theBoundParameterIndex++)
				{
					// Search for the first unbound slot.
					while (theBoundParameterIndex < theBoundParameters.length && theBoundParameters[theBoundParameterIndex] !== undefined)
						theBoundParameterIndex++;

					// Bind the parameter to the new value.
					theBoundParameters[theBoundParameterIndex] = inUnboundParameters[theUnboundParameterIndex];
				}
			}

			if (!isAlwaysClosing && ParameterBinding.areParametersBound(theBoundParameters,inRequiredParameterCount))
				return (inFunction.apply(inThis !== undefined ? inThis : this,theBoundParameters));
			return (function()
			{
				return (bind(theBoundParameters,arguments));
			});
		}

		if (inRequiredParameterCount === undefined)
			inRequiredParameterCount = inFunction.length;

		return (bind([],inParameters,true));
	},


	shuffleParameters: function(inFunction,inParameterIndices,inThis)
	{
		return (function()
		{
			var theParameters;
			var i;

			theParameters = new Array();
			for (i = 0; i < inParameterIndices.length; i++)
			{
				if (typeof(inParameterIndices[i]) == "number")
					theParameters[i] = arguments[inParameterIndices[i]];
				else
					theParameters[i] = undefined;
			}
			return (inFunction.apply(inThis !== undefined ? inThis : this,theParameters));
		});
	},


	compose: function(inFirstFunction)
	{
		var theFunctions;

		if (arguments.length <= 1)
			return (inFirstFunction);
		theFunctions = Array.prototype.slice.apply(arguments,[1]);

		return (function()
		{
			var i;
			var theValue;

			theValue = [ inFirstFunction.apply(this,arguments) ];
			for (i = 0; i < theFunctions.length; i++)
				theValue[0] = theFunctions[i].apply(this,theValue)
			return (theValue[0]);
		});
	},


	chain: function(inFirstFunction,inSecondFunction)
	{
		return (function() { inFirstFunction(); return (inSecondFunction()); });
	},


	// deprecated, these can all be done in terms of bindParameters
	bind2And3Of3: function(inFunction,in2,in3)
	{
		return (function(in1) { inFunction(in1,in2,in3) });
	},


	bind1And2And3Of4: function(inFunction,in1,in2,in3)
	{
		return (function(in4) { return (inFunction(in1,in2,in3,in4)); });
	},


	bind1: function(inFunction,in1)
	{
		return (function() { return (inFunction(in1)); });
	},


	bind2: function(inFunction,in1,in2)
	{
		return (function() { return (inFunction(in1,in2)); });
	}
};
