"use strict";
var path = require("path");
var net = require("net");
var Future = require("fibers/future");
var helpers_1 = require("../common/helpers");
var AndroidDebugService = (function () {
    function AndroidDebugService($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;
    }
    Object.defineProperty(AndroidDebugService.prototype, "platform", {
        get: function () { return "android"; },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(AndroidDebugService.prototype, "device", {
        get: function () {
            return this._device;
        },
        set: function (newDevice) {
            this._device = newDevice;
        },
        enumerable: true,
        configurable: true
    });
    AndroidDebugService.prototype.debug = function () {
        return this.$options.emulator
            ? this.debugOnEmulator()
            : this.debugOnDevice();
    };
    AndroidDebugService.prototype.debugOnEmulator = function () {
        var _this = this;
        return (function () {
            _this.$platformService.deployOnEmulator(_this.platform).wait();
            _this.$androidDeviceDiscovery.startLookingForDevices().wait();
            _this.debugOnDevice().wait();
        }).future()();
    };
    AndroidDebugService.prototype.isPortAvailable = function (candidatePort) {
        var future = new Future();
        var server = net.createServer();
        server.on("error", function (err) {
            future.return(false);
        });
        server.once("close", function () {
            if (!future.isResolved()) {
                future.return(true);
            }
        });
        server.on("listening", function (err) {
            if (err) {
                future.return(false);
            }
            server.close();
        });
        server.listen(candidatePort, "localhost");
        return future;
    };
    AndroidDebugService.prototype.getForwardedLocalDebugPortForPackageName = function (deviceId, packageName) {
        var _this = this;
        return (function () {
            var port = -1;
            var forwardsResult = _this.device.adb.executeCommand(["forward", "--list"]).wait();
            var regexp = new RegExp("(?:" + deviceId + " tcp:)([\\d]+)(?= localabstract:" + packageName + "-debug)", "g");
            var match = regexp.exec(forwardsResult);
            if (match) {
                port = parseInt(match[1]);
            }
            else {
                var 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, packageName + "-debug").wait();
            }
            return port;
        }).future()();
    };
    AndroidDebugService.prototype.unixSocketForward = function (local, remote) {
        return this.device.adb.executeCommand(["forward", ("tcp:" + local), ("localabstract:" + remote)]);
    };
    AndroidDebugService.prototype.debugOnDevice = function () {
        var _this = this;
        return (function () {
            var packageFile = "";
            if (!_this.$options.start && !_this.$options.emulator && !_this.$options.getPort) {
                var cachedDeviceOption = _this.$options.forDevice;
                _this.$options.forDevice = true;
                if (_this.$options.rebuild) {
                    _this.$platformService.prepareAndExecute(_this.platform, function () { return _this.$platformService.buildPlatform(_this.platform); }).wait();
                }
                _this.$options.forDevice = !!cachedDeviceOption;
                var platformData = _this.$platformsData.getPlatformData(_this.platform);
                packageFile = _this.$platformService.getLatestApplicationPackageForDevice(platformData).wait().packageName;
                _this.$logger.out("Using ", packageFile);
            }
            _this.$devicesService.initialize({ platform: _this.platform, deviceId: _this.$options.device }).wait();
            var action = function (device) { return _this.debugCore(device, packageFile, _this.$projectData.projectId); };
            _this.$devicesService.execute(action).wait();
        }).future()();
    };
    AndroidDebugService.prototype.debugCore = function (device, packageFile, packageName) {
        var _this = this;
        return (function () {
            _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()();
    };
    AndroidDebugService.prototype.printDebugPort = function (deviceId, packageName) {
        var _this = this;
        return (function () {
            var port = _this.getForwardedLocalDebugPortForPackageName(deviceId, packageName).wait();
            _this.$logger.info("device: " + deviceId + " debug port: " + port + "\n");
        }).future()();
    };
    AndroidDebugService.prototype.attachDebugger = function (deviceId, packageName) {
        var _this = this;
        return (function () {
            var startDebuggerCommand = ["am", "broadcast", "-a", ("\"" + packageName + "-debug\""), "--ez", "enable", "true"];
            _this.device.adb.executeShellCommand(startDebuggerCommand).wait();
            if (_this.$options.client) {
                var port = _this.getForwardedLocalDebugPortForPackageName(deviceId, packageName).wait();
                _this.startDebuggerClient(port).wait();
                _this.openDebuggerClient(AndroidDebugService.DEFAULT_NODE_INSPECTOR_URL + "?port=" + port);
            }
        }).future()();
    };
    AndroidDebugService.prototype.detachDebugger = function (packageName) {
        return this.device.adb.executeShellCommand(["am", "broadcast", "-a", (packageName + "-debug"), "--ez", "enable", "false"]);
    };
    AndroidDebugService.prototype.startAppWithDebugger = function (packageFile, packageName) {
        var _this = this;
        return (function () {
            if (!_this.$options.emulator && !_this.$config.debugLivesync) {
                _this.device.applicationManager.uninstallApplication(packageName).wait();
                _this.device.applicationManager.installApplication(packageFile).wait();
            }
            _this.debugStartCore().wait();
        }).future()();
    };
    AndroidDebugService.prototype.debugStart = function () {
        var _this = this;
        return (function () {
            _this.$devicesService.initialize({ platform: _this.platform, deviceId: _this.$options.device }).wait();
            var action = function (device) {
                _this.device = device;
                return _this.debugStartCore();
            };
            _this.$devicesService.execute(action).wait();
        }).future()();
    };
    AndroidDebugService.prototype.debugStop = function () {
        this.stopDebuggerClient();
        return Future.fromResult();
    };
    AndroidDebugService.prototype.debugStartCore = function () {
        var _this = this;
        return (function () {
            var 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()();
    };
    AndroidDebugService.prototype.waitForDebugger = function (packageName) {
        var waitText = "0 /data/local/tmp/" + packageName + "-debugger-started";
        var maxWait = 12;
        var debugerStarted = false;
        while (maxWait > 0 && !debugerStarted) {
            var 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 #");
        }
    };
    AndroidDebugService.prototype.startDebuggerClient = function (port) {
        var _this = this;
        return (function () {
            var nodeInspectorModuleFilePath = require.resolve("node-inspector");
            var nodeInspectorModuleDir = path.dirname(nodeInspectorModuleFilePath);
            var nodeInspectorFullPath = path.join(nodeInspectorModuleDir, "bin", "inspector");
            _this._debuggerClientProcess = _this.$childProcess.spawn(process.argv[0], [nodeInspectorFullPath, "--debug-port", port.toString()], { stdio: "ignore", detached: true });
            _this.$processService.attachToProcessExitSignals(_this, _this.debugStop);
        }).future()();
    };
    AndroidDebugService.prototype.stopDebuggerClient = function () {
        if (this._debuggerClientProcess) {
            this._debuggerClientProcess.kill();
            this._debuggerClientProcess = null;
        }
    };
    AndroidDebugService.prototype.openDebuggerClient = function (url) {
        var defaultDebugUI = "chrome";
        if (this.$hostInfo.isDarwin) {
            defaultDebugUI = "Google Chrome";
        }
        if (this.$hostInfo.isLinux) {
            defaultDebugUI = "google-chrome";
        }
        var debugUI = this.$config.ANDROID_DEBUG_UI || defaultDebugUI;
        var child = this.$opener.open(url, debugUI);
        if (!child) {
            this.$errors.failWithoutHelp("Unable to open " + debugUI + ".");
        }
    };
    AndroidDebugService.DEFAULT_NODE_INSPECTOR_URL = "http://127.0.0.1:8080/debug";
    return AndroidDebugService;
}());
$injector.register("androidDebugService", AndroidDebugService);
