"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const constants = require("../constants");
const path = require("path");
const os = require("os");
class TestExecutionService {
    constructor($platformService, $liveSyncService, $httpClient, $config, $logger, $fs, $options, $pluginsService, $devicesService, $childProcess) {
        this.$platformService = $platformService;
        this.$liveSyncService = $liveSyncService;
        this.$httpClient = $httpClient;
        this.$config = $config;
        this.$logger = $logger;
        this.$fs = $fs;
        this.$options = $options;
        this.$pluginsService = $pluginsService;
        this.$devicesService = $devicesService;
        this.$childProcess = $childProcess;
        this.allowedParameters = [];
    }
    startKarmaServer(platform, projectData, projectFilesConfig) {
        return __awaiter(this, void 0, void 0, function* () {
            platform = platform.toLowerCase();
            this.platform = platform;
            yield this.$pluginsService.ensureAllDependenciesAreInstalled(projectData);
            const projectDir = projectData.projectDir;
            yield this.$devicesService.initialize({
                platform: platform,
                deviceId: this.$options.device,
                emulator: this.$options.emulator
            });
            const karmaConfig = this.getKarmaConfiguration(platform, projectData), karmaRunner = this.$childProcess.spawn(process.execPath, [path.join(__dirname, "karma-execution.js")], { stdio: ["inherit", "inherit", "inherit", "ipc"] }), launchKarmaTests = (karmaData) => __awaiter(this, void 0, void 0, function* () {
                this.$logger.trace("## Unit-testing: Parent process received message", karmaData);
                let port;
                if (karmaData.url) {
                    port = karmaData.url.port;
                    const socketIoJsUrl = `http://${karmaData.url.host}/socket.io/socket.io.js`;
                    const socketIoJs = (yield this.$httpClient.httpRequest(socketIoJsUrl)).body;
                    this.$fs.writeFile(path.join(projectDir, TestExecutionService.SOCKETIO_JS_FILE_NAME), socketIoJs);
                }
                if (karmaData.launcherConfig) {
                    const configOptions = JSON.parse(karmaData.launcherConfig);
                    const configJs = this.generateConfig(port, configOptions);
                    this.$fs.writeFile(path.join(projectDir, TestExecutionService.CONFIG_FILE_NAME), configJs);
                }
                let devices = [];
                if (this.$options.debugBrk) {
                    const selectedDeviceForDebug = yield this.$devicesService.pickSingleDevice({
                        onlyEmulators: this.$options.emulator,
                        onlyDevices: this.$options.forDevice,
                        deviceId: this.$options.device
                    });
                    devices = [selectedDeviceForDebug];
                }
                else {
                    devices = this.$devicesService.getDeviceInstances();
                }
                const platformLowerCase = this.platform && this.platform.toLowerCase();
                const deviceDescriptors = devices.filter(d => !platformLowerCase || d.deviceInfo.platform.toLowerCase() === platformLowerCase)
                    .map(d => {
                    const info = {
                        identifier: d.deviceInfo.identifier,
                        buildAction: () => __awaiter(this, void 0, void 0, function* () {
                            const buildConfig = {
                                buildForDevice: !d.isEmulator,
                                iCloudContainerEnvironment: this.$options.iCloudContainerEnvironment,
                                projectDir: this.$options.path,
                                clean: this.$options.clean,
                                teamId: this.$options.teamId,
                                device: this.$options.device,
                                provision: this.$options.provision,
                                release: this.$options.release,
                                keyStoreAlias: this.$options.keyStoreAlias,
                                keyStorePath: this.$options.keyStorePath,
                                keyStoreAliasPassword: this.$options.keyStoreAliasPassword,
                                keyStorePassword: this.$options.keyStorePassword
                            };
                            yield this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, projectData);
                            const pathToBuildResult = yield this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, projectData);
                            return pathToBuildResult;
                        }),
                        debugOptions: this.$options,
                        debugggingEnabled: this.$options.debugBrk
                    };
                    return info;
                });
                const env = this.$options.env || {};
                env.unitTesting = !!this.$options.bundle;
                const liveSyncInfo = {
                    projectDir: projectData.projectDir,
                    skipWatcher: !this.$options.watch || this.$options.justlaunch,
                    watchAllFiles: this.$options.syncAllFiles,
                    bundle: !!this.$options.bundle,
                    release: this.$options.release,
                    env,
                    timeout: this.$options.timeout,
                    useHotModuleReload: this.$options.hmr
                };
                yield this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo);
            });
            karmaRunner.on("message", (karmaData) => {
                this.$logger.trace(`The received message from karma is: `, karmaData);
                if (!karmaData.launcherConfig && !karmaData.url) {
                    return;
                }
                launchKarmaTests(karmaData)
                    .catch((result) => {
                    this.$logger.error(result);
                    process.exit(130);
                });
            });
            return new Promise((resolve, reject) => {
                karmaRunner.on("exit", (exitCode) => {
                    if (exitCode !== 0) {
                        const testError = new Error("Test run failed.");
                        testError.suppressCommandHelp = true;
                        reject(testError);
                    }
                    else {
                        resolve();
                    }
                });
                karmaRunner.send({ karmaConfig: karmaConfig });
            });
        });
    }
    canStartKarmaServer(projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            let canStartKarmaServer = true;
            const requiredDependencies = ["karma", "nativescript-unit-test-runner"];
            _.each(requiredDependencies, (dep) => {
                if (!projectData.dependencies[dep] && !projectData.devDependencies[dep]) {
                    canStartKarmaServer = false;
                    return;
                }
            });
            return canStartKarmaServer;
        });
    }
    generateConfig(port, options) {
        const nics = os.networkInterfaces();
        const ips = Object.keys(nics)
            .map(nicName => nics[nicName].filter((binding) => binding.family === 'IPv4')[0])
            .filter(binding => binding)
            .map(binding => binding.address);
        const config = {
            port,
            ips,
            options,
        };
        return 'module.exports = ' + JSON.stringify(config);
    }
    getKarmaConfiguration(platform, projectData) {
        const karmaConfig = {
            browsers: [platform],
            configFile: path.join(projectData.projectDir, 'karma.conf.js'),
            _NS: {
                log: this.$logger.getLevel(),
                path: this.$options.path,
                tns: process.argv[1],
                node: process.execPath,
                options: {
                    debugTransport: this.$options.debugTransport,
                    debugBrk: this.$options.debugBrk,
                    watch: !!this.$options.watch,
                    bundle: !!this.$options.bundle,
                    appDirectoryRelativePath: projectData.getAppDirectoryRelativePath()
                }
            },
        };
        if (this.$config.DEBUG || this.$logger.getLevel() === 'TRACE') {
            karmaConfig.logLevel = 'DEBUG';
        }
        if (!this.$options.watch) {
            karmaConfig.singleRun = true;
        }
        if (this.$options.debugBrk) {
            karmaConfig.browserNoActivityTimeout = 1000000000;
        }
        karmaConfig.projectDir = projectData.projectDir;
        karmaConfig.bundle = this.$options.bundle;
        karmaConfig.debugBrk = this.$options.debugBrk;
        karmaConfig.platform = platform.toLowerCase();
        this.$logger.debug(JSON.stringify(karmaConfig, null, 4));
        return karmaConfig;
    }
}
TestExecutionService.CONFIG_FILE_NAME = `node_modules/${constants.TEST_RUNNER_NAME}/config.js`;
TestExecutionService.SOCKETIO_JS_FILE_NAME = `node_modules/${constants.TEST_RUNNER_NAME}/socket.io.js`;
exports.TestExecutionService = TestExecutionService;
$injector.register('testExecutionService', TestExecutionService);
