/**
  * ******************************************************
  *                                                       *
  *   Copyright (C) Microsoft. All rights reserved.       *
  *                                                       *
  *******************************************************
  */
"use_strict";
"use strict";
const child_process = require("child_process");
var ioslib = require("ioslib");
var iosDevice = require("node-ios-device");
const path = require("path");
const fs = require("fs");
const utils = require("vcremote-utils");
const nconf = require("nconf");
var resources = utils.ResourcesManager;
var VCRemoteLib;
(function (VCRemoteLib) {
    // 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;
    };
    // See ios-deploy documentation for flags.
    // Note: not all flags mapped here, add as needed.
    class IosdOptions {
        constructor(udid) {
            this.udid = udid;
            this.install = false;
            this.reinstall = false;
            this.justLaunch = false;
            this.debug = false;
            this.checkAppExists = false;
            this.checkConnected = false;
            this.installOnly = false;
            this.startOnly = false;
            // Initialize path to the ios-deploy binary.
            if (IosdOptions._iosdCmd == null) {
                // TODO: require.resolve cannot resolve ios-deploy for some reason
                var iosdeployDir = path.resolve(__dirname + "/../../ios-deploy/build/Release");
                var iosdeployPath = iosdeployDir + "/ios-deploy";
                if (!fs.existsSync(iosdeployPath)) {
                    console.error(resources.getStringForLanguage(nconf.get("lang"), "FailedGetIosDeploy", iosdeployPath));
                }
                IosdOptions._iosdCmd = iosdeployPath;
            }
        }
        iosCmd() {
            return IosdOptions._iosdCmd;
        }
        buildCmd() {
            var cmdLine = ["--id", this.udid];
            if (this.checkConnected) {
                cmdLine.push("--detect");
                return cmdLine;
            }
            if (this.installOnly)
                cmdLine.push('--nostart');
            if (this.startOnly)
                cmdLine.push('--noinstall');
            if (this.reinstall)
                cmdLine.push('--uninstall');
            if (this.debug)
                cmdLine.push('--debug');
            if (this.justLaunch)
                cmdLine.push('--justlaunch');
            if (this.checkAppExists)
                cmdLine.push('--exists');
            if (this.bundleId != null)
                cmdLine.push('--bundle_id', this.bundleId);
            if (this.appPath != null)
                cmdLine.push('--bundle', this.appPath);
            return cmdLine;
        }
    }
    VCRemoteLib.IosdOptions = IosdOptions;
    ;
    class Device {
        get udid() {
            return this._udid;
        }
        get name() {
            return this._name;
        }
        get cpuArchitecture() {
            return this._cpuArchitecture;
        }
        constructor(udid, name, cpuArchitecture) {
            this._udid = udid;
            this._name = name;
            this._cpuArchitecture = cpuArchitecture;
        }
        isConnected() {
            var deferred = new Promise.defer;
            var iosdOptions = new IosdOptions(this.udid);
            iosdOptions.checkConnected = true;
            var self = this;
            child_process.execFile(iosdOptions.iosCmd(), iosdOptions.buildCmd(), function (err, stdout, stderr) {
                if (err !== null) {
                    // If not found, error code is returned. 
                    // Note: In future, we might possibly need to distinguish
                    // between other failures.
                    console.error(resources.getStringForLanguage(nconf.get("lang"), "DeviceNotConnectedError", self.udid));
                    deferred.reject(new Error(resources.getStringForLanguage(nconf.get("lang"), "DeviceNotConnectedError", self.udid)));
                }
                else {
                    deferred.resolve({});
                }
            });
            return deferred.promise;
        }
        iosDeployHelper(iosdOptions, functionPrefix, printErrOnly) {
            var deferred = new Promise.defer;
            var self = this;
            this.isConnected().then(function (connected) {
                var iosdCmd = iosdOptions.iosCmd();
                var iosdCmdArgs = iosdOptions.buildCmd();
                iosdCmdArgs.push('--timeout', '5');
                console.info(functionPrefix + ' executing command: ' + iosdCmd + ' ' + iosdCmdArgs.join(' '));
                child_process.execFile(iosdCmd, // CodeQL [SM01509] Safe use per thread model: All code is downloaded to user device, and can be altered if granted physical access, but still presents no threats as protected by manual certificate exchange and authentication. (https://threatmodelingportal.security.azure/Service/ReviewDetails?reviewId=e38e8e6a-8826-45de-8f13-9877e8e54dd2)
                iosdCmdArgs, function (err, stdout, stderr) {
                    if (err !== null) {
                        if (printErrOnly) {
                            console.error(functionPrefix + ' stdout: ' + stdout);
                            console.error(functionPrefix + ' stderr: ' + stderr);
                        }
                        var message = functionPrefix + 'error: ' + err.message;
                        console.error(message);
                        deferred.reject(new Error(message));
                    }
                    else {
                        deferred.resolve({});
                    }
                });
            }).catch(function (reason) {
                var err = resources.getStringForLanguage(nconf.get("lang"), "DeviceDisconnectedError", functionPrefix, self.udid);
                console.error(err);
                deferred.reject(new Error(err));
            });
            return deferred.promise;
        }
        isAppInstalled(bundleId) {
            var iosdOptions = new IosdOptions(this.udid);
            iosdOptions.checkAppExists = true;
            iosdOptions.bundleId = bundleId;
            return this.iosDeployHelper(iosdOptions, 'Device.isAppInstalled', false /*printErrorOnly*/);
        }
        installApp(appPath) {
            var iosdOptions = new IosdOptions(this.udid);
            iosdOptions.installOnly = true;
            iosdOptions.appPath = appPath;
            return this.iosDeployHelper(iosdOptions, 'Device.installApp', true /*printErrorOnly*/);
        }
        reinstallApp(appPath) {
            var iosdOptions = new IosdOptions(this.udid);
            iosdOptions.reinstall = true;
            iosdOptions.appPath = appPath;
            return this.iosDeployHelper(iosdOptions, 'Device.reinstallApp', true /*printErrorOnly*/);
        }
        startApp(appPath) {
            var iosdOptions = new IosdOptions(this.udid);
            iosdOptions.startOnly = true;
            iosdOptions.justLaunch = true;
            iosdOptions.appPath = appPath;
            return this.iosDeployHelper(iosdOptions, 'Device.startApp', true /*printErrorOnly*/);
        }
        installStartApp(appPath) {
            var deferred = new Promise.defer;
            var iosdOptions = new IosdOptions(this.udid);
            iosdOptions.debug = true;
            iosdOptions.justLaunch = true;
            iosdOptions.appPath = appPath;
            var iosdCmdArgs = iosdOptions.buildCmd();
            var self = this;
            console.info(resources.getStringForLanguage(nconf.get("lang"), "StartingApplication", appPath, self.udid));
            child_process.execFile(iosdOptions.iosCmd(), iosdCmdArgs, function (err, stdout, stderr) {
                console.log('Device.installStartApp stdout: ' + stdout);
                console.log('Device.installStartApp stderr: ' + stderr);
                if (err !== null) {
                    console.log('exec error: ' + err.message);
                    deferred.reject('Error in Device.installStartApp: ' + err.message);
                }
                else {
                    console.info(resources.getStringForLanguage(nconf.get("lang"), "SimulatorSuccessfullyLaunched", appPath, self.udid));
                    deferred.resolve({});
                }
            });
            return deferred.promise;
        }
    }
    VCRemoteLib.Device = Device;
    class Simulator {
        get udid() {
            return this._udid;
        }
        get name() {
            return this._name;
        }
        get ios() {
            return this._ios;
        }
        get cpuArchitecture() {
            return this._cpuArchitecture;
        }
        constructor(udid, name, ios) {
            this._handle = null;
            this._udid = udid;
            this._name = name;
            this._ios = ios;
            this._cpuArchitecture = "x86_64";
        }
        start() {
            var deferred = new Promise.defer;
            var self = this;
            ioslib.simulator.launch(self.udid, function (err, simHandle) {
                if (err) {
                    console.info('Simulator.start error: ' + err);
                    deferred.reject(err);
                }
                else {
                    self._handle = simHandle;
                    deferred.resolve({});
                }
            });
            return deferred.promise;
        }
        stop() {
            var deferred = new Promise.defer;
            var self = this;
            ioslib.simulator.stop(self._handle, function () {
                self._handle = null;
                deferred.resolve({});
            }).on('error', function (err) {
                deferred.reject(err);
            });
            return deferred.promise;
        }
        // See ioslib docs/code for supported options.
        installStartApp(options) {
            var deferred = new Promise.defer;
            var self = this;
            ioslib.simulator.launch(self.udid, options).on('launched', function (simHandle) {
                console.log(resources.getStringForLanguage(nconf.get("lang"), "SimulatorLaunched", self.name));
                self._handle = simHandle;
            }).on('appStarted', function (msg) {
                console.log(resources.getStringForLanguage(nconf.get("lang"), "SimulatorSuccessfullyLaunched", options.appPath, self.name));
                deferred.resolve({});
            }).on('log', function (msg) {
                console.log('[LOG] ' + options.appPath + ': ' + msg);
            }).on('error', function (err) {
                console.info('Simulator.installStartApp error: ' + err);
                deferred.reject(err);
            }).on('app-quit', function (msg) {
                console.log(resources.getStringForLanguage(nconf.get("lang"), "SimulatorQuit", options.appPath, self.name));
                // Note: we might need callback here.
                // ioslib has a bug where 'app-quit' might get called, even though 'app-start' never was.
                // in such case, reject the request.
                if (!deferred.promise.isFulfilled())
                    deferred.reject(new Error('Simulator.installStartApp: app quit but was never started.'));
            });
            return deferred.promise;
        }
    }
    VCRemoteLib.Simulator = Simulator;
    class DeviceDiscovery {
        static get firstSimulator() {
            var deferred = new Promise.defer;
            new DeviceDiscovery().getSimulators()
                .then(function (simulators) {
                deferred.resolve(simulators[0]);
            })
                .catch(function (err) {
                deferred.reject(err);
            });
            return deferred.promise;
        }
        static get firstDevice() {
            var deferred = new Promise.defer;
            new DeviceDiscovery().getDevices()
                .then(function (devices) {
                deferred.resolve(devices[0]);
            })
                .catch(function (err) {
                deferred.reject(err);
            });
            return deferred.promise;
        }
        static tryGetSimulatorByIdOrDefault(deviceUdid) {
            var deferred = new Promise.defer;
            new DeviceDiscovery().getSimulators().then(function (simulators) {
                if (deviceUdid != undefined && deviceUdid.length > 0) {
                    for (var i = 0; i < simulators.length; i++) {
                        if (simulators[i].udid === deviceUdid) {
                            deferred.resolve(simulators[i]);
                        }
                    }
                }
                deferred.resolve(simulators[0]);
            }).catch(function (err) {
                deferred.reject(err);
            });
            return deferred.promise;
        }
        static tryGetDeviceByIdOrDefault(deviceUdid) {
            var deferred = new Promise.defer;
            new DeviceDiscovery().getDevices().then(function (devices) {
                if (deviceUdid != undefined && deviceUdid.length > 0) {
                    for (var i = 0; i < devices.length; i++) {
                        if (devices[i].udid === deviceUdid) {
                            deferred.resolve(devices[i]);
                        }
                    }
                }
                deferred.resolve(devices[0]);
            }).catch(function (err) {
                deferred.reject(err);
            });
            return deferred.promise;
        }
        // Gets list of iOS devices detected using 'ioslib'
        getDevices() {
            var deferred = new Promise.defer;
            // Note: this is cached, possibly need to bypass
            ioslib.device.detect({ bypassCache: true }, function (err, results) {
                if (err) {
                    console.info('Error in DeviceDiscovery.getDevices: ' + err);
                    deferred.reject(err);
                }
                else {
                    var devsOut = new Array();
                    results.devices.forEach(function (dev) {
                        devsOut.push(new Device(dev.udid, dev.name, dev.cpuArchitecture));
                    });
                    deferred.resolve(devsOut);
                }
            });
            return deferred.promise;
        }
        // Gets list of iOS simulators detected using 'ioslib'
        getSimulators() {
            var deferred = new Promise.defer;
            ioslib.simulator.detect({ bypassCache: true }, function (err, results) {
                if (err) {
                    console.info('Error in DeviceDiscovery.getSimulators: ' + err);
                    deferred.reject(err);
                }
                else {
                    var simulators = new Array();
                    for (var version in results.simulators.ios) {
                        for (var i = 0; i < results.simulators.ios[version].length; i++) {
                            var simulator = results.simulators.ios[version][i];
                            simulators.push(new Simulator(simulator.udid, simulator.name, simulator.ios));
                        }
                    }
                    deferred.resolve(simulators);
                }
            });
            return deferred.promise;
        }
        // Will call devicesChangedCallback with list of Devices available
        // every time physical device is connected/disconnected.
        trackDeviceChanges(devicesChangedCallback) {
            this._stopTrackingCallback = iosDevice.trackDevices(function (err, devices) {
                if (err) {
                    console.info('Error in DeviceDiscovery.trackDeviceChanges: ' + err);
                    devicesChangedCallback(err, null);
                }
                else {
                    var devsOut = new Array();
                    devices.forEach(function (dev) {
                        devsOut.push(new Device(dev.udid, dev.name, dev.cpuArchitecture));
                    });
                    devicesChangedCallback(err, devsOut);
                }
            });
        }
        // Stop device tracking.
        stopDeviceTracking() {
            this._stopTrackingCallback();
        }
    }
    VCRemoteLib.DeviceDiscovery = DeviceDiscovery;
})(VCRemoteLib || (VCRemoteLib = {}));
module.exports = VCRemoteLib;
