"use strict";
const constants = require("../constants");
const path = require("path");
const Future = require("fibers/future");
const os = require("os");
const fiberBootstrap = require("../common/fiber-bootstrap");
class TestExecutionService {
    constructor($injector, $projectData, $platformService, $platformsData, $liveSyncProvider, $devicePlatformsConstants, $resources, $httpClient, $config, $logger, $fs, $options, $pluginsService, $errors, $androidDebugService, $iOSDebugService, $devicesService, $childProcess) {
        this.$injector = $injector;
        this.$projectData = $projectData;
        this.$platformService = $platformService;
        this.$platformsData = $platformsData;
        this.$liveSyncProvider = $liveSyncProvider;
        this.$devicePlatformsConstants = $devicePlatformsConstants;
        this.$resources = $resources;
        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 (() => {
            this.platform = platform;
            this.$options.justlaunch = true;
            let blockingOperationFuture = new Future();
            process.on('message', (launcherConfig) => {
                fiberBootstrap.run(() => {
                    try {
                        let platformData = this.$platformsData.getPlatformData(platform.toLowerCase());
                        let projectDir = this.$projectData.projectDir;
                        this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device }).wait();
                        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 = this.$httpClient.httpRequest(socketIoJsUrl).wait().body;
                        this.$fs.writeFile(path.join(projectDir, TestExecutionService.SOCKETIO_JS_FILE_NAME), socketIoJs);
                        if (!this.$platformService.preparePlatform(platform).wait()) {
                            this.$errors.failWithoutHelp("Verify that listed files are well-formed and try again the operation.");
                        }
                        this.detourEntryPoint(projectFilesPath);
                        this.liveSyncProject(platform);
                        if (this.$options.debugBrk) {
                            this.$logger.info('Starting debugger...');
                            let debugService = this.$injector.resolve(`${platform}DebugService`);
                            debugService.debugStart().wait();
                        }
                        blockingOperationFuture.return();
                    }
                    catch (err) {
                        blockingOperationFuture.throw(err);
                    }
                });
            });
            process.send("ready");
            blockingOperationFuture.wait();
        }).future()();
    }
    startKarmaServer(platform) {
        let karmaFuture = new Future();
        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.");
        }
        this.$pluginsService.ensureAllDependenciesAreInstalled().wait();
        let projectDir = this.$projectData.projectDir;
        this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device }).wait();
        let karmaConfig = this.getKarmaConfiguration(platform), karmaRunner = this.$childProcess.fork(path.join(__dirname, "karma-execution.js"));
        karmaRunner.on("message", (karmaData) => {
            fiberBootstrap.run(() => {
                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 = this.$httpClient.httpRequest(socketIoJsUrl).wait().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 (!this.$platformService.preparePlatform(platform).wait()) {
                    this.$errors.failWithoutHelp("Verify that listed files are well-formed and try again the operation.");
                }
                if (this.$options.debugBrk) {
                    this.getDebugService(platform).debug().wait();
                }
                else {
                    this.liveSyncProject(platform).wait();
                }
            });
        });
        karmaRunner.on("exit", (exitCode) => {
            if (exitCode !== 0) {
                const testError = new Error("Test run failed.");
                testError.suppressCommandHelp = true;
                karmaFuture.throw(testError);
            }
            else {
                karmaFuture.return();
            }
        });
        karmaRunner.send({ karmaConfig: karmaConfig });
        return karmaFuture;
    }
    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;
    }
    liveSyncProject(platform) {
        return (() => {
            let platformData = this.$platformsData.getPlatformData(platform.toLowerCase()), projectFilesPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);
            let liveSyncData = {
                platform: platform,
                appIdentifier: this.$projectData.projectId,
                projectFilesPath: projectFilesPath,
                syncWorkingDirectory: path.join(this.$projectData.projectDir, constants.APP_FOLDER_NAME),
                excludedProjectDirsAndFiles: this.$options.release ? constants.LIVESYNC_EXCLUDED_FILE_PATTERNS : []
            };
            let liveSyncService = this.$injector.resolve(this.$liveSyncProvider.platformSpecificLiveSyncServices[platform.toLowerCase()], { _liveSyncData: liveSyncData });
            liveSyncService.fullSync().wait();
        }).future()();
    }
}
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);
