"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());
    });
};
const constants = require("../constants");
const path = require("path");
const os = require("os");
class TestExecutionService {
    constructor($injector, $projectData, $platformService, $platformsData, $usbLiveSyncService, $devicePlatformsConstants, $httpClient, $config, $logger, $fs, $options, $pluginsService, $errors, $androidDebugService, $iOSDebugService, $devicesService, $childProcess) {
        this.$injector = $injector;
        this.$projectData = $projectData;
        this.$platformService = $platformService;
        this.$platformsData = $platformsData;
        this.$usbLiveSyncService = $usbLiveSyncService;
        this.$devicePlatformsConstants = $devicePlatformsConstants;
        this.$httpClient = $httpClient;
        this.$config = $config;
        this.$logger = $logger;
        this.$fs = $fs;
        this.$options = $options;
        this.$pluginsService = $pluginsService;
        this.$errors = $errors;
        this.$androidDebugService = $androidDebugService;
        this.$iOSDebugService = $iOSDebugService;
        this.$devicesService = $devicesService;
        this.$childProcess = $childProcess;
        this.allowedParameters = [];
    }
    startTestRunner(platform) {
        return __awaiter(this, void 0, void 0, function* () {
            this.platform = platform;
            this.$options.justlaunch = true;
            yield new Promise((resolve, reject) => {
                process.on('message', (launcherConfig) => __awaiter(this, void 0, void 0, function* () {
                    try {
                        let platformData = this.$platformsData.getPlatformData(platform.toLowerCase());
                        let projectDir = this.$projectData.projectDir;
                        yield this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device });
                        let projectFilesPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);
                        let configOptions = JSON.parse(launcherConfig);
                        this.$options.debugBrk = configOptions.debugBrk;
                        this.$options.debugTransport = configOptions.debugTransport;
                        let configJs = this.generateConfig(this.$options.port.toString(), configOptions);
                        this.$fs.writeFile(path.join(projectDir, TestExecutionService.CONFIG_FILE_NAME), configJs);
                        let socketIoJsUrl = `http://localhost:${this.$options.port}/socket.io/socket.io.js`;
                        let socketIoJs = (yield this.$httpClient.httpRequest(socketIoJsUrl)).body;
                        this.$fs.writeFile(path.join(projectDir, TestExecutionService.SOCKETIO_JS_FILE_NAME), socketIoJs);
                        if (!(yield this.$platformService.preparePlatform(platform))) {
                            this.$errors.failWithoutHelp("Verify that listed files are well-formed and try again the operation.");
                        }
                        this.detourEntryPoint(projectFilesPath);
                        yield this.$platformService.deployPlatform(platform);
                        yield this.$usbLiveSyncService.liveSync(platform);
                        if (this.$options.debugBrk) {
                            this.$logger.info('Starting debugger...');
                            let debugService = this.$injector.resolve(`${platform}DebugService`);
                            yield debugService.debugStart();
                        }
                        resolve();
                    }
                    catch (err) {
                        reject(err);
                    }
                }));
                process.send("ready");
            });
        });
    }
    startKarmaServer(platform) {
        return __awaiter(this, void 0, void 0, function* () {
            platform = platform.toLowerCase();
            this.platform = platform;
            if (this.$options.debugBrk && this.$options.watch) {
                this.$errors.failWithoutHelp("You cannot use --watch and --debug-brk simultaneously. Remove one of the flags and try again.");
            }
            yield this.$pluginsService.ensureAllDependenciesAreInstalled();
            let projectDir = this.$projectData.projectDir;
            yield this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device });
            let karmaConfig = this.getKarmaConfiguration(platform), karmaRunner = this.$childProcess.fork(path.join(__dirname, "karma-execution.js")), 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;
                    let socketIoJsUrl = `http://${karmaData.url.host}/socket.io/socket.io.js`;
                    let socketIoJs = (yield this.$httpClient.httpRequest(socketIoJsUrl)).body;
                    this.$fs.writeFile(path.join(projectDir, TestExecutionService.SOCKETIO_JS_FILE_NAME), socketIoJs);
                }
                if (karmaData.launcherConfig) {
                    let configOptions = JSON.parse(karmaData.launcherConfig);
                    let configJs = this.generateConfig(port, configOptions);
                    this.$fs.writeFile(path.join(projectDir, TestExecutionService.CONFIG_FILE_NAME), configJs);
                }
                if (!(yield this.$platformService.preparePlatform(platform))) {
                    this.$errors.failWithoutHelp("Verify that listed files are well-formed and try again the operation.");
                }
                if (this.$options.debugBrk) {
                    yield this.getDebugService(platform).debug();
                }
                else {
                    yield this.$platformService.deployPlatform(platform);
                    yield this.$usbLiveSyncService.liveSync(platform);
                }
            });
            karmaRunner.on("message", (karmaData) => {
                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 });
            });
        });
    }
    detourEntryPoint(projectFilesPath) {
        let packageJsonPath = path.join(projectFilesPath, 'package.json');
        let packageJson = this.$fs.readJson(packageJsonPath);
        packageJson.main = TestExecutionService.MAIN_APP_NAME;
        this.$fs.writeJson(packageJsonPath, packageJson);
    }
    generateConfig(port, options) {
        let nics = os.networkInterfaces();
        let ips = Object.keys(nics)
            .map(nicName => nics[nicName].filter((binding) => binding.family === 'IPv4')[0])
            .filter(binding => binding)
            .map(binding => binding.address);
        let config = {
            port,
            ips,
            options,
        };
        return 'module.exports = ' + JSON.stringify(config);
    }
    getDebugService(platform) {
        let lowerCasedPlatform = platform.toLowerCase();
        if (lowerCasedPlatform === this.$devicePlatformsConstants.iOS.toLowerCase()) {
            return this.$iOSDebugService;
        }
        else if (lowerCasedPlatform === this.$devicePlatformsConstants.Android.toLowerCase()) {
            return this.$androidDebugService;
        }
        throw new Error(`Invalid platform ${platform}. Valid platforms are ${this.$devicePlatformsConstants.iOS} and ${this.$devicePlatformsConstants.Android}`);
    }
    getKarmaConfiguration(platform) {
        let karmaConfig = {
            browsers: [platform],
            configFile: path.join(this.$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
                }
            },
        };
        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 = this.$projectData.projectDir;
        this.$logger.debug(JSON.stringify(karmaConfig, null, 4));
        return karmaConfig;
    }
}
TestExecutionService.MAIN_APP_NAME = `./tns_modules/${constants.TEST_RUNNER_NAME}/app.js`;
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`;
$injector.register('testExecutionService', TestExecutionService);
