"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;
};
const util = require("util");
const Future = require("fibers/future");
const helpers = require("../../helpers");
const assert = require("assert");
const constants = require("../../constants");
const fiberBootstrap = require("../../fiber-bootstrap");
const decorators_1 = require("../../decorators");
class DevicesService {
    constructor($logger, $errors, $iOSSimulatorDiscovery, $iOSDeviceDiscovery, $androidDeviceDiscovery, $staticConfig, $messages, $mobileHelper, $deviceLogProvider, $hostInfo, $devicePlatformsConstants, $injector, $options, $androidProcessService, $processService) {
        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.$androidProcessService = $androidProcessService;
        this.$processService = $processService;
        this._devices = {};
        this.platforms = [];
        this._isInitialized = false;
        this.attachToDeviceDiscoveryEvents();
    }
    get $companionAppsService() {
        return this.$injector.resolve("companionAppsService");
    }
    get platform() {
        return this._platform;
    }
    get deviceCount() {
        return this._device ? 1 : this.getDeviceInstances().length;
    }
    getDevices() {
        return this.getDeviceInstances().map(deviceInstance => deviceInstance.deviceInfo);
    }
    getDevicesForPlatform(platform) {
        return _.filter(this.getDeviceInstances(), d => d.deviceInfo.platform.toLowerCase() === platform.toLowerCase());
    }
    isAndroidDevice(device) {
        return this.$mobileHelper.isAndroidPlatform(device.deviceInfo.platform);
    }
    isiOSDevice(device) {
        return this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform) && !device.isEmulator;
    }
    isiOSSimulator(device) {
        return !!(this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform) && device.isEmulator);
    }
    setLogLevel(logLevel, deviceIdentifier) {
        this.$deviceLogProvider.setLogLevel(logLevel, deviceIdentifier);
    }
    isAppInstalledOnDevices(deviceIdentifiers, appIdentifier, framework) {
        this.$logger.trace(`Called isInstalledOnDevices for identifiers ${deviceIdentifiers}. AppIdentifier is ${appIdentifier}. Framework is: ${framework}.`);
        return _.map(deviceIdentifiers, deviceIdentifier => this.isApplicationInstalledOnDevice(deviceIdentifier, appIdentifier, framework));
    }
    isCompanionAppInstalledOnDevices(deviceIdentifiers, framework) {
        this.$logger.trace(`Called isCompanionAppInstalledOnDevices for identifiers ${deviceIdentifiers}. Framework is ${framework}.`);
        return _.map(deviceIdentifiers, deviceIdentifier => this.isCompanionAppInstalledOnDevice(deviceIdentifier, framework));
    }
    getDeviceInstances() {
        return _.values(this._devices);
    }
    getAllPlatforms() {
        if (this.platforms.length > 0) {
            return this.platforms;
        }
        this.platforms = _.filter(this.$mobileHelper.platformNames, platform => this.$mobileHelper.getPlatformCapabilities(platform).cableDeploy);
        return this.platforms;
    }
    getPlatform(platform) {
        let allSupportedPlatforms = this.getAllPlatforms();
        let normalizedPlatform = this.$mobileHelper.validatePlatformName(platform);
        if (!_.includes(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;
    }
    attachToDeviceDiscoveryEvents() {
        this.$iOSSimulatorDiscovery.on("deviceFound", (device) => this.onDeviceFound(device));
        this.$iOSSimulatorDiscovery.on("deviceLost", (device) => this.onDeviceLost(device));
        this.$iOSDeviceDiscovery.on("deviceFound", (device) => this.onDeviceFound(device));
        this.$iOSDeviceDiscovery.on("deviceLost", (device) => this.onDeviceLost(device));
        this.$androidDeviceDiscovery.on("deviceFound", (device) => this.onDeviceFound(device));
        this.$androidDeviceDiscovery.on("deviceLost", (device) => this.onDeviceLost(device));
    }
    onDeviceFound(device) {
        this.$logger.trace("Found device with identifier '%s'", device.deviceInfo.identifier);
        this._devices[device.deviceInfo.identifier] = device;
    }
    onDeviceLost(device) {
        this.$logger.trace("Lost device with identifier '%s'", device.deviceInfo.identifier);
        delete this._devices[device.deviceInfo.identifier];
    }
    detectCurrentlyAttachedDevices() {
        return (() => {
            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()();
    }
    startDeviceDetectionInterval() {
        this.$processService.attachToProcessExitSignals(this, this.clearDeviceDetectionInterval);
        if (this.deviceDetectionInterval) {
            this.$logger.trace("Device detection interval is already started. New Interval will not be started.");
        }
        else {
            this.deviceDetectionInterval = setInterval(() => {
                fiberBootstrap.run(() => {
                    if (this.deviceDetectionIntervalFuture) {
                        return;
                    }
                    this.deviceDetectionIntervalFuture = new Future();
                    try {
                        this.$iOSDeviceDiscovery.checkForDevices().wait();
                    }
                    catch (err) {
                        this.$logger.trace("Error while checking for new iOS devices.", err);
                    }
                    try {
                        this.$androidDeviceDiscovery.startLookingForDevices().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, device => {
                        try {
                            device.applicationManager.checkForApplicationUpdates().wait();
                        }
                        catch (err) {
                            this.$logger.trace(`Error checking for application updates on device ${device.deviceInfo.identifier}.`, err);
                        }
                    });
                    this.deviceDetectionIntervalFuture.return();
                    this.deviceDetectionIntervalFuture.wait();
                    this.deviceDetectionIntervalFuture = null;
                });
            }, DevicesService.DEVICE_LOOKING_INTERVAL).unref();
        }
    }
    stopDeviceDetectionInterval() {
        return (() => {
            this.clearDeviceDetectionInterval();
            this.deviceDetectionInterval = null;
            this.getDeviceDetectionIntervalFuture().wait();
        }).future()();
    }
    getDeviceByIdentifier(identifier) {
        let searchedDevice = _.find(this.getDeviceInstances(), (device) => { return device.deviceInfo.identifier === identifier; });
        if (!searchedDevice) {
            this.$errors.fail(this.$messages.Devices.NotFoundDeviceByIdentifierErrorMessageWithIdentifier, identifier, this.$staticConfig.CLIENT_NAME.toLowerCase());
        }
        return searchedDevice;
    }
    startLookingForDevices() {
        return (() => {
            this.$logger.trace("startLookingForDevices; platform is %s", this._platform);
            if (!this._platform) {
                this.detectCurrentlyAttachedDevices().wait();
                this.startDeviceDetectionInterval();
            }
            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()();
    }
    getDeviceByIndex(index) {
        this.validateIndex(index - 1);
        return this.getDeviceInstances()[index - 1];
    }
    getDevice(deviceOption) {
        return (() => {
            this.detectCurrentlyAttachedDevices().wait();
            let 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()();
    }
    executeOnDevice(action, canExecute) {
        return (() => {
            if (!canExecute || canExecute(this._device)) {
                action(this._device).wait();
            }
        }).future()();
    }
    executeOnAllConnectedDevices(action, canExecute) {
        return (() => {
            let devices = this.filterDevicesByPlatform();
            let sortedDevices = _.sortBy(devices, device => device.deviceInfo.platform);
            let futures = _.map(sortedDevices, (device) => {
                if (!canExecute || canExecute(device)) {
                    let future = action(device);
                    Future.settle(future);
                    return future;
                }
                else {
                    return Future.fromResult();
                }
            });
            Future.wait(futures);
        }).future()();
    }
    deployOnDevices(deviceIdentifiers, packageFile, packageName, framework) {
        this.$logger.trace(`Called deployOnDevices for identifiers ${deviceIdentifiers} for packageFile: ${packageFile}. packageName is ${packageName}.`);
        return _.map(deviceIdentifiers, deviceIdentifier => this.deployOnDevice(deviceIdentifier, packageFile, packageName, framework));
    }
    execute(action, canExecute, options) {
        return (() => {
            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();
                    let originalCanExecute = canExecute;
                    canExecute = (dev) => this.isiOSSimulator(dev) && (!originalCanExecute || !!(originalCanExecute(dev)));
                }
                this.executeCore(action, canExecute).wait();
            }
            else {
                let 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()();
    }
    initialize(data) {
        if (this._isInitialized) {
            return Future.fromResult();
        }
        return (() => {
            data = data || {};
            this._data = data;
            let platform = data.platform;
            let 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();
                    let devices = this.getDeviceInstances();
                    let platforms = _(devices)
                        .map(device => device.deviceInfo.platform)
                        .filter(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()();
    }
    get hasDevices() {
        if (!this._platform) {
            return this.getDeviceInstances().length !== 0;
        }
        else {
            return this.filterDevicesByPlatform().length !== 0;
        }
    }
    isOnlyiOSSimultorRunning() {
        let devices = this.getDeviceInstances();
        return this._platform && this.$mobileHelper.isiOSPlatform(this._platform) && _.find(devices, d => d.isEmulator) && !_.find(devices, d => !d.isEmulator);
    }
    getDeviceByDeviceOption() {
        return this._device;
    }
    mapAbstractToTcpPort(deviceIdentifier, appIdentifier, framework) {
        return this.$androidProcessService.mapAbstractToTcpPort(deviceIdentifier, appIdentifier, framework);
    }
    getDebuggableApps(deviceIdentifiers) {
        return _.map(deviceIdentifiers, (deviceIdentifier) => this.getDebuggableAppsCore(deviceIdentifier));
    }
    getDebuggableViews(deviceIdentifier, appIdentifier) {
        return (() => {
            let device = this.getDeviceByIdentifier(deviceIdentifier), debuggableViewsPerApp = device.applicationManager.getDebuggableAppViews([appIdentifier]).wait();
            return debuggableViewsPerApp && debuggableViewsPerApp[appIdentifier];
        }).future()();
    }
    clearDeviceDetectionInterval() {
        if (this.deviceDetectionInterval) {
            clearInterval(this.deviceDetectionInterval);
        }
        else {
            this.$logger.trace("Device detection interval is not started, so it cannot be stopped.");
        }
    }
    getDebuggableAppsCore(deviceIdentifier) {
        return (() => {
            let device = this.getDeviceByIdentifier(deviceIdentifier);
            return device.applicationManager.getDebuggableApps().wait();
        }).future()();
    }
    deployOnDevice(deviceIdentifier, packageFile, packageName, framework) {
        return (() => {
            this.stopDeviceDetectionInterval().wait();
            let device = this.getDeviceByIdentifier(deviceIdentifier);
            device.applicationManager.reinstallApplication(packageName, packageFile).wait();
            this.$logger.info(`Successfully deployed on device with identifier '${device.deviceInfo.identifier}'.`);
            device.applicationManager.tryStartApplication(packageName, framework).wait();
        }).future()();
    }
    hasDevice(identifier) {
        return _.some(this.getDeviceInstances(), (device) => { return device.deviceInfo.identifier === identifier; });
    }
    filterDevicesByPlatform() {
        return _.filter(this.getDeviceInstances(), (device) => {
            if (this.$options.emulator && !device.isEmulator) {
                return false;
            }
            if (this._platform) {
                return device.deviceInfo.platform === this._platform;
            }
            return true;
        });
    }
    validateIndex(index) {
        if (index < 0 || index > this.getDeviceInstances().length) {
            throw new Error(util.format(this.$messages.Devices.NotFoundDeviceByIndexErrorMessage, index, this.$staticConfig.CLIENT_NAME.toLowerCase()));
        }
    }
    resolveEmulatorServices(platform) {
        platform = platform || this._platform;
        if (this.$mobileHelper.isiOSPlatform(platform) && this.$hostInfo.isDarwin) {
            return this.$injector.resolve("iOSEmulatorServices");
        }
        else if (this.$mobileHelper.isAndroidPlatform(platform)) {
            return this.$injector.resolve("androidEmulatorServices");
        }
        return null;
    }
    startEmulator(platform) {
        return (() => {
            platform = platform || this._platform;
            let emulatorServices = this.resolveEmulatorServices(platform);
            if (!emulatorServices) {
                this.$errors.failWithoutHelp("Unable to detect platform for which to start emulator.");
            }
            emulatorServices.startEmulator().wait();
            if (this.$mobileHelper.isAndroidPlatform(platform)) {
                this.$androidDeviceDiscovery.startLookingForDevices().wait();
            }
            else if (this.$mobileHelper.isiOSPlatform(platform) && this.$hostInfo.isDarwin) {
                this.$iOSSimulatorDiscovery.startLookingForDevices().wait();
            }
        }).future()();
    }
    executeCore(action, canExecute) {
        if (this._device) {
            return this.executeOnDevice(action, canExecute);
        }
        return this.executeOnAllConnectedDevices(action, canExecute);
    }
    isApplicationInstalledOnDevice(deviceIdentifier, appIdentifier, framework) {
        return (() => {
            let isInstalled = false, isLiveSyncSupported = false, device = this.getDeviceByIdentifier(deviceIdentifier);
            try {
                isInstalled = device.applicationManager.isApplicationInstalled(appIdentifier).wait();
                device.applicationManager.tryStartApplication(appIdentifier, framework).wait();
                isLiveSyncSupported = isInstalled && !!device.applicationManager.isLiveSyncSupported(appIdentifier).wait();
            }
            catch (err) {
                this.$logger.trace("Error while checking is application installed. Error is: ", err);
            }
            return {
                appIdentifier,
                deviceIdentifier,
                isInstalled,
                isLiveSyncSupported
            };
        }).future()();
    }
    isCompanionAppInstalledOnDevice(deviceIdentifier, framework) {
        return (() => {
            let isInstalled = false, isLiveSyncSupported = false, device = this.getDeviceByIdentifier(deviceIdentifier), appIdentifier = this.$companionAppsService.getCompanionAppIdentifier(framework, device.deviceInfo.platform);
            try {
                isLiveSyncSupported = isInstalled = device.applicationManager.isApplicationInstalled(appIdentifier).wait();
            }
            catch (err) {
                this.$logger.trace("Error while checking is application installed. Error is: ", err);
            }
            return {
                appIdentifier,
                deviceIdentifier,
                isInstalled,
                isLiveSyncSupported
            };
        }).future()();
    }
    getDeviceDetectionIntervalFuture() {
        return this.deviceDetectionIntervalFuture || Future.fromResult();
    }
}
DevicesService.DEVICE_LOOKING_INTERVAL = 2200;
__decorate([
    decorators_1.exported("devicesService")
], DevicesService.prototype, "getDevices", null);
__decorate([
    decorators_1.exported("devicesService")
], DevicesService.prototype, "setLogLevel", null);
__decorate([
    decorators_1.exportedPromise("devicesService")
], DevicesService.prototype, "isAppInstalledOnDevices", null);
__decorate([
    decorators_1.exportedPromise("devicesService")
], DevicesService.prototype, "isCompanionAppInstalledOnDevices", null);
__decorate([
    decorators_1.exportedPromise("devicesService", function () {
        this.startDeviceDetectionInterval();
    })
], DevicesService.prototype, "deployOnDevices", null);
__decorate([
    decorators_1.exportedPromise("devicesService")
], DevicesService.prototype, "mapAbstractToTcpPort", null);
__decorate([
    decorators_1.exportedPromise("devicesService")
], DevicesService.prototype, "getDebuggableApps", null);
__decorate([
    decorators_1.exportedPromise("devicesService")
], DevicesService.prototype, "getDebuggableViews", null);
exports.DevicesService = DevicesService;
$injector.register("devicesService", DevicesService);
