"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
var Suite_1 = require("../Suite");
var Test_1 = require("../Test");
var lang_1 = require("@dojo/core/lang");
var Task_1 = require("@dojo/core/async/Task");
var Formatter_1 = require("../common/Formatter");
var util_1 = require("../common/util");
var object_1 = require("../interfaces/object");
var tdd_1 = require("../interfaces/tdd");
var bdd_1 = require("../interfaces/bdd");
var benchmark_1 = require("../interfaces/benchmark");
var Promise_1 = require("@dojo/shim/Promise");
var chai = require("chai");
var global_1 = require("@dojo/core/global");
var Deferred_1 = require("../Deferred");
var GenericExecutor = (function () {
    function GenericExecutor(config) {
        this._hasSuiteErrors = false;
        this._config = {
            instrumenterOptions: {
                coverageVariable: '__internCoverage'
            },
            defaultTimeout: 30000,
            excludeInstrumentation: /(?:node_modules|browser|tests)\//
        };
        this._availableReporters = {};
        this._listeners = {};
        this._reporters = [];
        this._assertions = {};
        this._interfaces = {};
        this.registerInterface('object', object_1.default(this));
        this.registerInterface('tdd', tdd_1.default(this));
        this.registerInterface('bdd', bdd_1.default(this));
        this.registerInterface('benchmark', benchmark_1.default(this));
        this.registerAssertions('assert', chai.assert);
        this.registerAssertions('expect', chai.expect);
        this.registerAssertions('should', chai.should);
        if (config) {
            this.configure(config);
        }
        this._rootSuite = new Suite_1.default({
            executor: this,
            name: this.config.name
        });
    }
    Object.defineProperty(GenericExecutor.prototype, "config", {
        get: function () {
            return this._config;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(GenericExecutor.prototype, "formatter", {
        get: function () {
            if (!this._formatter) {
                this._formatter = new Formatter_1.default(this.config);
            }
            return this._formatter;
        },
        enumerable: true,
        configurable: true
    });
    GenericExecutor.prototype.addTest = function (suiteOrTest) {
        if (!(suiteOrTest instanceof Test_1.default) && !(suiteOrTest instanceof Suite_1.default)) {
            if (Test_1.isTestOptions(suiteOrTest)) {
                suiteOrTest = new Test_1.default(suiteOrTest);
            }
            else if (Suite_1.isSuiteOptions(suiteOrTest)) {
                suiteOrTest = new Suite_1.default(suiteOrTest);
            }
            else {
                throw new Error('InvalidTest: argument is not a valid suite or test');
            }
        }
        this._rootSuite.add(suiteOrTest);
    };
    GenericExecutor.prototype.configure = function (config) {
        var _this = this;
        Object.keys(config).forEach(function (key) {
            _this._processOption(key, config[key]);
        });
    };
    GenericExecutor.prototype.createDeferred = function () {
        return new Deferred_1.default();
    };
    GenericExecutor.prototype.emit = function (eventName, data) {
        var _this = this;
        if (eventName === 'suiteEnd' && data.error) {
            this._hasSuiteErrors = true;
        }
        var notifications = [];
        (this._listeners[eventName] || []).forEach(function (listener) {
            notifications.push(Promise_1.default.resolve(listener(data)));
        });
        var starListeners = this._listeners['*'] || [];
        if (starListeners.length > 0) {
            var starEvent_1 = { name: eventName, data: data };
            starListeners.forEach(function (listener) {
                notifications.push(Promise_1.default.resolve(listener(starEvent_1)));
            });
        }
        if (notifications.length === 0) {
            if (eventName === 'error') {
                console.error('ERROR:', this.formatter.format(data));
            }
            return resolvedTask;
        }
        return Task_1.default.all(notifications).catch(function (error) {
            console.error("Error emitting " + eventName + ": " + _this.formatter.format(error));
        });
    };
    GenericExecutor.prototype.getAssertions = function (name) {
        var assertions = this._assertions[name];
        if (name === 'should') {
            return assertions();
        }
        return assertions;
    };
    GenericExecutor.prototype.getInterface = function (name) {
        return this._interfaces[name];
    };
    GenericExecutor.prototype.log = function () {
        var args = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            args[_i] = arguments[_i];
        }
        if (this.config.debug) {
            var message = args.map(function (arg) {
                var type = typeof arg;
                if (type === 'string') {
                    return arg;
                }
                if (type === 'function' || arg instanceof RegExp) {
                    return arg.toString();
                }
                if (arg instanceof Error) {
                    arg = { name: arg.name, message: arg.message, stack: arg.stack };
                }
                return JSON.stringify(arg);
            }).join(' ');
            return this.emit('log', message);
        }
        else {
            return resolvedTask;
        }
    };
    GenericExecutor.prototype.on = function (eventName, listener) {
        var listeners = this._listeners[eventName];
        if (!listeners) {
            listeners = this._listeners[eventName] = [];
        }
        if (listeners.indexOf(listener) === -1) {
            listeners.push(listener);
        }
        var handle = {
            destroy: function () {
                this.destroy = function () { };
                util_1.pullFromArray(listeners, listener);
            }
        };
        return handle;
    };
    GenericExecutor.prototype.registerAssertions = function (name, assertions) {
        this._assertions[name] = assertions;
    };
    GenericExecutor.prototype.registerInterface = function (name, iface) {
        this._interfaces[name] = iface;
    };
    GenericExecutor.prototype.registerLoader = function (loader) {
        this._loader = loader;
    };
    GenericExecutor.prototype.registerReporter = function (name, Class) {
        this._availableReporters[name] = Class;
    };
    GenericExecutor.prototype.run = function () {
        var _this = this;
        if (!this._runTask) {
            try {
                this._runTask = this._preloadScripts()
                    .then(function () { return _this._resolveConfig(); })
                    .then(function () { return _this.emit('beforeRun'); })
                    .then(function () { return _this._beforeRun(); })
                    .then(function () { return _this._loadSuites(); })
                    .then(function () {
                    return _this.emit('runStart')
                        .then(function () { return _this._runTests(); })
                        .finally(function () { return _this.emit('runEnd'); });
                })
                    .finally(function () { return _this._afterRun(); })
                    .finally(function () { return _this.emit('afterRun'); })
                    .then(function () {
                    if (_this._hasSuiteErrors) {
                        throw new Error('One or more suite errors occurred during testing');
                    }
                })
                    .catch(function (error) {
                    _this.emit('error', error);
                    throw error;
                });
            }
            catch (error) {
                this._runTask = this.emit('error', error).then(function () {
                    return Task_1.default.reject(error);
                });
            }
        }
        return this._runTask;
    };
    GenericExecutor.prototype.setInterface = function (name, iface) {
        this._interfaces[name] = iface;
    };
    GenericExecutor.prototype._afterRun = function () {
        return resolvedTask;
    };
    GenericExecutor.prototype._beforeRun = function () {
        var _this = this;
        this.config.reporters.forEach(function (reporter) {
            if (typeof reporter === 'string') {
                var ReporterClass = _this._getReporter(reporter);
                _this._reporters.push(new ReporterClass(_this));
            }
            else {
                var ReporterClass = _this._getReporter(reporter.reporter);
                _this._reporters.push(new ReporterClass(_this, reporter.options));
            }
        });
        return resolvedTask;
    };
    GenericExecutor.prototype._emitCoverage = function () {
        var coverage = global_1.default[this.config.instrumenterOptions.coverageVariable];
        if (coverage) {
            return this.emit('coverage', { coverage: coverage, sessionId: this.config.sessionId });
        }
    };
    GenericExecutor.prototype._getReporter = function (name) {
        if (!this._availableReporters[name]) {
            throw new Error("A reporter named \"" + name + "\" has not been registered");
        }
        return this._availableReporters[name];
    };
    GenericExecutor.prototype._loadSuites = function (config) {
        var _this = this;
        config = config || this.config;
        var loader = config.loader.script;
        switch (loader) {
            case 'default':
            case 'dojo':
            case 'dojo2':
            case 'systemjs':
                config.loader.script = this.config.internPath + "loaders/" + loader + ".js";
        }
        return this.loadScript(config.loader.script).then(function () {
            if (!_this._loader) {
                throw new Error("Loader script " + config.loader.script + " did not register a loader callback");
            }
            return Task_1.default.resolve(_this._loader(config || _this.config));
        });
    };
    GenericExecutor.prototype._preloadScripts = function () {
        if (this.config.preload) {
            return this.loadScript(this.config.preload);
        }
        return resolvedTask;
    };
    GenericExecutor.prototype._processOption = function (name, value) {
        switch (name) {
            case 'loader':
                if (typeof value === 'string') {
                    try {
                        value = util_1.parseValue(name, value, 'object');
                    }
                    catch (error) {
                        value = { script: value };
                    }
                }
                if (!value.script) {
                    throw new Error("Invalid value \"" + value + "\" for " + name);
                }
                this.config[name] = value;
                break;
            case 'bail':
            case 'baseline':
            case 'benchmark':
            case 'debug':
            case 'filterErrorStack':
                this.config[name] = util_1.parseValue(name, value, 'boolean');
                break;
            case 'internPath':
                this.config[name] = util_1.parseValue(name, value, 'string');
                break;
            case 'defaultTimeout':
                this.config[name] = util_1.parseValue(name, value, 'number');
                break;
            case 'excludeInstrumentation':
                if (value === true) {
                    this.config[name] = value;
                }
                else if (typeof value === 'string' || value instanceof RegExp) {
                    this.config[name] = util_1.parseValue(name, value, 'regexp');
                }
                else {
                    throw new Error("Invalid value \"" + value + "\" for " + name + "; must be (string | RegExp | true)");
                }
                break;
            case 'grep':
                this.config[name] = util_1.parseValue(name, value, 'regexp');
                break;
            case 'instrumenterOptions':
                this.config[name] = lang_1.deepMixin(this.config[name] || {}, util_1.parseValue(name, value, 'object'));
                break;
            case 'reporters':
                this.config[name] = (Array.isArray(value) ? value : [value]).map(function (reporter) {
                    if (typeof reporter === 'string') {
                        try {
                            reporter = JSON.parse(reporter);
                        }
                        catch (error) {
                            reporter = { reporter: reporter };
                        }
                    }
                    if (!reporter.reporter) {
                        throw new Error("Invalid value \"" + value + "\" for " + name);
                    }
                    return reporter;
                });
                break;
            case 'name':
                this.config[name] = util_1.parseValue(name, value, 'string');
                break;
            case 'benchmarkSuites':
            case 'preload':
            case 'suites':
                this.config[name] = util_1.parseValue(name, value, 'string[]');
                break;
            default:
                this.config[name] = value;
        }
    };
    GenericExecutor.prototype._resolveConfig = function () {
        var config = this.config;
        if (config.internPath != null) {
            config.internPath = util_1.normalizePath(config.internPath);
        }
        if (!config.loader) {
            config.loader = { script: 'default' };
        }
        if (config.grep == null) {
            config.grep = new RegExp('');
        }
        if (config.suites == null) {
            config.suites = [];
        }
        if (config.benchmarkSuites == null) {
            config.benchmarkSuites = [];
        }
        if (config.reporters == null) {
            config.reporters = [];
        }
        if (config.benchmark) {
            config.benchmarkConfig = lang_1.deepMixin({
                id: 'Benchmark',
                filename: 'baseline.json',
                mode: 'test',
                thresholds: {
                    warn: { rme: 3, mean: 5 },
                    fail: { rme: 6, mean: 10 }
                },
                verbosity: 0
            }, config.benchmarkConfig);
        }
        if (!config.reporters) {
            config.reporters = [];
        }
        return resolvedTask;
    };
    GenericExecutor.prototype._runTests = function () {
        var _this = this;
        return this._rootSuite.run().finally(function () { return _this._emitCoverage(); });
    };
    return GenericExecutor;
}());
exports.GenericExecutor = GenericExecutor;
function initialize(ExecutorClass, config) {
    if (global_1.default['intern']) {
        throw new Error('Intern has already been initialized in this environment');
    }
    var executor = new ExecutorClass(config);
    global_1.default.intern = executor;
    return executor;
}
exports.initialize = initialize;
var Executor = (function (_super) {
    __extends(Executor, _super);
    function Executor() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return Executor;
}(GenericExecutor));
exports.Executor = Executor;
exports.default = Executor;
var resolvedTask = Task_1.default.resolve();
//# sourceMappingURL=Executor.js.map