/**
  * ******************************************************
  *                                                       *
  *   Copyright (C) Microsoft. All rights reserved.       *
  *                                                       *
  *******************************************************
  */
/// <reference path="../customTypings/vcremote-utils.d.ts" />
/// <reference path="../../typings/plist.d.ts" />
"use strict";
const path = require("path");
const fs = require("fs");
const child_process = require("child_process");
const net = require("net");
const tls = require("tls");
const pl = require("plist");
const utils = require("vcremote-utils");
const nconf = require("nconf");
const quote = require("shell-quote/quote");
var resources = utils.ResourcesManager;
var Debug;
(function (Debug) {
    var serverInstance = null;
    var proxyInstance = null;
    var debugserverInstance = null;
    var secure = true;
    // Will use /localization directory with translated strings if exists in npm installation, otherwise use default english strings.
    var pathToLocalizedStrings = path.join(__dirname + "../../../../", "localization/app/localization/i18n");
    if (fs.existsSync(pathToLocalizedStrings)) {
        resources.init(nconf.get("lang"), pathToLocalizedStrings);
    }
    else {
        resources.init(nconf.get("lang"), path.join(__dirname, "../resources"));
    }
    Promise.defer = function () {
        var result = {};
        result.promise = new Promise(function (resolve, reject) {
            result.resolve = resolve;
            result.reject = reject;
        });
        return result;
    };
    function getLldbBinaryName() {
        if (process.arch === 'arm' || process.arch === 'arm64') {
            return "lldb-mi-arm";
        }
        else {
            return "lldb-mi";
        }
    }
    function init() {
        console.info(resources.getStringForLanguage(nconf.get("lang"), "VerifylldbInstalled", __dirname));
        process.env['PATH'] = path.resolve(__dirname, "bin") + ":" + process.env["PATH"];
        child_process.exec('which ' + getLldbBinaryName(), function (err, stdout, stderr) {
            if (err != null) {
                console.warn(resources.getStringForLanguage(nconf.get("lang"), "LldbNotInstalled"));
            }
            else {
                console.log(resources.getStringForLanguage(nconf.get("lang"), "LldbInstalled"));
            }
        });
    }
    Debug.init = init;
    function startDebugListenerServer(conf, req, res) {
        secure = utils.UtilHelper.argToBool(conf.serverConf().get("secure"));
        var idevicedebugserverproxyport = conf.serverConf().get("nativeDebugProxyPort") || 3001;
        var debugListenerPort = conf.serverConf().get("debugListenerPort") || 3002;
        var isDevice = (req.query.target || "device").toLowerCase().localeCompare("device") == 0;
        var udid = req.query.deviceUdid || "";
        checkLldb().then(function () {
            return startDebugServer(idevicedebugserverproxyport, isDevice, udid);
        }).then(function () {
            if (serverInstance !== null) {
                return null;
            }
            return createServer(secure, conf.certs());
        }).then(function (server) {
            return startServer(server, debugListenerPort);
        }).then(function (server) {
            if (server !== null) {
                serverInstance = server;
                console.log(resources.getStringForLanguage(nconf.get("lang"), "DebugListenerListening", debugListenerPort, idevicedebugserverproxyport));
            }
            else {
                console.log(resources.getStringForLanguage(nconf.get("lang"), "DebugListenerAlreadyListening"));
            }
            res.status(200).json({ idevicedebugserverproxyport: idevicedebugserverproxyport, debugListenerPort: debugListenerPort });
        }).catch(function (reason) {
            if (reason.message) {
                console.log(reason.message);
                console.log(reason.stack);
            }
            else {
                console.log(reason);
            }
            terminate();
            res.status(404).json(reason);
        }).finally();
    }
    Debug.startDebugListenerServer = startDebugListenerServer;
    function getAppRemotePath(req, res) {
        var packageId = req.query.package;
        let udidArgArr = [req.query.deviceUdid || ""];
        var udid = quote(udidArgArr);
        console.log("UDID: = " + udid);
        var command = "ideviceinstaller -l -o xml";
        if (udid.length > 0) {
            command = command + " -u " + udid;
        }
        new Promise(function (resolve, reject) {
            child_process.exec(command + "> /tmp/$$.ideviceinstaller && echo /tmp/$$.ideviceinstaller", (err, stout, sterr) => {
                if (err) {
                    reject(sterr);
                }
                else {
                    resolve(stout);
                }
            });
        }).then(function (args) {
            // First find the path of the app on the device
            var filename = args.trim();
            if (!/^\/tmp\/[0-9]+\.ideviceinstaller$/.test(filename)) {
                throw new Error("WrongInstalledAppsFile");
            }
            var list = pl.parse(fs.readFileSync(filename, 'utf8'));
            fs.unlinkSync(filename);
            for (var i = 0; i < list.length; ++i) {
                if (list[i].CFBundleIdentifier == packageId) {
                    var path = list[i].Path;
                    return path;
                }
            }
            throw new Error("PackageNotInstalled");
        }).then(function (remotePath) {
            res.status(200).send(remotePath);
        }).catch(function (reason) {
            console.log(reason);
            res.status(404).json(reason);
        }).finally();
    }
    Debug.getAppRemotePath = getAppRemotePath;
    function terminate() {
        closeListenerServer();
        killIDeviceDebugServerProxy();
    }
    Debug.terminate = terminate;
    function closeListenerServer() {
        if (serverInstance !== null) {
            serverInstance.close();
            serverInstance = null;
        }
    }
    function killIDeviceDebugServerProxy() {
        if (proxyInstance !== null) {
            proxyInstance.kill('SIGHUP'); // idevicedebugserverproxy does not exit from sigterm
            proxyInstance = null;
        }
    }
    function killDebugServer() {
        if (debugserverInstance !== null) {
            debugserverInstance.kill();
            debugserverInstance = null;
        }
    }
    function checkLldb() {
        var deferred = new Promise.defer;
        var lldb = child_process.exec(getLldbBinaryName() + " --version", function (err, stdout, stderr) {
            if (err) {
                deferred.reject("lldb-mi is not installed.");
            }
            else {
                deferred.resolve(null);
            }
        });
        return deferred.promise;
    }
    function startDebugServer(port, isDevice, udid) {
        if (isDevice) {
            return startIDeviceDebugServerProxy(port, udid);
        }
        else {
            killIDeviceDebugServerProxy();
            killDebugServer();
            var configString = fs.readFileSync(path.join(__dirname, "debug-config.json"), "utf8");
            var config = JSON.parse(configString.trim());
            var debugServerArgs = ["--native-regs", "localhost:" + port.toString()];
            debugserverInstance = child_process.spawn(config.debugServerPath, debugServerArgs);
            return debugserverInstance;
        }
    }
    function startIDeviceDebugServerProxy(port, udid) {
        killIDeviceDebugServerProxy();
        var args = [];
        if (udid.length > 0) {
            args.push("-u");
            args.push(udid);
        }
        args.push(port.toString());
        console.log(resources.getStringForLanguage(nconf.get("lang"), "StartingDeviceDebugServerProxy", args));
        proxyInstance = child_process.spawn("idevicedebugserverproxy", args);
        proxyInstance.stderr.on("data", function (data) {
            console.error("idevicedebugproxyserver err: " + data);
        });
        proxyInstance.stdout.on("data", function (data) {
            console.log("idevicedebugproxyserver out: " + data);
        });
        return proxyInstance;
    }
    function createServer(secure, certs) {
        if (secure && !certs) {
            throw new Error("Cannot start server without certs.");
        }
        else {
            return createUnsecureTcpServer();
        }
    }
    function createUnsecureTcpServer() {
        var server = net.createServer(function (socket) {
            onConnection(socket);
        });
        return server;
    }
    function createSecureTcpServer(certs) {
        var options = {
            key: certs.getKey(),
            cert: certs.getCert(),
            requestCert: true,
            rejectUnauthorized: true
        };
        var server = tls.createServer(options, function (socket) {
            if (!socket.authorized) {
                console.error(socket.authorizationError.message);
                socket.end();
            }
            onConnection(socket);
        });
        return server;
    }
    function startServer(server, port) {
        var deferred = new Promise.defer;
        if (server === null) {
            deferred.resolve(null);
        }
        else {
            server.on("error", function (error) {
                console.error(resources.getStringForLanguage(nconf.get("lang"), "DebugListenerShutdown"));
                console.log(resources.getStringForLanguage(nconf.get("lang"), "ErrorName", error.name));
                console.log(resources.getStringForLanguage(nconf.get("lang"), "ErrorMessage", error.message));
                closeListenerServer();
            });
            server.listen(port, function () {
                deferred.resolve(server);
            });
        }
        return deferred.promise;
    }
    function onConnection(stream) {
        var lldb = child_process.spawn(getLldbBinaryName(), ["--interpreter"]);
        lldb.stdout.on("data", function (data) {
            stream.write(data);
        });
        lldb.stderr.on("data", function (data) {
            stream.write(data);
        });
        lldb.on("error", function (error) {
            console.error("LLDB Error");
            console.error(resources.getStringForLanguage(nconf.get("lang"), "ErrorName", error.name));
            console.error(resources.getStringForLanguage(nconf.get("lang"), "ErrorMessage", error.message));
        });
        lldb.on("exit", function (code, signal) {
            console.log(resources.getStringForLanguage(nconf.get("lang"), "LldbExited"));
            stream.end();
            console.log(resources.getStringForLanguage(nconf.get("lang"), "UnexpectedDebuggerShutdown"));
            if (secure) {
                process.exit();
            }
        });
        stream.on("data", function (data) {
            if (lldb.stdin.writable) {
                lldb.stdin.write(data);
            }
        });
        stream.on("error", function (err) {
            console.error(resources.getStringForLanguage(nconf.get("lang"), "DebubListenerError"));
            lldb.kill();
        });
        stream.on("close", function (had_error) {
            console.log(resources.getStringForLanguage(nconf.get("lang"), "DebubListenerClosed"));
            lldb.kill();
        });
    }
})(Debug || (Debug = {}));
module.exports = Debug;
