/**
 * Copyright (c) 2011 Bruno Jouhier <bruno.jouhier@sage.com>
 *
 * 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 in 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.
 *
 */
/// !doc
/// 
/// # Flow control utilities
///  
/// `var flows = require('streamline/lib/util/flows')`
/// 
(function(exports) {
	"use strict";
	var fe_ = Array.prototype.forEach_,
		dir = 'streamline/lib/' + (fe_.gstreamlineFunction ? 'generators' : fe_.fstreamlineFunction ? 'fibers' : 'callbacks');
	var builtins = require(dir + '/builtins');
	/// !nodoc
	/// Obsolete API
	/// 
	/// This API is obsolete. Use `array.forEach_`, `array.map_`, ... instead.
	/// 
	/// * `flows.each(_, array, fn, [thisObj])`  
	///   applies `fn` sequentially to the elements of `array`.  
	///   `fn` is called as `fn(_, elt, i)`.
	exports.each = function(cb, array, fn, thisObj) {
		return (array && array.length) ? array.forEach_(cb, fn, thisObj) : cb(null);
	}
	/// * `result = flows.map(_, array, fn, [thisObj])`  
	///   transforms `array` by applying `fn` to each element in turn.  
	///   `fn` is called as `fn(_, elt, i)`.
	exports.map = function(cb, array, fn, thisObj) {
		return array ? array.map_(cb, fn, thisObj) : cb(null, array);
	}
	/// * `result = flows.filter(_, array, fn, [thisObj])`  
	///   generates a new array that only contains the elements that satisfy the `fn` predicate.  
	///   `fn` is called as `fn(_, elt)`.
	exports.filter = function(cb, array, fn, thisObj) {
		return array ? array.filter_(cb, fn, thisObj) : cb(null, array);
	}
	/// * `bool = flows.every(_, array, fn, [thisObj])`  
	///   returns true if `fn` is true on every element (if `array` is empty too).  
	///   `fn` is called as `fn(_, elt)`.
	exports.every = function(cb, array, fn, thisObj) {
		return array ? array.every_(cb, fn, thisObj) : cb(null);
	}
	/// * `bool = flows.some(_, array, fn, [thisObj])`  
	///   returns true if `fn` is true for at least one element.  
	///   `fn` is called as `fn(_, elt)`.
	exports.some = function(cb, array, fn, thisObj) {
		return array ? array.some_(cb, fn, thisObj) : cb(null);
	}
	/// * `result = flows.reduce(_, array, fn, val, [thisObj])`  
	///   reduces by applying `fn` to each element.  
	///   `fn` is called as `val = fn(_, val, elt, i, array)`.
	exports.reduce = function(cb, array, fn, v, thisObj) {
		return array ? array.reduce_(cb, fn, v, thisObj) : cb(null, v);
	}
	/// * `result = flows.reduceRight(_, array, fn, val, [thisObj])`  
	///   reduces from end to start by applying `fn` to each element.  
	///   `fn` is called as `val = fn(_, val, elt, i, array)`.
	exports.reduceRight = function(cb, array, fn, v, thisObj) {
		return array ? array.reduceRight_(cb, fn, v, thisObj) : cb(null, v);
	}

	/// * `array = flows.sort(_, array, compare, [beg], [end])`  
	///   sorts the array.  
	///   `compare` is called as `cmp = compare(_, elt1, elt2)`
	///   
	///   Note: this function _changes_ the original array (and returns it)
	exports.sort = function(cb, array, compare, beg, end) {
		return array ? array.sort_(cb, compare, beg, end) : cb(null, array);
	}
	/// 
	/// ## Object utility (obsolete)
	/// 
	/// This API is obsolete. Use `Object.keys(obj).forEach_` instead.
	/// 
	/// * `flows.eachKey(_, obj, fn)`  
	///   calls `fn(_, key, obj[key])` for every `key` in `obj`.
	exports.eachKey = function(cb, obj, fn, thisObj) {
		return (obj ? Object.keys(obj) : []).forEach_(cb, function(cb, elt) {
			fn.call(thisObj, cb, elt, obj[elt]);
		});
	}
	// deprecated -- don't document 
	exports.spray = function(fns, max) {
		return new

		function() {
			var funnel = exports.funnel(max);
			this.collect = function(callback, count, trim) {
				if (typeof(callback) != "function") throw new Error("invalid call to collect: no callback")
				var results = trim ? [] : new Array(fns.length);
				count = count < 0 ? fns.length : Math.min(count, fns.length);
				if (count == 0) return callback(null, results);
				var collected = 0;
				for (var i = 0; i < fns.length; i++) {
					(function(i) {
						funnel(function(err, result) {
							if (err) return callback(err);
							if (trim) results.push(result);
							else results[i] = result;
							if (++collected == count) return callback(null, results);
						}, fns[i])
					})(i);
				}
			}
			this.collectOne = function(callback) {
				return this.collect(function(err, result) {
					return callback(err, result && result[0]);
				}, 1, true)
			}
			this.collectAll = function(callback) {
				return this.collect(callback, -1, false);
			}
		}

	}

	/// !doc
	/// * `fun = flows.funnel(max)`  
	///   limits the number of concurrent executions of a given code block.
	/// 
	/// The `funnel` function is typically used with the following pattern:
	/// 
	/// ``` javascript
	/// // somewhere
	/// var myFunnel = flows.funnel(10); // create a funnel that only allows 10 concurrent executions.
	/// 
	/// // elsewhere
	/// myFunnel(_, function(_) { /* code with at most 10 concurrent executions */ });
	/// ```
	/// 
	/// The `diskUsage2.js` example demonstrates how these calls can be combined to control concurrent execution.
	/// 
	/// The `funnel` function can also be used to implement critical sections. Just set funnel's `max` parameter to 1.
	/// 
	/// If `max` is set to 0, a default number of parallel executions is allowed. 
	/// This default number can be read and set via `flows.funnel.defaultSize`.  
	/// If `max` is negative, the funnel does not limit the level of parallelism.
	/// 
	/// The funnel can be closed with `fun.close()`.  
	/// When a funnel is closed, the operations that are still in the funnel will continue but their callbacks
	/// won't be called, and no other operation will enter the funnel.
	exports.funnel = builtins.funnel;

	/// 
	/// * `results = flows.collect(_, futures)`  
	///   collects the results of an array of futures
	exports.collect = function(cb, futures) {
		return futures && futures.map_(cb, function(cb, future) {
			return future(cb);
		});
	}

	// Obsolete API - use require('streamline/lib/globals').context instead
	var globals = require("streamline/lib/globals");
	exports.setContext = function(ctx) {
		var old = globals.context;
		globals.context = ctx;
		return old;
	}
	exports.getContext = function() {
		return globals.context;
	}

	/// 
	/// * `flows.nextTick(_)`  
	///   `nextTick` function for both browser and server.  
	///   Aliased to `process.nextTick` on the server side.
	exports.nextTick = typeof process === "object" && typeof process.nextTick === "function" ? process.nextTick : function(callback) {
		setTimeout(function() {
			callback();
		}, 0);
	};

	//   Obsolete. Use `fn.apply_` instead.
	exports.apply = function apply(cb, fn, thisObj, args, index) {
		return fn.apply_(cb, thisObj, args);
	}


})(typeof exports !== 'undefined' ? exports : (window.StreamlineFlows = window.StreamlineFlows || {}));