'use strict';
var Promise = require('bluebird');
var isPromise = require('is-promise');
var AvaError = require('./ava-error');

function noop() {}

module.exports = Concurrent;

function Concurrent(tests, bail) {
	if (!(this instanceof Concurrent)) {
		throw new TypeError('Class constructor Concurrent cannot be invoked without \'new\'');
	}

	if (!Array.isArray(tests)) {
		throw new TypeError('Expected an array of tests');
	}

	this.results = [];
	this.passed = true;
	this.reason = null;
	this.tests = tests;
	this.bail = bail || false;

	Object.keys(Concurrent.prototype).forEach(function (key) {
		this[key] = this[key].bind(this);
	}, this);
}

Concurrent.prototype.run = function () {
	var results;

	try {
		results = this.tests.map(this._runTest);
	} catch (err) {
		if (err instanceof AvaError) {
			return this._results();
		}

		throw err;
	}

	var isAsync = results.some(isPromise);

	if (isAsync) {
		return Promise.all(results)
			.catch(AvaError, noop)
			.then(this._results);
	}

	return this._results();
};

Concurrent.prototype._runTest = function (test, index) {
	var result = test.run();

	if (isPromise(result)) {
		var self = this;

		return result.then(function (result) {
			return self._addResult(result, index);
		});
	}

	return this._addResult(result, index);
};

Concurrent.prototype._addResult = function (result, index) {
	// always save result when not in bail mode or all previous tests pass
	if ((this.bail && this.passed) || !this.bail) {
		this.results[index] = result;
	}

	if (result.passed === false) {
		this.passed = false;

		// only set reason once
		if (!this.reason) {
			this.reason = result.reason;
		}

		if (this.bail) {
			throw new AvaError('Error in Concurrent while in bail mode');
		}
	}

	return result;
};

Concurrent.prototype._results = function () {
	return {
		passed: this.passed,
		reason: this.reason,
		result: this.results
	};
};