"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 net = require("net");
const path = require("path");
const log4js = require("log4js");
const os = require("os");
const byline = require("byline");
const inspectorBackendPort = 18181;
const inspectorAppName = "NativeScript Inspector.app";
const inspectorNpmPackageName = "tns-ios-inspector";
const inspectorUiDir = "WebInspectorUI/";
const TIMEOUT_SECONDS = 9;
class IOSDebugService {
    constructor($platformService, $iOSEmulatorServices, $devicesService, $platformsData, $childProcess, $logger, $errors, $npmInstallationManager, $options, $utils, $iOSNotification, $iOSSocketRequestExecutor, $processService, $socketProxyFactory) {
        this.$platformService = $platformService;
        this.$iOSEmulatorServices = $iOSEmulatorServices;
        this.$devicesService = $devicesService;
        this.$platformsData = $platformsData;
        this.$childProcess = $childProcess;
        this.$logger = $logger;
        this.$errors = $errors;
        this.$npmInstallationManager = $npmInstallationManager;
        this.$options = $options;
        this.$utils = $utils;
        this.$iOSNotification = $iOSNotification;
        this.$iOSSocketRequestExecutor = $iOSSocketRequestExecutor;
        this.$processService = $processService;
        this.$socketProxyFactory = $socketProxyFactory;
        this._sockets = [];
        this.$processService.attachToProcessExitSignals(this, this.debugStop);
    }
    get platform() {
        return "ios";
    }
    debug(projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.$options.debugBrk && this.$options.start) {
                this.$errors.failWithoutHelp("Expected exactly one of the --debug-brk or --start options.");
            }
            if (this.$devicesService.isOnlyiOSSimultorRunning() || this.$devicesService.deviceCount === 0) {
                this.$options.emulator = true;
            }
            if (this.$options.emulator) {
                if (this.$options.debugBrk) {
                    return this.emulatorDebugBrk(projectData, true);
                }
                else if (this.$options.start) {
                    return this.emulatorStart(projectData);
                }
                else {
                    return this.emulatorDebugBrk(projectData);
                }
            }
            else {
                if (this.$options.debugBrk) {
                    return this.deviceDebugBrk(projectData, true);
                }
                else if (this.$options.start) {
                    return this.deviceStart(projectData);
                }
                else {
                    return this.deviceDebugBrk(projectData, false);
                }
            }
        });
    }
    debugStart(projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.$devicesService.initialize({ platform: this.platform, deviceId: this.$options.device });
            this.$devicesService.execute((device) => __awaiter(this, void 0, void 0, function* () { return (yield device.isEmulator) ? this.emulatorDebugBrk(projectData) : this.debugBrkCore(device, projectData); }));
        });
    }
    debugStop() {
        return __awaiter(this, void 0, void 0, function* () {
            if (this._socketProxy) {
                this._socketProxy.close();
                this._socketProxy = null;
            }
            _.forEach(this._sockets, socket => socket.destroy());
            this._sockets = [];
            if (this._lldbProcess) {
                this._lldbProcess.stdin.write("process detach\n");
                this._lldbProcess.kill();
                this._lldbProcess = undefined;
            }
            if (this._childProcess) {
                this._childProcess.kill();
                this._childProcess = undefined;
            }
        });
    }
    emulatorDebugBrk(projectData, shouldBreak) {
        return __awaiter(this, void 0, void 0, function* () {
            let platformData = this.$platformsData.getPlatformData(this.platform, projectData);
            let emulatorPackage = this.$platformService.getLatestApplicationPackageForEmulator(platformData);
            let args = shouldBreak ? "--nativescript-debug-brk" : "--nativescript-debug-start";
            let child_process = yield this.$iOSEmulatorServices.runApplicationOnEmulator(emulatorPackage.packageName, {
                waitForDebugger: true, captureStdin: true,
                args: args, appId: projectData.projectId,
                skipInstall: true
            });
            let lineStream = byline(child_process.stdout);
            this._childProcess = child_process;
            lineStream.on('data', (line) => {
                let lineText = line.toString();
                if (lineText && _.startsWith(lineText, projectData.projectId)) {
                    let pid = _.trimStart(lineText, projectData.projectId + ": ");
                    this._lldbProcess = this.$childProcess.spawn("lldb", ["-p", pid]);
                    if (log4js.levels.TRACE.isGreaterThanOrEqualTo(this.$logger.getLevel())) {
                        this._lldbProcess.stdout.pipe(process.stdout);
                    }
                    this._lldbProcess.stderr.pipe(process.stderr);
                    this._lldbProcess.stdin.write("process continue\n");
                }
                else {
                    process.stdout.write(line + "\n");
                }
            });
            yield this.wireDebuggerClient(projectData);
        });
    }
    emulatorStart(projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.wireDebuggerClient(projectData);
            let attachRequestMessage = this.$iOSNotification.getAttachRequest(projectData.projectId);
            let iOSEmulator = this.$iOSEmulatorServices;
            yield iOSEmulator.postDarwinNotification(attachRequestMessage);
        });
    }
    deviceDebugBrk(projectData, shouldBreak) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.$devicesService.initialize({ platform: this.platform, deviceId: this.$options.device });
            this.$devicesService.execute((device) => __awaiter(this, void 0, void 0, function* () {
                if (device.isEmulator) {
                    return yield this.emulatorDebugBrk(projectData, shouldBreak);
                }
                const runOptions = {
                    device: this.$options.device,
                    emulator: this.$options.emulator,
                    justlaunch: this.$options.justlaunch
                };
                let action = this.$platformService.runPlatform(this.platform, runOptions, projectData);
                yield this.debugBrkCore(device, projectData, shouldBreak);
                yield action;
            }));
        });
    }
    debugBrkCore(device, projectData, shouldBreak) {
        return __awaiter(this, void 0, void 0, function* () {
            let timeout = this.$utils.getMilliSecondsTimeout(TIMEOUT_SECONDS);
            yield this.$iOSSocketRequestExecutor.executeLaunchRequest(device.deviceInfo.identifier, timeout, timeout, projectData.projectId, shouldBreak);
            yield this.wireDebuggerClient(projectData, device);
        });
    }
    deviceStart(projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.$devicesService.initialize({ platform: this.platform, deviceId: this.$options.device });
            this.$devicesService.execute((device) => __awaiter(this, void 0, void 0, function* () { return device.isEmulator ? yield this.emulatorStart(projectData) : yield this.deviceStartCore(device, projectData); }));
        });
    }
    deviceStartCore(device, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            let timeout = this.$utils.getMilliSecondsTimeout(TIMEOUT_SECONDS);
            yield this.$iOSSocketRequestExecutor.executeAttachRequest(device, timeout, projectData.projectId);
            yield this.wireDebuggerClient(projectData, device);
        });
    }
    wireDebuggerClient(projectData, device) {
        return __awaiter(this, void 0, void 0, function* () {
            const factory = () => __awaiter(this, void 0, void 0, function* () {
                let socket = device ? yield device.connectToPort(inspectorBackendPort) : net.connect(inspectorBackendPort);
                this._sockets.push(socket);
                return socket;
            });
            if (this.$options.chrome) {
                this._socketProxy = this.$socketProxyFactory.createWebSocketProxy(factory);
                const commitSHA = "02e6bde1bbe34e43b309d4ef774b1168d25fd024";
                this.$logger.info(`To start debugging, open the following URL in Chrome:${os.EOL}chrome-devtools://devtools/remote/serve_file/@${commitSHA}/inspector.html?experiments=true&ws=localhost:${this._socketProxy.options.port}${os.EOL}`.cyan);
            }
            else {
                this._socketProxy = this.$socketProxyFactory.createTCPSocketProxy(factory);
                yield this.openAppInspector(this._socketProxy.address(), projectData);
            }
        });
    }
    openAppInspector(fileDescriptor, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.$options.client) {
                let inspectorPath = yield this.$npmInstallationManager.getInspectorFromCache(inspectorNpmPackageName, projectData.projectDir);
                let inspectorSourceLocation = path.join(inspectorPath, inspectorUiDir, "Main.html");
                let inspectorApplicationPath = path.join(inspectorPath, inspectorAppName);
                let cmd = `open -a '${inspectorApplicationPath}' --args '${inspectorSourceLocation}' '${projectData.projectName}' '${fileDescriptor}'`;
                yield this.$childProcess.exec(cmd);
            }
            else {
                this.$logger.info("Suppressing debugging client.");
            }
        });
    }
}
$injector.register("iOSDebugService", IOSDebugService);
