/*
 * Licensed to Elasticsearch B.V. under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch B.V. licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import _ from 'lodash';
import { uiModules } from '../modules';

const module = uiModules.get('kibana');

// Provides a tiny subset of the excellent 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"');

    const 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) {
    const defer = $q.defer();
    defer.resolve(val);
    return defer.promise;
  };
  Promise.reject = function (reason) {
    const 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 () {
      const 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) {
    const 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 () {
    const 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(new TypeError('fn must be a function'));
    }

    let value;

    if (Array.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]);
      });
    });
  };
  Promise.race = function (iterable) {
    return new Promise((resolve, reject) => {
      for (const i of iterable) {
        Promise.resolve(i).then(resolve, reject);
      }
    });
  };

  return Promise;
});
