'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;
    var lazyLoad = function () {
        // 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 getHarness(opts) {
        if (!opts) { opts = {}; }
        opts.autoclose = !canEmitExit;
        if (!harness) { harness = createExitHarness(opts, wait); }
        return harness;
    }
}());

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;

    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;

    function run() {
        if (running) { return; }
        running = true;
        var stream = harness.createStream({ objectMode: config.objectMode });
        var es = stream.pipe(config.stream || createDefaultStream());
        if (canEmitExit) {
            // 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; });
    }
}

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

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

    var test = function (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;
}
