/*!

* async
* https://github.com/caolan/async
*
* Copyright 2010-2014 Caolan McMahon
* Released under the MIT license
*/

(function () {

var async = {};
function noop() {}
function identity(v) {
    return v;
}
function toBool(v) {
    return !!v;
}
function notId(v) {
    return !v;
}

// global on the server, window in the browser
var previous_async;

// Establish the root object, `window` (`self`) in the browser, `global`
// on the server, or `this` in some virtual machines. We use `self`
// instead of `window` for `WebWorker` support.
var root = typeof self === 'object' && self.self === self && self ||
        typeof global === 'object' && global.global === global && global ||
        this;

if (root != null) {
    previous_async = root.async;
}

async.noConflict = function () {
    root.async = previous_async;
    return async;
};

function only_once(fn) {
    return function() {
        if (fn === null) throw new Error("Callback was already called.");
        fn.apply(this, arguments);
        fn = null;
    };
}

function _once(fn) {
    return function() {
        if (fn === null) return;
        fn.apply(this, arguments);
        fn = null;
    };
}

//// cross-browser compatiblity functions ////

var _toString = Object.prototype.toString;

var _isArray = Array.isArray || function (obj) {
    return _toString.call(obj) === '[object Array]';
};

// Ported from underscore.js isObject
var _isObject = function(obj) {
    var type = typeof obj;
    return type === 'function' || type === 'object' && !!obj;
};

function _isArrayLike(arr) {
    return _isArray(arr) || (
        // has a positive integer length property
        typeof arr.length === "number" &&
        arr.length >= 0 &&
        arr.length % 1 === 0
    );
}

function _arrayEach(arr, iterator) {
    var index = -1,
        length = arr.length;

    while (++index < length) {
        iterator(arr[index], index, arr);
    }
}

function _map(arr, iterator) {
    var index = -1,
        length = arr.length,
        result = Array(length);

    while (++index < length) {
        result[index] = iterator(arr[index], index, arr);
    }
    return result;
}

function _range(count) {
    return _map(Array(count), function (v, i) { return i; });
}

function _reduce(arr, iterator, memo) {
    _arrayEach(arr, function (x, i, a) {
        memo = iterator(memo, x, i, a);
    });
    return memo;
}

function _forEachOf(object, iterator) {
    _arrayEach(_keys(object), function (key) {
        iterator(object[key], key);
    });
}

function _indexOf(arr, item) {
    for (var i = 0; i < arr.length; i++) {
        if (arr[i] === item) return i;
    }
    return -1;
}

var _keys = Object.keys || function (obj) {
    var keys = [];
    for (var k in obj) {
        if (obj.hasOwnProperty(k)) {
            keys.push(k);
        }
    }
    return keys;
};

function _keyIterator(coll) {
    var i = -1;
    var len;
    var keys;
    if (_isArrayLike(coll)) {
        len = coll.length;
        return function next() {
            i++;
            return i < len ? i : null;
        };
    } else {
        keys = _keys(coll);
        len = keys.length;
        return function next() {
            i++;
            return i < len ? keys[i] : null;
        };
    }
}

// Similar to ES6's rest param (http://ariya.ofilabs.com/2013/03/es6-and-rest-parameter.html)
// This accumulates the arguments passed into an array, after a given index.
// From underscore.js (https://github.com/jashkenas/underscore/pull/2140).
function _restParam(func, startIndex) {
    startIndex = startIndex == null ? func.length - 1 : +startIndex;
    return function() {
        var length = Math.max(arguments.length - startIndex, 0);
        var rest = Array(length);
        for (var index = 0; index < length; index++) {
            rest[index] = arguments[index + startIndex];
        }
        switch (startIndex) {
            case 0: return func.call(this, rest);
            case 1: return func.call(this, arguments[0], rest);
        }
        // Currently unused but handle cases outside of the switch statement:
        // var args = Array(startIndex + 1);
        // for (index = 0; index < startIndex; index++) {
        //     args[index] = arguments[index];
        // }
        // args[startIndex] = rest;
        // return func.apply(this, args);
    };
}

function _withoutIndex(iterator) {
    return function (value, index, callback) {
        return iterator(value, callback);
    };
}

//// exported async module functions ////

//// nextTick implementation with browser-compatible fallback ////

// capture the global reference to guard against fakeTimer mocks
var _setImmediate = typeof setImmediate === 'function' && setImmediate;

var _delay = _setImmediate ? function(fn) {
    // not a direct alias for IE10 compatibility
    _setImmediate(fn);
} : function(fn) {
    setTimeout(fn, 0);
};

if (typeof process === 'object' && typeof process.nextTick === 'function') {
    async.nextTick = process.nextTick;
} else {
    async.nextTick = _delay;
}
async.setImmediate = _setImmediate ? _delay : async.nextTick;

async.forEach =
async.each = function (arr, iterator, callback) {
    return async.eachOf(arr, _withoutIndex(iterator), callback);
};

async.forEachSeries =
async.eachSeries = function (arr, iterator, callback) {
    return async.eachOfSeries(arr, _withoutIndex(iterator), callback);
};

async.forEachLimit =
async.eachLimit = function (arr, limit, iterator, callback) {
    return _eachOfLimit(limit)(arr, _withoutIndex(iterator), callback);
};

async.forEachOf =
async.eachOf = function (object, iterator, callback) {
    callback = _once(callback || noop);
    object = object || [];

    var iter = _keyIterator(object);
    var key, completed = 0;

    while ((key = iter()) != null) {
        completed += 1;
        iterator(object[key], key, only_once(done));
    }

    if (completed === 0) callback(null);

    function done(err) {
        completed--;
        if (err) {
            callback(err);
        }
        // Check key is null in case iterator isn't exhausted
        // and done resolved synchronously.
        else if (key === null && completed <= 0) {
            callback(null);
        }
    }
};

async.forEachOfSeries =
async.eachOfSeries = function (obj, iterator, callback) {
    callback = _once(callback || noop);
    obj = obj || [];
    var nextKey = _keyIterator(obj);
    var key = nextKey();
    function iterate() {
        var sync = true;
        if (key === null) {
            return callback(null);
        }
        iterator(obj[key], key, only_once(function (err) {
            if (err) {
                callback(err);
            }
            else {
                key = nextKey();
                if (key === null) {
                    return callback(null);
                } else {
                    if (sync) {
                        async.setImmediate(iterate);
                    } else {
                        iterate();
                    }
                }
            }
        }));
        sync = false;
    }
    iterate();
};

async.forEachOfLimit =
async.eachOfLimit = function (obj, limit, iterator, callback) {
    _eachOfLimit(limit)(obj, iterator, callback);
};

function _eachOfLimit(limit) {

    return function (obj, iterator, callback) {
        callback = _once(callback || noop);
        obj = obj || [];
        var nextKey = _keyIterator(obj);
        if (limit <= 0) {
            return callback(null);
        }
        var done = false;
        var running = 0;
        var errored = false;

        (function replenish () {
            if (done && running <= 0) {
                return callback(null);
            }

            while (running < limit && !errored) {
                var key = nextKey();
                if (key === null) {
                    done = true;
                    if (running <= 0) {
                        callback(null);
                    }
                    return;
                }
                running += 1;
                iterator(obj[key], key, only_once(function (err) {
                    running -= 1;
                    if (err) {
                        callback(err);
                        errored = true;
                    }
                    else {
                        replenish();
                    }
                }));
            }
        })();
    };
}

function doParallel(fn) {
    return function (obj, iterator, callback) {
        return fn(async.eachOf, obj, iterator, callback);
    };
}
function doParallelLimit(fn) {
    return function (obj, limit, iterator, callback) {
        return fn(_eachOfLimit(limit), obj, iterator, callback);
    };
}
function doSeries(fn) {
    return function (obj, iterator, callback) {
        return fn(async.eachOfSeries, obj, iterator, callback);
    };
}

function _asyncMap(eachfn, arr, iterator, callback) {
    callback = _once(callback || noop);
    arr = arr || [];
    var results = _isArrayLike(arr) ? [] : {};
    eachfn(arr, function (value, index, callback) {
        iterator(value, function (err, v) {
            results[index] = v;
            callback(err);
        });
    }, function (err) {
        callback(err, results);
    });
}

async.map = doParallel(_asyncMap);
async.mapSeries = doSeries(_asyncMap);
async.mapLimit = doParallelLimit(_asyncMap);

// reduce only has a series version, as doing reduce in parallel won't
// work in many situations.
async.inject =
async.foldl =
async.reduce = function (arr, memo, iterator, callback) {
    async.eachOfSeries(arr, function (x, i, callback) {
        iterator(memo, x, function (err, v) {
            memo = v;
            callback(err);
        });
    }, function (err) {
        callback(err, memo);
    });
};

async.foldr =
async.reduceRight = function (arr, memo, iterator, callback) {
    var reversed = _map(arr, identity).reverse();
    async.reduce(reversed, memo, iterator, callback);
};

async.transform = function (arr, memo, iterator, callback) {
    if (arguments.length === 3) {
        callback = iterator;
        iterator = memo;
        memo = _isArray(arr) ? [] : {};
    }

    async.eachOf(arr, function(v, k, cb) {
        iterator(memo, v, k, cb);
    }, function(err) {
        callback(err, memo);
    });
};

function _filter(eachfn, arr, iterator, callback) {
    var results = [];
    eachfn(arr, function (x, index, callback) {
        iterator(x, function (v) {
            if (v) {
                results.push({index: index, value: x});
            }
            callback();
        });
    }, function () {
        callback(_map(results.sort(function (a, b) {
            return a.index - b.index;
        }), function (x) {
            return x.value;
        }));
    });
}

async.select =
async.filter = doParallel(_filter);

async.selectLimit =
async.filterLimit = doParallelLimit(_filter);

async.selectSeries =
async.filterSeries = doSeries(_filter);

function _reject(eachfn, arr, iterator, callback) {
    _filter(eachfn, arr, function(value, cb) {
        iterator(value, function(v) {
            cb(!v);
        });
    }, callback);
}
async.reject = doParallel(_reject);
async.rejectLimit = doParallelLimit(_reject);
async.rejectSeries = doSeries(_reject);

function _createTester(eachfn, check, getResult) {
    return function(arr, limit, iterator, cb) {
        function done() {
            if (cb) cb(getResult(false, void 0));
        }
        function iteratee(x, _, callback) {
            if (!cb) return callback();
            iterator(x, function (v) {
                if (cb && check(v)) {
                    cb(getResult(true, x));
                    cb = iterator = false;
                }
                callback();
            });
        }
        if (arguments.length > 3) {
            eachfn(arr, limit, iteratee, done);
        } else {
            cb = iterator;
            iterator = limit;
            eachfn(arr, iteratee, done);
        }
    };
}

async.any =
async.some = _createTester(async.eachOf, toBool, identity);

async.someLimit = _createTester(async.eachOfLimit, toBool, identity);

async.all =
async.every = _createTester(async.eachOf, notId, notId);

async.everyLimit = _createTester(async.eachOfLimit, notId, notId);

function _findGetResult(v, x) {
    return x;
}
async.detect = _createTester(async.eachOf, identity, _findGetResult);
async.detectSeries = _createTester(async.eachOfSeries, identity, _findGetResult);
async.detectLimit = _createTester(async.eachOfLimit, identity, _findGetResult);

async.sortBy = function (arr, iterator, callback) {
    async.map(arr, function (x, callback) {
        iterator(x, function (err, criteria) {
            if (err) {
                callback(err);
            }
            else {
                callback(null, {value: x, criteria: criteria});
            }
        });
    }, function (err, results) {
        if (err) {
            return callback(err);
        }
        else {
            callback(null, _map(results.sort(comparator), function (x) {
                return x.value;
            }));
        }

    });

    function comparator(left, right) {
        var a = left.criteria, b = right.criteria;
        return a < b ? -1 : a > b ? 1 : 0;
    }
};

async.auto = function (tasks, concurrency, callback) {
    if (typeof arguments[1] === 'function') {
        // concurrency is optional, shift the args.
        callback = concurrency;
        concurrency = null;
    }
    callback = _once(callback || noop);
    var keys = _keys(tasks);
    var remainingTasks = keys.length;
    if (!remainingTasks) {
        return callback(null);
    }
    if (!concurrency) {
        concurrency = remainingTasks;
    }

    var results = {};
    var runningTasks = 0;

    var hasError = false;

    var listeners = [];
    function addListener(fn) {
        listeners.unshift(fn);
    }
    function removeListener(fn) {
        var idx = _indexOf(listeners, fn);
        if (idx >= 0) listeners.splice(idx, 1);
    }
    function taskComplete() {
        remainingTasks--;
        _arrayEach(listeners.slice(0), function (fn) {
            fn();
        });
    }

    addListener(function () {
        if (!remainingTasks) {
            callback(null, results);
        }
    });

    _arrayEach(keys, function (k) {
        if (hasError) return;
        var task = _isArray(tasks[k]) ? tasks[k]: [tasks[k]];
        var taskCallback = _restParam(function(err, args) {
            runningTasks--;
            if (args.length <= 1) {
                args = args[0];
            }
            if (err) {
                var safeResults = {};
                _forEachOf(results, function(val, rkey) {
                    safeResults[rkey] = val;
                });
                safeResults[k] = args;
                hasError = true;

                callback(err, safeResults);
            }
            else {
                results[k] = args;
                async.setImmediate(taskComplete);
            }
        });
        var requires = task.slice(0, task.length - 1);
        // prevent dead-locks
        var len = requires.length;
        var dep;
        while (len--) {
            if (!(dep = tasks[requires[len]])) {
                throw new Error('Has nonexistent dependency in ' + requires.join(', '));
            }
            if (_isArray(dep) && _indexOf(dep, k) >= 0) {
                throw new Error('Has cyclic dependencies');
            }
        }
        function ready() {
            return runningTasks < concurrency && _reduce(requires, function (a, x) {
                return (a && results.hasOwnProperty(x));
            }, true) && !results.hasOwnProperty(k);
        }
        if (ready()) {
            runningTasks++;
            task[task.length - 1](taskCallback, results);
        }
        else {
            addListener(listener);
        }
        function listener() {
            if (ready()) {
                runningTasks++;
                removeListener(listener);
                task[task.length - 1](taskCallback, results);
            }
        }
    });
};

async.retry = function(times, task, callback) {
    var DEFAULT_TIMES = 5;
    var DEFAULT_INTERVAL = 0;

    var attempts = [];

    var opts = {
        times: DEFAULT_TIMES,
        interval: DEFAULT_INTERVAL
    };

    function parseTimes(acc, t){
        if(typeof t === 'number'){
            acc.times = parseInt(t, 10) || DEFAULT_TIMES;
        } else if(typeof t === 'object'){
            acc.times = parseInt(t.times, 10) || DEFAULT_TIMES;
            acc.interval = parseInt(t.interval, 10) || DEFAULT_INTERVAL;
        } else {
            throw new Error('Unsupported argument type for \'times\': ' + typeof t);
        }
    }

    var length = arguments.length;
    if (length < 1 || length > 3) {
        throw new Error('Invalid arguments - must be either (task), (task, callback), (times, task) or (times, task, callback)');
    } else if (length <= 2 && typeof times === 'function') {
        callback = task;
        task = times;
    }
    if (typeof times !== 'function') {
        parseTimes(opts, times);
    }
    opts.callback = callback;
    opts.task = task;

    function wrappedTask(wrappedCallback, wrappedResults) {
        function retryAttempt(task, finalAttempt) {
            return function(seriesCallback) {
                task(function(err, result){
                    seriesCallback(!err || finalAttempt, {err: err, result: result});
                }, wrappedResults);
            };
        }

        function retryInterval(interval){
            return function(seriesCallback){
                setTimeout(function(){
                    seriesCallback(null);
                }, interval);
            };
        }

        while (opts.times) {

            var finalAttempt = !(opts.times-=1);
            attempts.push(retryAttempt(opts.task, finalAttempt));
            if(!finalAttempt && opts.interval > 0){
                attempts.push(retryInterval(opts.interval));
            }
        }

        async.series(attempts, function(done, data){
            data = data[data.length - 1];
            (wrappedCallback || opts.callback)(data.err, data.result);
        });
    }

    // If a callback is passed, run this as a controll flow
    return opts.callback ? wrappedTask() : wrappedTask;
};

async.waterfall = function (tasks, callback) {
    callback = _once(callback || noop);
    if (!_isArray(tasks)) {
        var err = new Error('First argument to waterfall must be an array of functions');
        return callback(err);
    }
    if (!tasks.length) {
        return callback();
    }
    function wrapIterator(iterator) {
        return _restParam(function (err, args) {
            if (err) {
                callback.apply(null, [err].concat(args));
            }
            else {
                var next = iterator.next();
                if (next) {
                    args.push(wrapIterator(next));
                }
                else {
                    args.push(callback);
                }
                ensureAsync(iterator).apply(null, args);
            }
        });
    }
    wrapIterator(async.iterator(tasks))();
};

function _parallel(eachfn, tasks, callback) {
    callback = callback || noop;
    var results = _isArrayLike(tasks) ? [] : {};

    eachfn(tasks, function (task, key, callback) {
        task(_restParam(function (err, args) {
            if (args.length <= 1) {
                args = args[0];
            }
            results[key] = args;
            callback(err);
        }));
    }, function (err) {
        callback(err, results);
    });
}

async.parallel = function (tasks, callback) {
    _parallel(async.eachOf, tasks, callback);
};

async.parallelLimit = function(tasks, limit, callback) {
    _parallel(_eachOfLimit(limit), tasks, callback);
};

async.series = function(tasks, callback) {
    _parallel(async.eachOfSeries, tasks, callback);
};

async.iterator = function (tasks) {
    function makeCallback(index) {
        function fn() {
            if (tasks.length) {
                tasks[index].apply(null, arguments);
            }
            return fn.next();
        }
        fn.next = function () {
            return (index < tasks.length - 1) ? makeCallback(index + 1): null;
        };
        return fn;
    }
    return makeCallback(0);
};

async.apply = _restParam(function (fn, args) {
    return _restParam(function (callArgs) {
        return fn.apply(
            null, args.concat(callArgs)
        );
    });
});

function _concat(eachfn, arr, fn, callback) {
    var result = [];
    eachfn(arr, function (x, index, cb) {
        fn(x, function (err, y) {
            result = result.concat(y || []);
            cb(err);
        });
    }, function (err) {
        callback(err, result);
    });
}
async.concat = doParallel(_concat);
async.concatSeries = doSeries(_concat);

async.whilst = function (test, iterator, callback) {
    callback = callback || noop;
    if (test()) {
        var next = _restParam(function(err, args) {
            if (err) {
                callback(err);
            } else if (test.apply(this, args)) {
                iterator(next);
            } else {
                callback.apply(null, [null].concat(args));
            }
        });
        iterator(next);
    } else {
        callback(null);
    }
};

async.doWhilst = function (iterator, test, callback) {
    var calls = 0;
    return async.whilst(function() {
        return ++calls <= 1 || test.apply(this, arguments);
    }, iterator, callback);
};

async.until = function (test, iterator, callback) {
    return async.whilst(function() {
        return !test.apply(this, arguments);
    }, iterator, callback);
};

async.doUntil = function (iterator, test, callback) {
    return async.doWhilst(iterator, function() {
        return !test.apply(this, arguments);
    }, callback);
};

async.during = function (test, iterator, callback) {
    callback = callback || noop;

    var next = _restParam(function(err, args) {
        if (err) {
            callback(err);
        } else {
            args.push(check);
            test.apply(this, args);
        }
    });

    var check = function(err, truth) {
        if (err) {
            callback(err);
        } else if (truth) {
            iterator(next);
        } else {
            callback(null);
        }
    };

    test(check);
};

async.doDuring = function (iterator, test, callback) {
    var calls = 0;
    async.during(function(next) {
        if (calls++ < 1) {
            next(null, true);
        } else {
            test.apply(this, arguments);
        }
    }, iterator, callback);
};

function _queue(worker, concurrency, payload) {
    if (concurrency == null) {
        concurrency = 1;
    }
    else if(concurrency === 0) {
        throw new Error('Concurrency must not be zero');
    }
    function _insert(q, data, pos, callback) {
        if (callback != null && typeof callback !== "function") {
            throw new Error("task callback must be a function");
        }
        q.started = true;
        if (!_isArray(data)) {
            data = [data];
        }
        if(data.length === 0 && q.idle()) {
            // call drain immediately if there are no tasks
            return async.setImmediate(function() {
                q.drain();
            });
        }
        _arrayEach(data, function(task) {
            var item = {
                data: task,
                callback: callback || noop
            };

            if (pos) {
                q.tasks.unshift(item);
            } else {
                q.tasks.push(item);
            }

            if (q.tasks.length === q.concurrency) {
                q.saturated();
            }
        });
        async.setImmediate(q.process);
    }
    function _next(q, tasks) {
        return function(){
            workers -= 1;

            var removed = false;
            var args = arguments;
            _arrayEach(tasks, function (task) {
                _arrayEach(workersList, function (worker, index) {
                    if (worker === task && !removed) {
                        workersList.splice(index, 1);
                        removed = true;
                    }
                });

                task.callback.apply(task, args);
            });
            if (q.tasks.length + workers === 0) {
                q.drain();
            }
            q.process();
        };
    }

    var workers = 0;
    var workersList = [];
    var q = {
        tasks: [],
        concurrency: concurrency,
        payload: payload,
        saturated: noop,
        empty: noop,
        drain: noop,
        started: false,
        paused: false,
        push: function (data, callback) {
            _insert(q, data, false, callback);
        },
        kill: function () {
            q.drain = noop;
            q.tasks = [];
        },
        unshift: function (data, callback) {
            _insert(q, data, true, callback);
        },
        process: function () {
            while(!q.paused && workers < q.concurrency && q.tasks.length){

                var tasks = q.payload ?
                    q.tasks.splice(0, q.payload) :
                    q.tasks.splice(0, q.tasks.length);

                var data = _map(tasks, function (task) {
                    return task.data;
                });

                if (q.tasks.length === 0) {
                    q.empty();
                }
                workers += 1;
                workersList.push(tasks[0]);
                var cb = only_once(_next(q, tasks));
                worker(data, cb);
            }
        },
        length: function () {
            return q.tasks.length;
        },
        running: function () {
            return workers;
        },
        workersList: function () {
            return workersList;
        },
        idle: function() {
            return q.tasks.length + workers === 0;
        },
        pause: function () {
            q.paused = true;
        },
        resume: function () {
            if (q.paused === false) { return; }
            q.paused = false;
            var resumeCount = Math.min(q.concurrency, q.tasks.length);
            // Need to call q.process once per concurrent
            // worker to preserve full concurrency after pause
            for (var w = 1; w <= resumeCount; w++) {
                async.setImmediate(q.process);
            }
        }
    };
    return q;
}

async.queue = function (worker, concurrency) {
    var q = _queue(function (items, cb) {
        worker(items[0], cb);
    }, concurrency, 1);

    return q;
};

async.priorityQueue = function (worker, concurrency) {

    function _compareTasks(a, b){
        return a.priority - b.priority;
    }

    function _binarySearch(sequence, item, compare) {
        var beg = -1,
            end = sequence.length - 1;
        while (beg < end) {
            var mid = beg + ((end - beg + 1) >>> 1);
            if (compare(item, sequence[mid]) >= 0) {
                beg = mid;
            } else {
                end = mid - 1;
            }
        }
        return beg;
    }

    function _insert(q, data, priority, callback) {
        if (callback != null && typeof callback !== "function") {
            throw new Error("task callback must be a function");
        }
        q.started = true;
        if (!_isArray(data)) {
            data = [data];
        }
        if(data.length === 0) {
            // call drain immediately if there are no tasks
            return async.setImmediate(function() {
                q.drain();
            });
        }
        _arrayEach(data, function(task) {
            var item = {
                data: task,
                priority: priority,
                callback: typeof callback === 'function' ? callback : noop
            };

            q.tasks.splice(_binarySearch(q.tasks, item, _compareTasks) + 1, 0, item);

            if (q.tasks.length === q.concurrency) {
                q.saturated();
            }
            async.setImmediate(q.process);
        });
    }

    // Start with a normal queue
    var q = async.queue(worker, concurrency);

    // Override push to accept second parameter representing priority
    q.push = function (data, priority, callback) {
        _insert(q, data, priority, callback);
    };

    // Remove unshift function
    delete q.unshift;

    return q;
};

async.cargo = function (worker, payload) {
    return _queue(worker, 1, payload);
};

function _console_fn(name) {
    return _restParam(function (fn, args) {
        fn.apply(null, args.concat([_restParam(function (err, args) {
            if (typeof console === 'object') {
                if (err) {
                    if (console.error) {
                        console.error(err);
                    }
                }
                else if (console[name]) {
                    _arrayEach(args, function (x) {
                        console[name](x);
                    });
                }
            }
        })]));
    });
}
async.log = _console_fn('log');
async.dir = _console_fn('dir');
/*async.info = _console_fn('info');
async.warn = _console_fn('warn');
async.error = _console_fn('error');*/

async.memoize = function (fn, hasher) {
    var memo = {};
    var queues = {};
    var has = Object.prototype.hasOwnProperty;
    hasher = hasher || identity;
    var memoized = _restParam(function memoized(args) {
        var callback = args.pop();
        var key = hasher.apply(null, args);
        if (has.call(memo, key)) {   
            async.setImmediate(function () {
                callback.apply(null, memo[key]);
            });
        }
        else if (has.call(queues, key)) {
            queues[key].push(callback);
        }
        else {
            queues[key] = [callback];
            fn.apply(null, args.concat([_restParam(function (args) {
                memo[key] = args;
                var q = queues[key];
                delete queues[key];
                for (var i = 0, l = q.length; i < l; i++) {
                    q[i].apply(null, args);
                }
            })]));
        }
    });
    memoized.memo = memo;
    memoized.unmemoized = fn;
    return memoized;
};

async.unmemoize = function (fn) {
    return function () {
        return (fn.unmemoized || fn).apply(null, arguments);
    };
};

function _times(mapper) {
    return function (count, iterator, callback) {
        mapper(_range(count), iterator, callback);
    };
}

async.times = _times(async.map);
async.timesSeries = _times(async.mapSeries);
async.timesLimit = function (count, limit, iterator, callback) {
    return async.mapLimit(_range(count), limit, iterator, callback);
};

async.seq = function (/* functions... */) {
    var fns = arguments;
    return _restParam(function (args) {
        var that = this;

        var callback = args[args.length - 1];
        if (typeof callback == 'function') {
            args.pop();
        } else {
            callback = noop;
        }

        async.reduce(fns, args, function (newargs, fn, cb) {
            fn.apply(that, newargs.concat([_restParam(function (err, nextargs) {
                cb(err, nextargs);
            })]));
        },
        function (err, results) {
            callback.apply(that, [err].concat(results));
        });
    });
};

async.compose = function (/* functions... */) {
    return async.seq.apply(null, Array.prototype.reverse.call(arguments));
};

function _applyEach(eachfn) {
    return _restParam(function(fns, args) {
        var go = _restParam(function(args) {
            var that = this;
            var callback = args.pop();
            return eachfn(fns, function (fn, _, cb) {
                fn.apply(that, args.concat([cb]));
            },
            callback);
        });
        if (args.length) {
            return go.apply(this, args);
        }
        else {
            return go;
        }
    });
}

async.applyEach = _applyEach(async.eachOf);
async.applyEachSeries = _applyEach(async.eachOfSeries);

async.forever = function (fn, callback) {
    var done = only_once(callback || noop);
    var task = ensureAsync(fn);
    function next(err) {
        if (err) {
            return done(err);
        }
        task(next);
    }
    next();
};

function ensureAsync(fn) {
    return _restParam(function (args) {
        var callback = args.pop();
        args.push(function () {
            var innerArgs = arguments;
            if (sync) {
                async.setImmediate(function () {
                    callback.apply(null, innerArgs);
                });
            } else {
                callback.apply(null, innerArgs);
            }
        });
        var sync = true;
        fn.apply(this, args);
        sync = false;
    });
}

async.ensureAsync = ensureAsync;

async.constant = _restParam(function(values) {
    var args = [null].concat(values);
    return function (callback) {
        return callback.apply(this, args);
    };
});

async.wrapSync =
async.asyncify = function asyncify(func) {
    return _restParam(function (args) {
        var callback = args.pop();
        var result;
        try {
            result = func.apply(this, args);
        } catch (e) {
            return callback(e);
        }
        // if result is Promise object
        if (_isObject(result) && typeof result.then === "function") {
            result.then(function(value) {
                callback(null, value);
            })["catch"](function(err) {
                callback(err.message ? err : new Error(err));
            });
        } else {
            callback(null, result);
        }
    });
};

// Node.js
if (typeof module === 'object' && module.exports) {
    module.exports = async;
}
// AMD / RequireJS
else if (typeof define === 'function' && define.amd) {
    define([], function () {
        return async;
    });
}
// included directly via <script> tag
else {
    root.async = async;
}

}());