define(function (require) {
  var _ = require('lodash');

  var module = require('ui/modules').get('kibana');

  // Provides a tiny subset of the excelent API from
  // bluebird, reimplemented using the $q service
  module.service('Promise', function ($q, $timeout) {
    function Promise(fn) {
      if (typeof this === 'undefined') throw new Error('Promise constructor must be called with "new"');

      var defer = $q.defer();
      try {
        fn(defer.resolve, defer.reject);
      } catch (e) {
        defer.reject(e);
      }
      return defer.promise;
    }

    Promise.all = Promise.props = $q.all;
    Promise.resolve = function (val) {
      var defer = $q.defer();
      defer.resolve(val);
      return defer.promise;
    };
    Promise.reject = function (reason) {
      var defer = $q.defer();
      defer.reject(reason);
      return defer.promise;
    };
    Promise.cast = $q.when;
    Promise.defer = $q.defer;
    Promise.delay = function (ms) {
      return $timeout(_.noop, ms);
    };
    Promise.method = function (fn) {
      return function () {
        var args = Array.prototype.slice.call(arguments);
        return Promise.try(fn, args, this);
      };
    };
    Promise.nodeify = function (promise, cb) {
      promise.then(function (val) {
        cb(void 0, val);
      }, cb);
    };
    Promise.map = function (arr, fn) {
      return Promise.all(arr.map(function (i, el, list) {
        return Promise.try(fn, [i, el, list]);
      }));
    };
    Promise.each = function (arr, fn) {
      let queue = arr.slice(0);
      let i = 0;
      return (function next() {
        if (!queue.length) return arr;
        return Promise.try(fn, [arr.shift(), i++]).then(next);
      }());
    };
    Promise.is = function (obj) {
      // $q doesn't create instances of any constructor, promises are just objects with a then function
      // https://github.com/angular/angular.js/blob/58f5da86645990ef984353418cd1ed83213b111e/src/ng/q.js#L335
      return obj && typeof obj.then === 'function';
    };
    Promise.halt = _.once(function () {
      var promise = new Promise();
      promise.then = _.constant(promise);
      promise.catch = _.constant(promise);
      return promise;
    });
    Promise.try = function (fn, args, ctx) {
      if (typeof fn !== 'function') {
        return Promise.reject('fn must be a function');
      }

      var value;

      if (_.isArray(args)) {
        try { value = fn.apply(ctx, args); }
        catch (e) { return Promise.reject(e); }
      } else {
        try { value = fn.call(ctx, args); }
        catch (e) { return Promise.reject(e); }
      }

      return Promise.resolve(value);
    };
    Promise.fromNode = function (takesCbFn) {
      return new Promise(function (resolve, reject) {
        takesCbFn(function (err, ...results) {
          if (err) reject(err);
          else if (results.length > 1) resolve(results);
          else resolve(results[0]);
        });
      });
    };

    return Promise;
  });

  module.factory('PromiseEmitter', function (Promise) {
    /**
     * Create a function that uses an "event" like pattern for promises.
     *
     * When a single argument is passed, this will behave just like calling `new Promise(fn)`,
     * but when a second arguemnt is passed, the fn will be used to recreate a promise eveytime
     * the previous is resolved. The following example demonstrates what this allows:
     *
     * When using `new Promise()` to create a promise, you can allow consumers to be
     * notified of a single change:
     * ```
     * obj.onUpdate= function() {
     *   // NOTE: we are NOT using `new Promise.emitter()` here
     *   return new Promise(function (resolve, reject) {
     *     // wait for the update...
     *     resolve();
     *   });
     * }
     * ```
     *
     * And the consumer can ask for continual updates be re-invoking the `.onChange()` method
     * every time a change occurs:
     * ```
     * obj.onChange().then(function useChanges(change) {
     *   // use changes...
     *   // then register to receive notifcation of the next change
     *   obj.onChange().then(useChanges);
     * });
     * ```
     *
     * But by defining obj.onChange using `new Promise.emitter`:
     * ```
     * obj.onChange = function (handler) {
     *   return new Promise.emitter(function (resolve, reject) {
     *     // wait for changes...
     *     resolve();
     *   });
     * };
     * ```
     *
     * The consumer can now simplify their code by passing the handler directly to `.onUpdate()`
     * and the boilerplate of recalling `.onUpdate()` will be handled for them.
     * ```
     * obj.onChanges(function useChanges(changes) {
     *   // use changes...
     * });
     * ```
     *
     * @param  {Function} fn - Used to init the promise, and call either
     *                       reject or resolve (passed as args)
     * @param  {Function} handler - A function that will be called every
     *                            time this promise is resolved
     *
     * @return {Promise}
     */
    function PromiseEmitter(fn, handler) {
      var prom = new Promise(fn);

      if (!handler) return prom;

      return prom.then(handler).then(function recurse() {
        return new PromiseEmitter(fn, handler);
      });
    }

    return PromiseEmitter;
  });
});
