// Limit number of concurrent function executions (to cLimit number).
// Limited calls are queued. Optionaly maximum queue length can also be
// controlled with qLimit value, any calls that would reach over that limit
// would be discarded (its promise would resolve with "Too many calls" error)

'use strict';

var toPosInt   = require('es5-ext/number/to-pos-integer')
  , callable   = require('es5-ext/object/valid-callable')
  , eeUnify    = require('event-emitter/unify')
  , deferred   = require('../../deferred')
  , isPromise  = require('../../is-promise')
  , assimilate = require('../../assimilate')

  , resolve = deferred.resolve, reject = deferred.reject
  , apply = Function.prototype.apply, max = Math.max
  , gateReject;

require('../promise/finally');

gateReject = function () {
	var e = new Error("Too many calls");
	e.type = 'deferred-gate-rejected';
	return reject(e);
};

module.exports = function (cLimit, qLimit) {
	var fn, count, decrement, unload, queue, run, result;
	fn = callable(this);
	cLimit = max(toPosInt(cLimit), 1);
	qLimit = ((qLimit == null) || isNaN(qLimit)) ? Infinity : toPosInt(qLimit);
	count = 0;
	queue = [];

	run = function (thisArg, args, def) {
		var r;
		try {
			r = apply.call(fn, thisArg, args);
		} catch (e) {
			if (!def) return reject(e);
			def.reject(e);
			unload();
			return;
		}
		r = assimilate(r);
		if (isPromise(r)) {
			if (def) eeUnify(def.promise, r);
			if (!r.resolved) {
				++count;
				if (def) def.resolve(r);
				return r.finally(decrement);
			}
			r = r.value;
		}
		if (!def) return resolve(r);
		def.resolve(r);
		unload();
	};

	decrement = function () {
		--count;
		unload();
	};

	unload = function () {
		var data;
		if ((data = queue.shift())) run.apply(null, data);
	};

	result = function () {
		var def;
		if (count >= cLimit) {
			if (queue.length < qLimit) {
				def = deferred();
				queue.push([this, arguments, def]);
				return def.promise;
			}
			return gateReject();
		}
		return run(this, arguments);
	};
	result.returnsPromise = true;
	return result;
};

// Limit number of concurrent function executions (to cLimit number).
// Limited calls are queued. Optionaly maximum queue length can also be
// controlled with qLimit value, any calls that would reach over that limit
// would be discarded (its promise would resolve with "Too many calls" error)

'use strict';

var toPosInt   = require('es5-ext/number/to-pos-integer')
  , callable   = require('es5-ext/object/valid-callable')
  , eeUnify    = require('event-emitter/unify')
  , deferred   = require('../../deferred')
  , isPromise  = require('../../is-promise')
  , assimilate = require('../../assimilate')

  , resolve = deferred.resolve, reject = deferred.reject
  , apply = Function.prototype.apply, max = Math.max
  , gateReject;

require('../promise/finally');

gateReject = function () {
	var e = new Error("Too many calls at the gate");
	e.code = 'DEFERRED_REJECTED_AT_GATE';
	e.type = 'deferred-gate-rejected';
	return reject(e);
};

module.exports = function (cLimit, qLimit) {
	var fn, count, decrement, unload, queue, run, result;
	fn = callable(this);
	cLimit = max(toPosInt(cLimit), 1);
	qLimit = ((qLimit == null) || isNaN(qLimit)) ? Infinity : toPosInt(qLimit);
	count = 0;
	queue = [];

	run = function (thisArg, args, def) {
		var r;
		try {
			r = apply.call(fn, thisArg, args);
		} catch (e) {
			if (!def) return reject(e);
			def.reject(e);
			unload();
			return;
		}
		r = assimilate(r);
		if (isPromise(r)) {
			if (def) eeUnify(def.promise, r);
			if (!r.resolved) {
				++count;
				if (def) def.resolve(r);
				return r.finally(decrement);
			}
			r = r.value;
		}
		if (!def) return resolve(r);
		def.resolve(r);
		unload();
	};

	decrement = function () {
		--count;
		unload();
	};

	unload = function () {
		var data;
		if ((data = queue.shift())) run.apply(null, data);
	};

	result = function () {
		var def;
		if (count >= cLimit) {
			if (queue.length < qLimit) {
				def = deferred();
				queue.push([this, arguments, def]);
				return def.promise;
			}
			return gateReject();
		}
		return run(this, arguments);
	};
	result.returnsPromise = true;
	return result;
};

improve gate error format