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 __());
    };
})();
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "./Suite", "@dojo/core/UrlSearchParams", "url", "@dojo/core/async/Task", "./common/util", "./Deferred"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    var Suite_1 = require("./Suite");
    var UrlSearchParams_1 = require("@dojo/core/UrlSearchParams");
    var url_1 = require("url");
    var Task_1 = require("@dojo/core/async/Task");
    var util_1 = require("./common/util");
    var Deferred_1 = require("./Deferred");
    /**
     * RemoteSuite is a class that acts as a local server for one or more unit test suites being run in a remote browser.
     */
    var RemoteSuite = (function (_super) {
        __extends(RemoteSuite, _super);
        function RemoteSuite(options) {
            var _this = this;
            options = options || {};
            if (options.name == null) {
                options.name = 'remote unit tests';
            }
            _this = _super.call(this, options) || this;
            if (_this.timeout == null) {
                _this.timeout = Infinity;
            }
            return _this;
        }
        Object.defineProperty(RemoteSuite.prototype, "id", {
            /**
             * Override Suite#id to exclude the RemoteSuite's name from the generated ID since the RemoteSuite is just a proxy
             * for a remote suite.
             */
            get: function () {
                var name = [];
                var suite = this.parent;
                do {
                    suite.name != null && name.unshift(suite.name);
                } while ((suite = suite.parent));
                return name.join(' - ');
            },
            enumerable: true,
            configurable: true
        });
        /**
         * Run a suite in a remote browser.
         */
        RemoteSuite.prototype.run = function () {
            var _this = this;
            var remote = this.remote;
            var sessionId = remote.session.sessionId;
            var server = this.executor.server;
            var listenerHandle;
            var connectTimer;
            return new Task_1.default(function (resolve, reject) {
                var handleError = function (error) {
                    _this.error = error;
                    reject(error);
                };
                // This is a deferred that will resolve when the remote sends back a 'remoteConfigured' message
                var pendingConnection = new Deferred_1.default();
                // If the remote takes to long to connect, reject the connection promise
                connectTimer = setTimeout(function () {
                    var error = new Error('Timed out waiting for remote to connect');
                    error.name = 'TimeoutError';
                    pendingConnection.reject(error);
                }, _this.executor.config.connectTimeout);
                // Subscribe to messages received by the server for a particular remote session ID.
                listenerHandle = server.subscribe(sessionId, function (name, data) {
                    var suite;
                    switch (name) {
                        case 'remoteStatus':
                            if (data === 'initialized') {
                                clearTimeout(connectTimer);
                                pendingConnection.resolve();
                            }
                            break;
                        case 'suiteStart':
                            suite = data;
                            if (!suite.hasParent) {
                                // This suite from the browser is a root suite; add its tests to the local suite
                                (_a = _this.tests).push.apply(_a, suite.tests);
                                // Tell the executor that the local suite has started
                                return _this.executor.emit('suiteStart', _this);
                            }
                            else {
                                // If suite from the browser isn't a root (i.e., it's a nested suite), just forward the
                                // start event
                                return _this.executor.emit(name, data);
                            }
                        case 'suiteEnd':
                            suite = data;
                            _this.skipped = suite.skipped;
                            if (!suite.hasParent) {
                                // When the remote root suite has finished, replace the local test objects with the
                                // incoming test data since it will include final results.
                                suite.tests.forEach(function (test, index) {
                                    _this.tests[index] = test;
                                });
                                if (suite.error) {
                                    handleError(suite.error);
                                }
                            }
                            else {
                                // If suite from the browser isn't a root, just forward the end event
                                return _this.executor.emit(name, data);
                            }
                            break;
                        case 'beforeRun':
                        case 'afterRun':
                        case 'runStart':
                            // Consume these events -- they shouldn't be forwarded to any local listeners
                            break;
                        case 'runEnd':
                            // Consume this event, and do some post-processing
                            var promise = remote.setHeartbeatInterval(0);
                            if (config.excludeInstrumentation !== true) {
                                // get about:blank to always collect code coverage data from the page in case it is
                                // navigated away later by some other process; this happens during self-testing when the
                                // Leadfoot library takes over
                                promise = promise.get('about:blank');
                            }
                            return promise.then(resolve, reject);
                        case 'error':
                            // Ignore summary suite error messages
                            if (!(/One or more suite errors/).test(data.message)) {
                                handleError(data);
                            }
                            break;
                        default:
                            return _this.executor.emit(name, data);
                    }
                    var _a;
                });
                var config = _this.executor.config;
                var serverUrl = url_1.parse(config.serverUrl);
                // Intern runs unit tests on the remote Selenium server by navigating to the client runner HTML page. No
                // real commands are issued after the call to remote.get() below until all unit tests are complete, so
                // we need to make sure that we periodically send no-ops through the channel to ensure the remote server
                // does not treat the session as having timed out
                var timeout = config.capabilities['idle-timeout'];
                if (timeout >= 1 && timeout < Infinity) {
                    remote.setHeartbeatInterval((timeout - 1) * 1000);
                }
                // These are options that will be passed as query params to the test harness page
                var queryOptions = {
                    basePath: serverUrl.pathname,
                    serverUrl: serverUrl.href,
                    sessionId: sessionId,
                    socketPort: server.socketPort
                };
                // Do some pre-serialization of the options
                var queryParams = {};
                Object.keys(queryOptions).filter(function (key) {
                    return queryOptions[key] != null;
                }).forEach(function (key) {
                    var value = queryOptions[key];
                    if (typeof value === 'object') {
                        value = JSON.stringify(value);
                    }
                    queryParams[key] = value;
                });
                var query = new UrlSearchParams_1.default(queryParams);
                var harness = config.serverUrl + "__intern/browser/remote.html";
                // These are options that will be POSTed to the remote page and used to configure intern. Stringify and
                // parse them to ensure that the config can be properly transmitted.
                var remoteConfig = {
                    debug: config.debug,
                    internPath: "" + serverUrl.pathname + config.internPath,
                    name: _this.id,
                    reporters: [{ name: 'dom' }]
                };
                // Don't overwrite any config data we've already set
                var excludeKeys = {
                    basePath: true,
                    internPath: true,
                    name: true,
                    reporters: true,
                    serverUrl: true,
                    sessionId: true,
                    socketPort: true
                };
                // Pass all non-excluded keys to the remote config
                Object.keys(config).filter(function (key) { return !excludeKeys[key]; }).forEach(function (key) {
                    remoteConfig[key] = config[key];
                });
                _this.executor.log('Configuring remote "', _this.name, '" with', remoteConfig);
                remote
                    .get(harness + "?" + query)
                    .then(function () { return pendingConnection.promise; })
                    .execute(/* istanbul ignore next */ function (configString) {
                    var options = JSON.parse(configString);
                    intern.configure(options);
                    intern.run().catch(function (_error) { });
                }, [util_1.stringify(remoteConfig)])
                    .catch(function (error) { return remote.setHeartbeatInterval(0).finally(function () { return handleError(error); }); });
            }, 
            // Canceller
            function () { return remote.setHeartbeatInterval(0); })
                .finally(function () {
                if (connectTimer) {
                    clearTimeout(connectTimer);
                }
                listenerHandle.destroy();
            })
                .finally(function () { return _this.executor.emit('suiteEnd', _this); });
        };
        return RemoteSuite;
    }(Suite_1.default));
    exports.default = RemoteSuite;
});
//# sourceMappingURL=RemoteSuite.js.map