(function ($) {
    /*
        $.whenAll(deferred1, deferred2, deferred3...) or $.whenAll([ deferred1, deferred2, deferred3... ])
        returns a deferred, waiting until all deferreds are resolved.
        then calls .fail() handlers if any deferred was rejected with those deferreds' .fail values
        or calls .done() handlers if all deferred were resolved with all deferreds' .done values
        resolves/rejects in the the format { 0: deferred1 result, 2: deferred3 result, etc. }
        
    */

    $.whenAll = function (deferreds) {
        if (!deferreds.length) {
            // convert $.whenAll(deferred1, ...) to $.whenAll( [ deferred1, ... ] )
            deferreds = [].slice.call(deferreds, 0);
        }
        if (deferreds && deferreds.length) {

            var deferred = $.Deferred(),
              incomplete = deferreds.length,
              someFailed = false,
              done,
              fail,
              always,
              resolved = {},
              rejected = {};

            always = function () {
                incomplete--;
                if (!incomplete) {
                    if (someFailed) {
                        deferred.reject(rejected);
                    } else {
                        deferred.resolve(resolved);
                    }
                }
            };
            done = function (index) {
                resolved[index] = [].slice.call(arguments, 1);
            };
            fail = function (index) {
                someFailed = true;
                rejected[index] = [].slice.call(arguments, 1);
            };

            $.each(deferreds, function (index, deferred) {
                deferred
                  .done(done.bind(deferred, index))
                  .fail(fail.bind(deferred, index))
                  .always(always);
            });

            return deferred;

        } else {
            return $.Deferred().resolve();
        }
    };
} (jQuery));
