"use strict";
const net = require("net");
const Future = require("fibers/future");
const helpers_1 = require("../common/helpers");
class AndroidDebugService {
    constructor($devicesService, $platformService, $platformsData, $projectData, $logger, $options, $childProcess, $hostInfo, $errors, $opener, $config, $processService, $androidDeviceDiscovery) {
        this.$devicesService = $devicesService;
        this.$platformService = $platformService;
        this.$platformsData = $platformsData;
        this.$projectData = $projectData;
        this.$logger = $logger;
        this.$options = $options;
        this.$childProcess = $childProcess;
        this.$hostInfo = $hostInfo;
        this.$errors = $errors;
        this.$opener = $opener;
        this.$config = $config;
        this.$processService = $processService;
        this.$androidDeviceDiscovery = $androidDeviceDiscovery;
        this._device = null;
    }
    get platform() { return "android"; }
    get device() {
        return this._device;
    }
    set device(newDevice) {
        this._device = newDevice;
    }
    debug() {
        return this.$options.emulator
            ? this.debugOnEmulator()
            : this.debugOnDevice();
    }
    debugOnEmulator() {
        return (() => {
            this.$platformService.deployPlatform(this.platform).wait();
            this.$androidDeviceDiscovery.startLookingForDevices().wait();
            this.debugOnDevice().wait();
        }).future()();
    }
    isPortAvailable(candidatePort) {
        let future = new Future();
        let server = net.createServer();
        server.on("error", (err) => {
            future.return(false);
        });
        server.once("close", () => {
            if (!future.isResolved()) {
                future.return(true);
            }
        });
        server.on("listening", (err) => {
            if (err) {
                future.return(false);
            }
            server.close();
        });
        server.listen(candidatePort, "localhost");
        return future;
    }
    getForwardedLocalDebugPortForPackageName(deviceId, packageName) {
        return (() => {
            let port = -1;
            let forwardsResult = this.device.adb.executeCommand(["forward", "--list"]).wait();
            let unixSocketName = `${packageName}-inspectorServer`;
            let regexp = new RegExp(`(?:${deviceId} tcp:)([\\d]+)(?= localabstract:${unixSocketName})`, "g");
            let match = regexp.exec(forwardsResult);
            if (match) {
                port = parseInt(match[1]);
            }
            else {
                let candidatePort = 40000;
                for (; !this.isPortAvailable(candidatePort).wait(); ++candidatePort) {
                    if (candidatePort > 65534) {
                        this.$errors.failWithoutHelp("Unable to find free local port.");
                    }
                }
                port = candidatePort;
                this.unixSocketForward(port, `${unixSocketName}`).wait();
            }
            return port;
        }).future()();
    }
    unixSocketForward(local, remote) {
        return this.device.adb.executeCommand(["forward", `tcp:${local}`, `localabstract:${remote}`]);
    }
    debugOnDevice() {
        return (() => {
            let packageFile = "";
            if (!this.$options.start && !this.$options.emulator && !this.$options.getPort) {
                let cachedDeviceOption = this.$options.forDevice;
                this.$options.forDevice = true;
                if (this.$options.rebuild) {
                    this.$platformService.buildPlatform(this.platform).wait();
                }
                this.$options.forDevice = !!cachedDeviceOption;
                let platformData = this.$platformsData.getPlatformData(this.platform);
                packageFile = this.$platformService.getLatestApplicationPackageForDevice(platformData).packageName;
                this.$logger.out("Using ", packageFile);
            }
            this.$devicesService.initialize({ platform: this.platform, deviceId: this.$options.device }).wait();
            let action = (device) => { return this.debugCore(device, packageFile, this.$projectData.projectId); };
            this.$devicesService.execute(action).wait();
        }).future()();
    }
    debugCore(device, packageFile, packageName) {
        return (() => {
            this.device = device;
            if (this.$options.getPort) {
                this.printDebugPort(device.deviceInfo.identifier, packageName).wait();
            }
            else if (this.$options.start) {
                this.attachDebugger(device.deviceInfo.identifier, packageName).wait();
            }
            else if (this.$options.stop) {
                this.detachDebugger(packageName).wait();
            }
            else {
                this.startAppWithDebugger(packageFile, packageName).wait();
                this.attachDebugger(device.deviceInfo.identifier, packageName).wait();
            }
        }).future()();
    }
    printDebugPort(deviceId, packageName) {
        return (() => {
            let port = this.getForwardedLocalDebugPortForPackageName(deviceId, packageName).wait();
            this.$logger.info("device: " + deviceId + " debug port: " + port + "\n");
        }).future()();
    }
    attachDebugger(deviceId, packageName) {
        return (() => {
            let startDebuggerCommand = ["am", "broadcast", "-a", `\"${packageName}-debug\"`, "--ez", "enable", "true"];
            this.device.adb.executeShellCommand(startDebuggerCommand).wait();
            if (this.$options.client) {
                let port = this.getForwardedLocalDebugPortForPackageName(deviceId, packageName).wait();
                this.startDebuggerClient(port).wait();
            }
        }).future()();
    }
    detachDebugger(packageName) {
        return this.device.adb.executeShellCommand(["am", "broadcast", "-a", `${packageName}-debug`, "--ez", "enable", "false"]);
    }
    startAppWithDebugger(packageFile, packageName) {
        return (() => {
            if (!this.$options.emulator && !this.$config.debugLivesync) {
                this.device.applicationManager.uninstallApplication(packageName).wait();
                this.device.applicationManager.installApplication(packageFile).wait();
            }
            this.debugStartCore().wait();
        }).future()();
    }
    debugStart() {
        return (() => {
            this.$devicesService.initialize({ platform: this.platform, deviceId: this.$options.device }).wait();
            let action = (device) => {
                this.device = device;
                return this.debugStartCore();
            };
            this.$devicesService.execute(action).wait();
        }).future()();
    }
    debugStop() {
        this.stopDebuggerClient();
        return Future.fromResult();
    }
    debugStartCore() {
        return (() => {
            let packageName = this.$projectData.projectId;
            this.device.applicationManager.stopApplication(packageName).wait();
            if (this.$options.debugBrk) {
                this.device.adb.executeShellCommand([`cat /dev/null > /data/local/tmp/${packageName}-debugbreak`]).wait();
            }
            this.device.adb.executeShellCommand([`cat /dev/null > /data/local/tmp/${packageName}-debugger-started`]).wait();
            this.device.applicationManager.startApplication(packageName).wait();
            this.waitForDebugger(packageName);
        }).future()();
    }
    waitForDebugger(packageName) {
        let waitText = `0 /data/local/tmp/${packageName}-debugger-started`;
        let maxWait = 12;
        let debugerStarted = false;
        while (maxWait > 0 && !debugerStarted) {
            let forwardsResult = this.device.adb.executeShellCommand(["ls", "-s", `/data/local/tmp/${packageName}-debugger-started`]).wait();
            maxWait--;
            debugerStarted = forwardsResult.indexOf(waitText) === -1;
            if (!debugerStarted) {
                helpers_1.sleep(500);
            }
        }
        if (debugerStarted) {
            this.$logger.info("# NativeScript Debugger started #");
        }
        else {
            this.$logger.warn("# NativeScript Debugger did not start in time #");
        }
    }
    startDebuggerClient(port) {
        return (() => {
            this.$logger.info(`To start debugging, open the following URL in Chrome:\nchrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=localhost:${port}\n`);
        }).future()();
    }
    stopDebuggerClient() {
        if (this._debuggerClientProcess) {
            this._debuggerClientProcess.kill();
            this._debuggerClientProcess = null;
        }
    }
}
$injector.register("androidDebugService", AndroidDebugService);
