'use strict';

var defined = require('defined');
var createDefaultStream = require('./lib/default_stream');
var Test = require('./lib/test');
var createResult = require('./lib/results');
var through = require('through');

var canEmitExit = typeof process !== 'undefined' && process
    && typeof process.on === 'function' && process.browser !== true;
var canExit = typeof process !== 'undefined' && process
    && typeof process.exit === 'function';

module.exports = (function () {
	var wait = false;
	var harness;

	function getHarness(opts) {
		if (!opts) { opts = {}; }
		opts.autoclose = !canEmitExit;
		// this override is here since tests fail via nyc if createHarness is moved upwards
		// eslint-disable-next-line no-use-before-define
		if (!harness) { harness = createExitHarness(opts, wait); }
		return harness;
	}

	function lazyLoad() {
		// eslint-disable-next-line no-invalid-this
		return getHarness().apply(this, arguments);
	}

	lazyLoad.wait = function () {
		wait = true;
	};

	lazyLoad.run = function () {
		var run = getHarness().run;

		if (run) { run(); }
	};

	lazyLoad.only = function () {
		return getHarness().only.apply(this, arguments);
	};

	lazyLoad.createStream = function (opts) {
		var options = opts || {};
		if (!harness) {
			var output = through();
			getHarness({ stream: output, objectMode: options.objectMode });
			return output;
		}
		return harness.createStream(options);
	};

	lazyLoad.onFinish = function () {
		return getHarness().onFinish.apply(this, arguments);
	};

	lazyLoad.onFailure = function () {
		return getHarness().onFailure.apply(this, arguments);
	};

	lazyLoad.getHarness = getHarness;

	return lazyLoad;
}());

function createHarness(conf_) {
	var results = createResult();
	if (!conf_ || conf_.autoclose !== false) {
		results.once('done', function () { results.close(); });
	}

	function test(name, conf, cb) {
		var t = new Test(name, conf, cb);
		test._tests.push(t);

		(function inspectCode(st) {
			st.on('test', function sub(st_) {
				inspectCode(st_);
			});
			st.on('result', function (r) {
				if (!r.todo && !r.ok && typeof r !== 'string') { test._exitCode = 1; }
			});
		}(t));

		results.push(t);
		return t;
	}
	test._results = results;

	test._tests = [];

	test.createStream = function (opts) {
		return results.createStream(opts);
	};

	test.onFinish = function (cb) {
		results.on('done', cb);
	};

	test.onFailure = function (cb) {
		results.on('fail', cb);
	};

	var only = false;
	test.only = function () {
		if (only) { throw new Error('there can only be one only test'); }
		if (conf_.noOnly) { throw new Error('`only` tests are prohibited'); }
		only = true;
		var t = test.apply(null, arguments);
		results.only(t);
		return t;
	};
	test._exitCode = 0;

	test.close = function () { results.close(); };

	return test;
}

function createExitHarness(conf, wait) {
	var config = conf || {};
	var harness = createHarness({
		autoclose: defined(config.autoclose, false),
		noOnly: defined(conf.noOnly, defined(process.env.NODE_TAPE_NO_ONLY_TEST, false))
	});
	var running = false;
	var ended = false;

	function run() {
		if (running) { return; }
		running = true;
		var stream = harness.createStream({ objectMode: config.objectMode });
		var es = stream.pipe(config.stream || createDefaultStream());
		if (canEmitExit && es) { // in node v0.4, `es` is `undefined`
			// TODO: use `err` arg?
			// eslint-disable-next-line no-unused-vars
			es.on('error', function (err) { harness._exitCode = 1; });
		}
		stream.on('end', function () { ended = true; });
	}

	if (wait) {
		harness.run = run;
	} else {
		run();
	}

	if (config.exit === false) { return harness; }
	if (!canEmitExit || !canExit) { return harness; }

	process.on('exit', function (code) {
		// let the process exit cleanly.
		if (typeof code === 'number' && code !== 0) {
			return;
		}

		if (!ended) {
			var only = harness._results._only;
			for (var i = 0; i < harness._tests.length; i++) {
				var t = harness._tests[i];
				if (!only || t === only) {
					t._exit();
				}
			}
		}
		harness.close();

		process.removeAllListeners('exit'); // necessary for node v0.6
		process.exit(code || harness._exitCode); // eslint-disable-line no-process-exit
	});

	return harness;
}

module.exports.createHarness = createHarness;
module.exports.Test = Test;
module.exports.test = module.exports; // tap compat
module.exports.test.skip = Test.skip;
