/*global process*/

'use strict';

var _ = require('lodash/core');

/**
 * @callback ResolvedCallback
 * @param {T} result - Resolved value
 * @returns {S}
 * @template T,S
 */

/**
 * @callback RejectedCallback
 * @param {Error} reason - Rejected reason
 * @returns {S}
 * @template S
 */

/**
 * @callback ResolveCallback
 * @param {T} result
 * @template T
 */

/**
 * @callback RejectedCallback
 * @param {Error} reason - Rejected reason
 * @returns {S}
 * @template S
 */

/**
 * @callback PromiseCallback
 * @param {ResolveCallback.<T>} resolve
 * @param {RejectCallback} reject
 * @template T
 */

/**
 * Promise class with a little extension
 *
 * @class Promise
 * @constructor
 * @param {PromiseCallback.<T>}
 * @template T
 */
var Promise = require('promise/lib/es6-extensions');

/**
 * The "then" method from the Promises/A+ specification
 *
 * @method Promise#then
 * @param {FulfilledCallback.<T, S1>} [onFulfilled]
 * @param {RejectedCallback.<S2>} [onRejected]
 * @returns {Promise.<S1|S2>}
 */

/**
 * Call "then" using given node-style callback function.
 * This is basically same as "nodeify" except that it always return the original promise
 *
 * @method Promise#thenCall
 * @param {Callback.<T>} [callback] - Callback function
 * @returns {Promise}
 */
Promise.prototype.thenCall = function(callback) {
  if (_.isFunction(callback)) {
    this.then(function(res) {
      process.nextTick(function() {
        callback(null, res);
      });
    }, function(err) {
      process.nextTick(function() {
        callback(err);
      });
    });
  }
  return this;
};

/**
 * A sugar method, equivalent to promise.then(undefined, onRejected).
 *
 * @method Promise#catch
 * @param {RejectedCallback.<S>} onRejected
 * @returns {Promise.<S>}
 */

/**
 * Synonym of Promise#catch
 *
 * @method Promise#fail
 * @param {RejectedCallback.<S>} onRejected
 * @returns {Promise.<S>}
 */
Promise.prototype.fail = Promise.prototype['catch'];

/**
 * Returns resolving promise with given reason
 *
 * @method Promise.resolve
 * @param {*} result - Resolved value
 * @returns {Promise}
 */

/**
 * Returns rejecting promise with given reason
 *
 * @method Promise.reject
 * @param {Error} reason - Rejecting reason
 * @returns {Promise}
 */

/**
 * Returns a promise that is fulfilled with an array containing the fulfillment value of each promise,
 * or is rejected with the same rejection reason as the first promise to be rejected.
 *
 * @method Promise.all
 * @param {Array.<Promise.<*>|*>} promises
 * @returns {Promise.<Array.<*>>}
 */

/**
 * Returns a deferred object
 *
 * @method Promise.defer
 * @returns {Deferred}
 */
Promise.defer = function() {
  return new Deferred();
};

/**
 * Deferred object
 *
 * @protected
 * @constructor
 */
var Deferred = function() {
  var self = this;
  this.promise = new Promise(function(resolve, reject) {
    self.resolve = resolve;
    self.reject = reject;
  });
};

/**
 * Resolve promise
 * @method Deferred#resolve
 * @param {*} result - Resolving result
 */

/**
 * Reject promise
 * @method Deferred#reject
 * @param {Error} error - Rejecting reason
 */

/**
 *
 */
module.exports = Promise;
