"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var util = require("util");
var Future = require("fibers/future");
var helpers = require("../../helpers");
var assert = require("assert");
var constants = require("../constants");
var decorators_1 = require("../../decorators");
var fiberBootstrap = require("../../fiber-bootstrap");
var DevicesService = (function () {
    function DevicesService($logger, $errors, $iOSSimulatorDiscovery, $iOSDeviceDiscovery, $androidDeviceDiscovery, $staticConfig, $messages, $mobileHelper, $deviceLogProvider, $hostInfo, $devicePlatformsConstants, $injector, $options) {
        this.$logger = $logger;
        this.$errors = $errors;
        this.$iOSSimulatorDiscovery = $iOSSimulatorDiscovery;
        this.$iOSDeviceDiscovery = $iOSDeviceDiscovery;
        this.$androidDeviceDiscovery = $androidDeviceDiscovery;
        this.$staticConfig = $staticConfig;
        this.$messages = $messages;
        this.$mobileHelper = $mobileHelper;
        this.$deviceLogProvider = $deviceLogProvider;
        this.$hostInfo = $hostInfo;
        this.$devicePlatformsConstants = $devicePlatformsConstants;
        this.$injector = $injector;
        this.$options = $options;
        this._devices = {};
        this.platforms = [];
        this._isInitialized = false;
        this.attachToDeviceDiscoveryEvents();
    }
    Object.defineProperty(DevicesService.prototype, "platform", {
        get: function () {
            return this._platform;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(DevicesService.prototype, "deviceCount", {
        get: function () {
            return this._device ? 1 : this.getDeviceInstances().length;
        },
        enumerable: true,
        configurable: true
    });
    DevicesService.prototype.getDevices = function () {
        return this.getDeviceInstances().map(function (deviceInstance) { return deviceInstance.deviceInfo; });
    };
    DevicesService.prototype.getDevicesForPlatform = function (platform) {
        return _.filter(this.getDeviceInstances(), function (d) { return d.deviceInfo.platform.toLowerCase() === platform.toLowerCase(); });
    };
    DevicesService.prototype.isAndroidDevice = function (device) {
        return this.$mobileHelper.isAndroidPlatform(device.deviceInfo.platform);
    };
    DevicesService.prototype.isiOSDevice = function (device) {
        return this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform) && !device.isEmulator;
    };
    DevicesService.prototype.isiOSSimulator = function (device) {
        return !!(this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform) && device.isEmulator);
    };
    DevicesService.prototype.setLogLevel = function (logLevel, deviceIdentifier) {
        this.$deviceLogProvider.setLogLevel(logLevel, deviceIdentifier);
    };
    DevicesService.prototype.isAppInstalledOnDevices = function (deviceIdentifiers, appIdentifier) {
        var _this = this;
        this.$logger.trace("Called isInstalledOnDevices for identifiers " + deviceIdentifiers + ". AppIdentifier is " + appIdentifier + ".");
        return _.map(deviceIdentifiers, function (deviceIdentifier) { return _this.isApplicationInstalledOnDevice(deviceIdentifier, appIdentifier); });
    };
    DevicesService.prototype.getDeviceInstances = function () {
        return _.values(this._devices);
    };
    DevicesService.prototype.getAllPlatforms = function () {
        var _this = this;
        if (this.platforms.length > 0) {
            return this.platforms;
        }
        this.platforms = _.filter(this.$mobileHelper.platformNames, function (platform) { return _this.$mobileHelper.getPlatformCapabilities(platform).cableDeploy; });
        return this.platforms;
    };
    DevicesService.prototype.getPlatform = function (platform) {
        var allSupportedPlatforms = this.getAllPlatforms();
        var normalizedPlatform = this.$mobileHelper.validatePlatformName(platform);
        if (!_.contains(allSupportedPlatforms, normalizedPlatform)) {
            this.$errors.failWithoutHelp("Deploying to %s connected devices is not supported. Build the " +
                "app using the `build` command and deploy the package manually.", normalizedPlatform);
        }
        return normalizedPlatform;
    };
    DevicesService.prototype.attachToDeviceDiscoveryEvents = function () {
        var _this = this;
        this.$iOSSimulatorDiscovery.on("deviceFound", function (device) { return _this.onDeviceFound(device); });
        this.$iOSSimulatorDiscovery.on("deviceLost", function (device) { return _this.onDeviceLost(device); });
        this.$iOSDeviceDiscovery.on("deviceFound", function (device) { return _this.onDeviceFound(device); });
        this.$iOSDeviceDiscovery.on("deviceLost", function (device) { return _this.onDeviceLost(device); });
        this.$androidDeviceDiscovery.on("deviceFound", function (device) { return _this.onDeviceFound(device); });
        this.$androidDeviceDiscovery.on("deviceLost", function (device) { return _this.onDeviceLost(device); });
    };
    DevicesService.prototype.onDeviceFound = function (device) {
        this.$logger.trace("Found device with identifier '%s'", device.deviceInfo.identifier);
        this._devices[device.deviceInfo.identifier] = device;
    };
    DevicesService.prototype.onDeviceLost = function (device) {
        this.$logger.trace("Lost device with identifier '%s'", device.deviceInfo.identifier);
        delete this._devices[device.deviceInfo.identifier];
    };
    DevicesService.prototype.detectCurrentlyAttachedDevices = function () {
        var _this = this;
        return (function () {
            try {
                _this.$iOSDeviceDiscovery.startLookingForDevices().wait();
                _this.$androidDeviceDiscovery.startLookingForDevices().wait();
                if (_this.$hostInfo.isDarwin) {
                    _this.$iOSSimulatorDiscovery.startLookingForDevices().wait();
                }
            }
            catch (err) {
                _this.$logger.trace("Error while detecting devices.", err);
            }
        }).future()();
    };
    DevicesService.prototype.startLookingForDevices = function () {
        var _this = this;
        return (function () {
            _this.$logger.trace("startLookingForDevices; platform is %s", _this._platform);
            if (!_this._platform) {
                _this.detectCurrentlyAttachedDevices().wait();
                setInterval(function () {
                    fiberBootstrap.run(function () {
                        try {
                            _this.$iOSDeviceDiscovery.checkForDevices().wait();
                        }
                        catch (err) {
                            _this.$logger.trace("Error while checking for new iOS devices.", err);
                        }
                        try {
                            _this.$androidDeviceDiscovery.checkForDevices().wait();
                        }
                        catch (err) {
                            _this.$logger.trace("Error while checking for new Android devices.", err);
                        }
                        try {
                            if (_this.$hostInfo.isDarwin) {
                                _this.$iOSSimulatorDiscovery.checkForDevices().wait();
                            }
                        }
                        catch (err) {
                            _this.$logger.trace("Error while checking for new iOS Simulators.", err);
                        }
                        _.each(_this._devices, function (device) {
                            try {
                                device.applicationManager.checkForApplicationUpdates().wait();
                            }
                            catch (err) {
                                _this.$logger.trace("Error checking for application updates on device " + device.deviceInfo.identifier + ".", err);
                            }
                        });
                    });
                }, DevicesService.DEVICE_LOOKING_INTERVAL).unref();
            }
            else if (_this.$mobileHelper.isiOSPlatform(_this._platform)) {
                _this.$iOSDeviceDiscovery.startLookingForDevices().wait();
                if (_this.$hostInfo.isDarwin) {
                    _this.$iOSSimulatorDiscovery.startLookingForDevices().wait();
                }
            }
            else if (_this.$mobileHelper.isAndroidPlatform(_this._platform)) {
                _this.$androidDeviceDiscovery.startLookingForDevices().wait();
            }
        }).future()();
    };
    DevicesService.prototype.getAllConnectedDevices = function () {
        if (!this._platform) {
            return this.getDeviceInstances();
        }
        else {
            return this.filterDevicesByPlatform();
        }
    };
    DevicesService.prototype.getDeviceByIndex = function (index) {
        this.validateIndex(index - 1);
        return this.getDeviceInstances()[index - 1];
    };
    DevicesService.prototype.getDeviceByIdentifier = function (identifier) {
        var searchedDevice = _.find(this.getDeviceInstances(), function (device) { return device.deviceInfo.identifier === identifier; });
        if (!searchedDevice) {
            this.$errors.fail(this.$messages.Devices.NotFoundDeviceByIdentifierErrorMessageWithIdentifier, identifier, this.$staticConfig.CLIENT_NAME.toLowerCase());
        }
        return searchedDevice;
    };
    DevicesService.prototype.getDevice = function (deviceOption) {
        var _this = this;
        return (function () {
            _this.startLookingForDevices().wait();
            var device = null;
            if (_this.hasDevice(deviceOption)) {
                device = _this.getDeviceByIdentifier(deviceOption);
            }
            else if (helpers.isNumber(deviceOption)) {
                device = _this.getDeviceByIndex(parseInt(deviceOption, 10));
            }
            if (!device) {
                _this.$errors.fail(_this.$messages.Devices.NotFoundDeviceByIdentifierErrorMessage, _this.$staticConfig.CLIENT_NAME.toLowerCase());
            }
            return device;
        }).future()();
    };
    DevicesService.prototype.executeOnDevice = function (action, canExecute) {
        var _this = this;
        return (function () {
            if (!canExecute || canExecute(_this._device)) {
                action(_this._device).wait();
            }
        }).future()();
    };
    DevicesService.prototype.executeOnAllConnectedDevices = function (action, canExecute) {
        var _this = this;
        return (function () {
            var devices = _this.getAllConnectedDevices();
            var sortedDevices = _.sortBy(devices, function (device) { return device.deviceInfo.platform; });
            var futures = _.map(sortedDevices, function (device) {
                if (!canExecute || canExecute(device)) {
                    var future = action(device);
                    Future.settle(future);
                    return future;
                }
                else {
                    return Future.fromResult();
                }
            });
            Future.wait(futures);
        }).future()();
    };
    DevicesService.prototype.deployOnDevices = function (deviceIdentifiers, packageFile, packageName) {
        var _this = this;
        this.$logger.trace("Called deployOnDevices for identifiers " + deviceIdentifiers + " for packageFile: " + packageFile + ". packageName is " + packageName + ".");
        return _.map(deviceIdentifiers, function (deviceIdentifier) { return _this.deployOnDevice(deviceIdentifier, packageFile, packageName); });
    };
    DevicesService.prototype.execute = function (action, canExecute, options) {
        var _this = this;
        return (function () {
            assert.ok(_this._isInitialized, "Devices services not initialized!");
            if (_this.hasDevices) {
                if (_this.$hostInfo.isDarwin && _this._platform && _this.$mobileHelper.isiOSPlatform(_this._platform) &&
                    _this.$options.emulator && !_this.isOnlyiOSSimultorRunning()) {
                    _this.startEmulator().wait();
                    var originalCanExecute = canExecute;
                    canExecute = function (dev) { return _this.isiOSSimulator(dev) && (!originalCanExecute || !!(originalCanExecute(dev))); };
                }
                _this.executeCore(action, canExecute).wait();
            }
            else {
                var message = constants.ERROR_NO_DEVICES;
                if (options && options.allowNoDevices) {
                    _this.$logger.info(message);
                }
                else {
                    if (!_this.$hostInfo.isDarwin && _this._platform && _this.$mobileHelper.isiOSPlatform(_this._platform)) {
                        _this.$errors.failWithoutHelp(message);
                    }
                    else {
                        _this.startEmulator().wait();
                        _this.executeCore(action, canExecute).wait();
                    }
                }
            }
        }).future()();
    };
    DevicesService.prototype.initialize = function (data) {
        var _this = this;
        if (this._isInitialized) {
            return Future.fromResult();
        }
        return (function () {
            data = data || {};
            _this._data = data;
            var platform = data.platform;
            var deviceOption = data.deviceId;
            if (platform && deviceOption) {
                _this._device = _this.getDevice(deviceOption).wait();
                _this._platform = _this._device.deviceInfo.platform;
                if (_this._platform !== _this.getPlatform(platform)) {
                    _this.$errors.fail("Cannot resolve the specified connected device. The provided platform does not match the provided index or identifier." +
                        "To list currently connected devices and verify that the specified pair of platform and index or identifier exists, run 'device'.");
                }
                _this.$logger.warn("Your application will be deployed only on the device specified by the provided index or identifier.");
            }
            else if (!platform && deviceOption) {
                _this._device = _this.getDevice(deviceOption).wait();
                _this._platform = _this._device.deviceInfo.platform;
            }
            else if (platform && !deviceOption) {
                _this._platform = _this.getPlatform(platform);
                _this.startLookingForDevices().wait();
            }
            else {
                if (data.skipInferPlatform) {
                    _this.startLookingForDevices().wait();
                }
                else {
                    _this.detectCurrentlyAttachedDevices().wait();
                    var devices = _this.getDeviceInstances();
                    var platforms = _(devices)
                        .map(function (device) { return device.deviceInfo.platform; })
                        .filter(function (pl) {
                        try {
                            return _this.getPlatform(pl);
                        }
                        catch (err) {
                            _this.$logger.warn(err.message);
                            return null;
                        }
                    })
                        .uniq()
                        .value();
                    if (platforms.length === 1) {
                        _this._platform = platforms[0];
                    }
                    else if (platforms.length === 0) {
                        _this.$errors.fail({ formatStr: constants.ERROR_NO_DEVICES, suppressCommandHelp: true });
                    }
                    else {
                        _this.$errors.fail("Multiple device platforms detected (%s). Specify platform or device on command line.", helpers.formatListOfNames(platforms, "and"));
                    }
                }
            }
            if (!_this.$hostInfo.isDarwin && _this._platform && _this.$mobileHelper.isiOSPlatform(_this._platform) && _this.$options.emulator) {
                _this.$errors.failWithoutHelp("You can use iOS simulator only on OS X.");
            }
            _this._isInitialized = true;
        }).future()();
    };
    Object.defineProperty(DevicesService.prototype, "hasDevices", {
        get: function () {
            if (!this._platform) {
                return this.getDeviceInstances().length !== 0;
            }
            else {
                return this.filterDevicesByPlatform().length !== 0;
            }
        },
        enumerable: true,
        configurable: true
    });
    DevicesService.prototype.isOnlyiOSSimultorRunning = function () {
        var devices = this.getDeviceInstances();
        return this._platform && this.$mobileHelper.isiOSPlatform(this._platform) && _.find(devices, function (d) { return d.isEmulator; }) && !_.find(devices, function (d) { return !d.isEmulator; });
    };
    DevicesService.prototype.getDeviceByDeviceOption = function () {
        return this._device;
    };
    DevicesService.prototype.deployOnDevice = function (deviceIdentifier, packageFile, packageName) {
        var _this = this;
        return (function () {
            var device = _this.getDeviceByIdentifier(deviceIdentifier);
            device.applicationManager.reinstallApplication(packageName, packageFile).wait();
            _this.$logger.info("Successfully deployed on device with identifier '" + device.deviceInfo.identifier + "'.");
            if (device.applicationManager.canStartApplication()) {
                try {
                    device.applicationManager.startApplication(packageName).wait();
                }
                catch (err) {
                    _this.$logger.trace("Unable to start application on device. Error is: ", err);
                }
            }
        }).future()();
    };
    DevicesService.prototype.hasDevice = function (identifier) {
        return _.some(this.getDeviceInstances(), function (device) { return device.deviceInfo.identifier === identifier; });
    };
    DevicesService.prototype.filterDevicesByPlatform = function () {
        var _this = this;
        return _.filter(this.getDeviceInstances(), function (device) { return device.deviceInfo.platform === _this._platform; });
    };
    DevicesService.prototype.validateIndex = function (index) {
        if (index < 0 || index > this.getDeviceInstances().length) {
            throw new Error(util.format(this.$messages.Devices.NotFoundDeviceByIndexErrorMessage, index, this.$staticConfig.CLIENT_NAME.toLowerCase()));
        }
    };
    DevicesService.prototype.resolveEmulatorServices = function () {
        if (this.$mobileHelper.isiOSPlatform(this._platform) && this.$hostInfo.isDarwin) {
            return this.$injector.resolve("iOSEmulatorServices");
        }
        else if (this.$mobileHelper.isAndroidPlatform(this._platform)) {
            return this.$injector.resolve("androidEmulatorServices");
        }
        return null;
    };
    DevicesService.prototype.startEmulator = function () {
        var _this = this;
        return (function () {
            var emulatorServices = _this.resolveEmulatorServices();
            if (!emulatorServices) {
                _this.$errors.failWithoutHelp("Unable to detect platform for which to start emulator.");
            }
            emulatorServices.startEmulator().wait();
            if (_this.$mobileHelper.isAndroidPlatform(_this._platform)) {
                _this.$androidDeviceDiscovery.startLookingForDevices().wait();
            }
            else if (_this.$mobileHelper.isiOSPlatform(_this._platform) && _this.$hostInfo.isDarwin) {
                _this.$iOSSimulatorDiscovery.startLookingForDevices().wait();
            }
        }).future()();
    };
    DevicesService.prototype.executeCore = function (action, canExecute) {
        if (this._device) {
            return this.executeOnDevice(action, canExecute);
        }
        return this.executeOnAllConnectedDevices(action, canExecute);
    };
    DevicesService.prototype.isApplicationInstalledOnDevice = function (deviceIdentifier, appIdentifier) {
        var _this = this;
        return (function () {
            var device = _this.getDeviceByIdentifier(deviceIdentifier);
            return device.applicationManager.isApplicationInstalled(appIdentifier).wait();
        }).future()();
    };
    DevicesService.DEVICE_LOOKING_INTERVAL = 2200;
    __decorate([
        decorators_1.exported("devicesService"), 
        __metadata('design:type', Function), 
        __metadata('design:paramtypes', []), 
        __metadata('design:returntype', Array)
    ], DevicesService.prototype, "getDevices", null);
    __decorate([
        decorators_1.exported("devicesService"), 
        __metadata('design:type', Function), 
        __metadata('design:paramtypes', [String, String]), 
        __metadata('design:returntype', void 0)
    ], DevicesService.prototype, "setLogLevel", null);
    __decorate([
        decorators_1.exportedPromise("devicesService"), 
        __metadata('design:type', Function), 
        __metadata('design:paramtypes', [Array, String]), 
        __metadata('design:returntype', Array)
    ], DevicesService.prototype, "isAppInstalledOnDevices", null);
    __decorate([
        decorators_1.exportedPromise("devicesService"), 
        __metadata('design:type', Function), 
        __metadata('design:paramtypes', [Array, String, String]), 
        __metadata('design:returntype', Array)
    ], DevicesService.prototype, "deployOnDevices", null);
    return DevicesService;
})();
exports.DevicesService = DevicesService;
$injector.register("devicesService", DevicesService);
