"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 Executor_1 = require("./Executor");
var Node_1 = require("./Node");
var BrowserStackTunnel_1 = require("digdug/BrowserStackTunnel");
var SeleniumTunnel_1 = require("digdug/SeleniumTunnel");
var SauceLabsTunnel_1 = require("digdug/SauceLabsTunnel");
var TestingBotTunnel_1 = require("digdug/TestingBotTunnel");
var CrossBrowserTestingTunnel_1 = require("digdug/CrossBrowserTestingTunnel");
var NullTunnel_1 = require("digdug/NullTunnel");
var Server_1 = require("../Server");
var lang_1 = require("@dojo/core/lang");
var Task_1 = require("@dojo/core/async/Task");
var Server_2 = require("leadfoot/Server");
var ProxiedSession_1 = require("../ProxiedSession");
var resolveEnvironments_1 = require("../resolveEnvironments");
var Suite_1 = require("../Suite");
var RemoteSuite_1 = require("../RemoteSuite");
var util_1 = require("../common/util");
var util_2 = require("../node/util");
var Environment_1 = require("../Environment");
var Command_1 = require("leadfoot/Command");
var Pretty_1 = require("../reporters/Pretty");
var Benchmark_1 = require("../reporters/Benchmark");
var Runner_1 = require("../reporters/Runner");
var Promise_1 = require("@dojo/shim/Promise");
var WebDriver = (function (_super) {
    __extends(WebDriver, _super);
    function WebDriver(config) {
        var _this = this;
        var defaults = {
            capabilities: { 'idle-timeout': 60 },
            contactTimeout: 30000,
            environmentRetries: 3,
            environments: [],
            maxConcurrency: Infinity,
            reporters: [{ reporter: 'runner' }],
            tunnel: 'selenium',
            tunnelOptions: { tunnelId: String(Date.now()) }
        };
        _this = _super.call(this, lang_1.deepMixin(defaults, config)) || this;
        _this._tunnels = {};
        _this.registerTunnel('null', NullTunnel_1.default);
        _this.registerTunnel('selenium', SeleniumTunnel_1.default);
        _this.registerTunnel('saucelabs', SauceLabsTunnel_1.default);
        _this.registerTunnel('browserstack', BrowserStackTunnel_1.default);
        _this.registerTunnel('testingbot', TestingBotTunnel_1.default);
        _this.registerTunnel('cbt', CrossBrowserTestingTunnel_1.default);
        _this.registerReporter('pretty', Pretty_1.default);
        _this.registerReporter('runner', Runner_1.default);
        _this.registerReporter('benchmark', Benchmark_1.default);
        return _this;
    }
    WebDriver.initialize = function (config) {
        return Executor_1.initialize(WebDriver, config);
    };
    Object.defineProperty(WebDriver.prototype, "environment", {
        get: function () {
            return 'webdriver';
        },
        enumerable: true,
        configurable: true
    });
    WebDriver.prototype.registerTunnel = function (name, Class) {
        this._tunnels[name] = Class;
    };
    WebDriver.prototype._afterRun = function () {
        var _this = this;
        return _super.prototype._afterRun.call(this)
            .finally(function () {
            var promises = [];
            if (_this.server) {
                promises.push(_this.server.stop().then(function () { return _this.emit('serverEnd', _this.server); }));
            }
            if (_this.tunnel) {
                promises.push(_this.tunnel.stop().then(function () { return _this.emit('tunnelStop', { tunnel: _this.tunnel }); }));
            }
            return Promise_1.default.all(promises)
                .then(function () { }, function (error) { return _this.emit('error', error); });
        });
    };
    WebDriver.prototype._beforeRun = function () {
        var _this = this;
        var config = this.config;
        var promise = _super.prototype._beforeRun.call(this).then(function () {
            var server = _this._createServer();
            return server.start().then(function () {
                _this.server = server;
                return _this.emit('serverStart', server);
            });
        });
        if (config.serveOnly) {
            return promise.then(function () {
                return new Task_1.default(function () { }).finally(function () { return _this.server && _this.server.stop(); });
            });
        }
        return promise
            .then(function () {
            if (config.environments.length === 0) {
                throw new Error('No environments specified');
            }
            if (config.tunnel === 'browserstack') {
                var options = config.tunnelOptions;
                options.servers = (options.servers || []).concat(config.serverUrl);
            }
            if (config.functionalSuites.length + config.suites.length + config.benchmarkSuites.length > 0) {
                var TunnelConstructor = _this._tunnels[config.tunnel];
                _this.tunnel = new TunnelConstructor(_this.config.tunnelOptions);
            }
        })
            .then(function () {
            return Promise_1.default.all(['suites', 'functionalSuites', 'benchmarkSuites'].map(function (property) {
                return util_2.expandFiles(config[property]).then(function (expanded) {
                    config[property] = expanded;
                });
            })).then(function () { return null; });
        })
            .then(function () {
            var tunnel = _this.tunnel;
            if (!tunnel) {
                return;
            }
            tunnel.on('downloadprogress', function (progress) {
                _this.emit('tunnelDownloadProgress', { tunnel: tunnel, progress: progress });
            });
            tunnel.on('status', function (status) {
                _this.emit('tunnelStatus', { tunnel: tunnel, status: status.status });
            });
            config.capabilities = lang_1.deepMixin(tunnel.extraCapabilities, config.capabilities);
        })
            .then(function () { return _this._createSessionSuites(); })
            .then(function () {
            var tunnel = _this.tunnel;
            if (!tunnel) {
                return;
            }
            return tunnel.start().then(function () { return _this.emit('tunnelStart', { tunnel: tunnel }); });
        });
    };
    WebDriver.prototype._createServer = function () {
        var server = new Server_1.default({
            basePath: this.config.basePath,
            instrumenterOptions: this.config.instrumenterOptions,
            excludeInstrumentation: this.config.excludeInstrumentation,
            executor: this,
            instrument: true,
            port: this.config.serverPort,
            runInSync: this.config.runInSync,
            socketPort: this.config.socketPort
        });
        return server;
    };
    WebDriver.prototype._createSessionSuites = function () {
        var _this = this;
        var tunnel = this.tunnel;
        if (!this.tunnel) {
            return;
        }
        var config = this.config;
        if (config.environments.length === 0) {
            this._rootSuites = [];
            return;
        }
        var leadfootServer = new Server_2.default(tunnel.clientUrl, {
            proxy: tunnel.proxy
        });
        leadfootServer.sessionConstructor = ProxiedSession_1.default;
        return tunnel.getEnvironments().then(function (tunnelEnvironments) {
            var executor = _this;
            _this._rootSuites = resolveEnvironments_1.default(config.capabilities, config.environments, tunnelEnvironments).map(function (environmentType) {
                var suite = new Suite_1.default({
                    name: String(environmentType),
                    publishAfterSetup: true,
                    grep: config.grep,
                    bail: config.bail,
                    tests: [],
                    timeout: config.defaultTimeout,
                    executor: _this,
                    before: function () {
                        var _this = this;
                        return util_1.retry(function () { return leadfootServer.createSession(environmentType); }, config.environmentRetries).then(function (session) {
                            session.executor = executor;
                            session.coverageEnabled = config.excludeInstrumentation !== true;
                            session.coverageVariable = config.instrumenterOptions.coverageVariable;
                            session.serverUrl = config.serverUrl;
                            session.serverBasePathLength = config.basePath.length;
                            var remote = new Command_1.default(session);
                            remote.environmentType = new Environment_1.default(session.capabilities);
                            _this.remote = remote;
                            return executor.emit('sessionStart', remote);
                        });
                    },
                    after: function () {
                        var _this = this;
                        var remote = this.remote;
                        if (remote) {
                            var endSession = function () {
                                return executor.emit('sessionEnd', remote).then(function () {
                                    return tunnel.sendJobState(remote.session.sessionId, {
                                        success: _this.numFailedTests === 0 && !_this.error
                                    });
                                });
                            };
                            if (config.leaveRemoteOpen === true ||
                                (config.leaveRemoteOpen === 'fail' && this.numFailedTests > 0)) {
                                return endSession();
                            }
                            return remote.quit().finally(endSession);
                        }
                    }
                });
                if (_this._rootSuite) {
                    _this._rootSuite.name = 'functional tests';
                    suite.add(_this._rootSuite);
                }
                if (config.suites.length > 0) {
                    suite.add(new RemoteSuite_1.default({ name: 'unit tests' }));
                }
                return suite;
            });
        });
    };
    WebDriver.prototype._processOption = function (name, value) {
        switch (name) {
            case 'serverUrl':
                this.config[name] = util_1.parseValue(name, value, 'string');
                break;
            case 'capabilities':
            case 'tunnelOptions':
                this.config[name] = util_1.parseValue(name, value, 'object');
                break;
            case 'environments':
                if (typeof value === 'string') {
                    try {
                        value = util_1.parseValue(name, value, 'object');
                    }
                    catch (error) {
                        value = { browserName: value };
                    }
                }
                if (!Array.isArray(value)) {
                    value = [value];
                }
                this.config[name] = value.map(function (val) {
                    if (typeof val === 'string') {
                        try {
                            val = util_1.parseValue(name, val, 'object');
                        }
                        catch (error) {
                            val = { browserName: val };
                        }
                    }
                    if (typeof val !== 'object') {
                        throw new Error("Invalid value \"" + value + "\" for " + name + "; must (string | object)[]");
                    }
                    if (val.browser && !val.browserName) {
                        val.browserName = val.browser;
                    }
                    return val;
                });
                break;
            case 'tunnel':
                if (typeof value !== 'string' && typeof value !== 'function') {
                    throw new Error("Invalid value \"" + value + "\" for " + name);
                }
                this.config[name] = value;
                break;
            case 'leaveRemoteOpen':
            case 'serveOnly':
            case 'runInSync':
                this.config[name] = util_1.parseValue(name, value, 'boolean');
                break;
            case 'functionalSuites':
                this.config[name] = util_1.parseValue(name, value, 'string[]');
                break;
            case 'contactTimeout':
            case 'maxConcurrency':
            case 'environmentRetries':
            case 'serverPort':
            case 'socketPort':
                this.config[name] = util_1.parseValue(name, value, 'number');
                break;
            default:
                _super.prototype._processOption.call(this, name, value);
        }
    };
    WebDriver.prototype._loadSuites = function () {
        var config = lang_1.deepMixin({}, this.config, { suites: this.config.functionalSuites });
        return _super.prototype._loadSuites.call(this, config);
    };
    WebDriver.prototype._resolveConfig = function () {
        var config = this.config;
        return _super.prototype._resolveConfig.call(this).then(function () {
            if (!config.serverPort) {
                config.serverPort = 9000;
            }
            if (!config.socketPort) {
                config.socketPort = config.serverPort + 1;
            }
            if (!config.serverUrl) {
                config.serverUrl = 'http://localhost:' + config.serverPort;
            }
            config.serverUrl = config.serverUrl.replace(/\/*$/, '/');
            if (config.functionalSuites == null) {
                config.functionalSuites = [];
            }
            if (!config.capabilities.name) {
                config.capabilities.name = 'intern';
            }
            var buildId = process.env.TRAVIS_COMMIT || process.env.BUILD_TAG;
            if (buildId) {
                config.capabilities.build = buildId;
            }
            return util_2.expandFiles(config.functionalSuites).then(function (expanded) {
                config.functionalSuites = expanded;
            }).then(function () { return null; });
        });
    };
    WebDriver.prototype._runTests = function () {
        var _this = this;
        var rootSuites = this._rootSuites;
        var queue = new FunctionQueue(this.config.maxConcurrency || Infinity);
        var numSuitesToRun = rootSuites.length;
        var numSuitesCompleted = 0;
        return Task_1.default.all(rootSuites.map(function (suite) {
            return queue.enqueue(function () {
                return suite.run().finally(function () {
                    numSuitesCompleted++;
                    if (numSuitesCompleted === numSuitesToRun) {
                        return _this._emitCoverage();
                    }
                });
            });
        }));
    };
    return WebDriver;
}(Node_1.GenericNode));
exports.default = WebDriver;
var FunctionQueue = (function () {
    function FunctionQueue(maxConcurrency) {
        this.maxConcurrency = maxConcurrency;
        this.queue = [];
        this.activeTasks = [];
        this.funcTasks = [];
    }
    FunctionQueue.prototype.enqueue = function (func) {
        var resolver;
        var rejecter;
        var funcTask = new Task_1.default(function (resolve, reject) {
            resolver = resolve;
            rejecter = reject;
        });
        this.funcTasks.push(funcTask);
        this.queue.push({ func: func, resolver: resolver, rejecter: rejecter });
        if (this.activeTasks.length < this.maxConcurrency) {
            this.next();
        }
        return funcTask;
    };
    FunctionQueue.prototype.clear = function () {
        this.activeTasks.forEach(function (task) { return task.cancel(); });
        this.funcTasks.forEach(function (task) { return task.cancel(); });
        this.activeTasks = [];
        this.funcTasks = [];
        this.queue = [];
    };
    FunctionQueue.prototype.next = function () {
        var _this = this;
        if (this.queue.length > 0) {
            var _a = this.queue.shift(), func = _a.func, resolver = _a.resolver, rejecter = _a.rejecter;
            var task_1 = func().then(resolver, rejecter).finally(function () {
                util_1.pullFromArray(_this.activeTasks, task_1);
                _this.next();
            });
            this.activeTasks.push(task_1);
        }
    };
    return FunctionQueue;
}());
//# sourceMappingURL=WebDriver.js.map