/**
* ******************************************************
*                                                       *
*   Copyright (C) Microsoft. All rights reserved.       *
*                                                       *
*******************************************************
*/
/// <reference path="../../typings/Q.d.ts" />
"use_strict";
var q = require("q");
var child_process = require("child_process");

var ioslib = require("ioslib");
var iosDevice = require("node-ios-device");
var path = require('path');
var fs = require('fs');

var VCRemoteLib;
(function (VCRemoteLib) {
    // See ios-deploy documentation for flags.
    // Note: not all flags mapped here, add as needed.
    var IosdOptions = (function () {
        function IosdOptions(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("Could not find ios-deploy at: " + iosdeployPath);
                }
                IosdOptions._iosdCmd = iosdeployPath;
            }
        }
        IosdOptions.prototype.buildCmd = function () {
            var cmdLine = IosdOptions._iosdCmd;
            cmdLine += ' --id ' + this.udid;

            if (this.checkConnected) {
                cmdLine += ' --detect';
                return cmdLine;
            }

            if (this.installOnly)
                cmdLine += ' --nostart';

            if (this.startOnly)
                cmdLine += ' --noinstall';

            if (this.reinstall)
                cmdLine += ' --uninstall';

            if (this.debug)
                cmdLine += ' --debug';

            if (this.justLaunch)
                cmdLine += ' --justlaunch';

            if (this.checkAppExists)
                cmdLine += ' --exists';

            if (this.bundleId != null)
                cmdLine += ' --bundle_id ' + this.bundleId;

            if (this.appPath != null)
                cmdLine += ' --bundle ' + '\"' + this.appPath + '\"';

            return cmdLine;
        };
        return IosdOptions;
    })();
    VCRemoteLib.IosdOptions = IosdOptions;
    ;

    var Device = (function () {
        function Device(udid, name, cpuArchitecture) {
            this._udid = udid;
            this._name = name;
            this._cpuArchitecture = cpuArchitecture;
        }
        Object.defineProperty(Device.prototype, "udid", {
            get: function () {
                return this._udid;
            },
            enumerable: true,
            configurable: true
        });

        Object.defineProperty(Device.prototype, "name", {
            get: function () {
                return this._name;
            },
            enumerable: true,
            configurable: true
        });

        Object.defineProperty(Device.prototype, "cpuArchitecture", {
            get: function () {
                return this._cpuArchitecture;
            },
            enumerable: true,
            configurable: true
        });

        Device.prototype.isConnected = function () {
            var deferred = q.defer();

            var iosdOptions = new IosdOptions(this.udid);
            iosdOptions.checkConnected = true;
            var self = this;
            child_process.exec(iosdOptions.buildCmd(), function (error, stdout, stderr) {
                if (error !== null) {
                    // If not found, error code is returned.
                    // Note: In future, we might possibly need to distinguish
                    // between other failures.
                    console.error('Device \'' + self.udid + '\' is not connected.');
                    deferred.reject(new Error('Device \'' + self.udid + '\' is not connected.'));
                } else {
                    deferred.resolve({});
                }
            });
            return deferred.promise;
        };

        Device.prototype.iosDeployHelper = function (iosdOptions, functionPrefix, printErrOnly) {
            var deferred = q.defer();
            var self = this;
            this.isConnected().then(function (connected) {
                var iosdCmd = iosdOptions.buildCmd() + ' --timeout 5';
                console.info(functionPrefix + ' executing command: ' + iosdCmd);
                child_process.exec(iosdCmd, function (error, stdout, stderr) {
                    if (error !== null) {
                        if (printErrOnly) {
                            console.error(functionPrefix + ' stdout: ' + stdout);
                            console.error(functionPrefix + ' stderr: ' + stderr);
                        }
                        var message = functionPrefix + 'error: ' + error;
                        console.error(message);
                        deferred.reject(new Error(message));
                    } else {
                        deferred.resolve({});
                    }
                });
            }).fail(function (reason) {
                var err = functionPrefix + ': device ' + self.udid + ' is disconnected';
                console.error(err);
                deferred.reject(new Error(err));
            });

            return deferred.promise;
        };

        Device.prototype.isAppInstalled = function (bundleId) {
            var iosdOptions = new IosdOptions(this.udid);
            iosdOptions.checkAppExists = true;
            iosdOptions.bundleId = bundleId;
            return this.iosDeployHelper(iosdOptions, 'Device.isAppInstalled', false);
        };

        Device.prototype.installApp = function (appPath) {
            var iosdOptions = new IosdOptions(this.udid);
            iosdOptions.installOnly = true;
            iosdOptions.appPath = appPath;
            return this.iosDeployHelper(iosdOptions, 'Device.installApp', true);
        };

        Device.prototype.reinstallApp = function (appPath) {
            var iosdOptions = new IosdOptions(this.udid);
            iosdOptions.reinstall = true;
            iosdOptions.appPath = appPath;
            return this.iosDeployHelper(iosdOptions, 'Device.reinstallApp', true);
        };

        Device.prototype.startApp = function (appPath) {
            var iosdOptions = new IosdOptions(this.udid);
            iosdOptions.startOnly = true;
            iosdOptions.justLaunch = true;
            iosdOptions.appPath = appPath;
            return this.iosDeployHelper(iosdOptions, 'Device.startApp', true);
        };

        Device.prototype.installStartApp = function (appPath) {
            var deferred = q.defer();

            var iosdOptions = new IosdOptions(this.udid);
            iosdOptions.debug = true;
            iosdOptions.justLaunch = true;
            iosdOptions.appPath = appPath;
            var iosdCmd = iosdOptions.buildCmd();

            var self = this;
            console.info('Device.installStartApp: starting application ' + appPath + ' on device ' + self.udid);
            child_process.exec(iosdCmd, function (error, stdout, stderr) {
                console.log('Device.installStartApp stdout: ' + stdout);
                console.log('Device.installStartApp stderr: ' + stderr);
                if (error !== null) {
                    console.log('exec error: ' + error);
                    deferred.reject('Error in Device.installStartApp: ' + error);
                } else {
                    console.info('Device.installStartApp: Successfully launched application \'' + appPath + '\' on device ' + self.udid);
                    deferred.resolve({});
                }
            });
            return deferred.promise;
        };
        return Device;
    })();
    VCRemoteLib.Device = Device;

    var Simulator = (function () {
        function Simulator(udid, name, ios) {
            this._handle = null;
            this._udid = udid;
            this._name = name;
            this._ios = ios;
            this._cpuArchitecture = "x86_64";
        }
        Object.defineProperty(Simulator.prototype, "udid", {
            get: function () {
                return this._udid;
            },
            enumerable: true,
            configurable: true
        });

        Object.defineProperty(Simulator.prototype, "name", {
            get: function () {
                return this._name;
            },
            enumerable: true,
            configurable: true
        });

        Object.defineProperty(Simulator.prototype, "ios", {
            get: function () {
                return this._ios;
            },
            enumerable: true,
            configurable: true
        });

        Object.defineProperty(Simulator.prototype, "cpuArchitecture", {
            get: function () {
                return this._cpuArchitecture;
            },
            enumerable: true,
            configurable: true
        });

        Simulator.prototype.start = function () {
            var deferred = q.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;
        };

        Simulator.prototype.stop = function () {
            var deferred = q.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.
        Simulator.prototype.installStartApp = function (options) {
            var deferred = q.defer();
            var self = this;
            ioslib.simulator.launch(self.udid, options).on('launched', function (simHandle) {
                console.log('Simulator \'' + self.name + '\' has launched');
                self._handle = simHandle;
            }).on('appStarted', function (msg) {
                console.log('Simulator.installStartApp: Successfully launched application \'' + options.appPath + '\' on simulator\'' + 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('Simulator.installStartApp: Application \'' + options.appPath + '\' on simulator\'' + self.name + '\' has quit');

                // 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;
        };
        return Simulator;
    })();
    VCRemoteLib.Simulator = Simulator;

    var DeviceDiscovery = (function () {
        function DeviceDiscovery() {
        }
        Object.defineProperty(DeviceDiscovery, "firstSimulator", {
            get: function () {
                var deferred = q.defer();
                new DeviceDiscovery().getSimulators().then(function (simulators) {
                    deferred.resolve(simulators[0]);
                }).fail(function (err) {
                    deferred.reject(err);
                });
                return deferred.promise;
            },
            enumerable: true,
            configurable: true
        });

        Object.defineProperty(DeviceDiscovery, "firstDevice", {
            get: function () {
                var deferred = q.defer();
                new DeviceDiscovery().getDevices().then(function (devices) {
                    deferred.resolve(devices[0]);
                }).fail(function (err) {
                    deferred.reject(err);
                });
                return deferred.promise;
            },
            enumerable: true,
            configurable: true
        });

        DeviceDiscovery.tryGetSimulatorByIdOrDefault = function (deviceUdid) {
            var deferred = q.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]);
            }).fail(function (err) {
                deferred.reject(err);
            });
            return deferred.promise;
        };

        DeviceDiscovery.tryGetDeviceByIdOrDefault = function (deviceUdid) {
            var deferred = q.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]);
            }).fail(function (err) {
                deferred.reject(err);
            });
            return deferred.promise;
        };

        // Gets list of iOS devices detected using 'ioslib'
        DeviceDiscovery.prototype.getDevices = function () {
            var deferred = q.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'
        DeviceDiscovery.prototype.getSimulators = function () {
            var deferred = q.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.
        DeviceDiscovery.prototype.trackDeviceChanges = function (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.
        DeviceDiscovery.prototype.stopDeviceTracking = function () {
            this._stopTrackingCallback();
        };
        return DeviceDiscovery;
    })();
    VCRemoteLib.DeviceDiscovery = DeviceDiscovery;
})(VCRemoteLib || (VCRemoteLib = {}));

module.exports = VCRemoteLib;
