"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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DevicesService = void 0;
const util = require("util");
const helpers = require("../../helpers");
const assert = require("assert");
const _ = require("lodash");
const constants = require("../../constants");
const decorators_1 = require("../../decorators");
const helpers_1 = require("../../helpers");
const events_1 = require("events");
const os_1 = require("os");
const constants_1 = require("../../constants");
const helpers_2 = require("../../helpers");
const constants_2 = require("../../../constants");
const decorators_2 = require("../../decorators");
const yok_1 = require("../../yok");
class DevicesService extends events_1.EventEmitter {
    constructor($logger, $errors, $iOSSimulatorDiscovery, $iOSDeviceDiscovery, $androidDeviceDiscovery, $staticConfig, $messages, $mobileHelper, $deviceLogProvider, $hostInfo, $injector, $options, $androidProcessService, $iOSEmulatorServices, $androidEmulatorServices, $androidEmulatorDiscovery, $emulatorHelper, $prompter) {
        super();
        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.$injector = $injector;
        this.$options = $options;
        this.$androidProcessService = $androidProcessService;
        this.$iOSEmulatorServices = $iOSEmulatorServices;
        this.$androidEmulatorServices = $androidEmulatorServices;
        this.$androidEmulatorDiscovery = $androidEmulatorDiscovery;
        this.$emulatorHelper = $emulatorHelper;
        this.$prompter = $prompter;
        this._devices = {};
        this._availableEmulators = {};
        this._isInitialized = false;
        this._otherDeviceDiscoveries = [];
        this._allDeviceDiscoveries = [];
        this.attachToKnownDeviceDiscoveryEvents();
        this.attachToKnownEmulatorDiscoveryEvents();
        this._allDeviceDiscoveries = [
            this.$iOSDeviceDiscovery,
            this.$androidDeviceDiscovery,
            this.$iOSSimulatorDiscovery,
        ];
    }
    pickSingleDevice(options) {
        return __awaiter(this, void 0, void 0, function* () {
            if (options.onlyDevices && options.onlyEmulators) {
                this.$errors.fail(constants_2.DebugCommandErrors.UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR);
            }
            if (options.deviceId) {
                const device = yield this.getDevice(options.deviceId);
                return device;
            }
            const availableDevicesAndEmulators = this.getDeviceInstances().filter((d) => d.deviceInfo.status === constants_1.CONNECTED_STATUS &&
                (!this.platform ||
                    d.deviceInfo.platform.toLowerCase() === this.platform.toLowerCase()));
            const selectedDevices = availableDevicesAndEmulators.filter((d) => options.onlyEmulators
                ? d.isEmulator
                : options.onlyDevices
                    ? !d.isEmulator
                    : true);
            if (selectedDevices.length > 1) {
                if ((0, helpers_2.isInteractive)()) {
                    const choices = selectedDevices.map((e) => `${e.deviceInfo.identifier} - ${e.deviceInfo.displayName}`);
                    const selectedDeviceString = yield this.$prompter.promptForChoice("Select device for debugging", choices);
                    const selectedDevice = _.find(selectedDevices, (d) => `${d.deviceInfo.identifier} - ${d.deviceInfo.displayName}` ===
                        selectedDeviceString);
                    return selectedDevice;
                }
                else {
                    const sortedInstances = _.sortBy(selectedDevices, (e) => e.deviceInfo.version);
                    const emulators = sortedInstances.filter((e) => e.isEmulator);
                    const devices = sortedInstances.filter((d) => !d.isEmulator);
                    let selectedInstance;
                    if (options.onlyEmulators || options.onlyDevices) {
                        selectedInstance = _.last(sortedInstances);
                    }
                    else {
                        if (emulators.length) {
                            selectedInstance = _.last(emulators);
                        }
                        else {
                            selectedInstance = _.last(devices);
                        }
                    }
                    this.$logger.warn(`Multiple devices/emulators found. Starting debugger on ${selectedInstance.deviceInfo.identifier}. ` +
                        "If you want to debug on specific device/emulator, you can specify it with --device option.");
                    return selectedInstance;
                }
            }
            else if (selectedDevices.length === 1) {
                return _.head(selectedDevices);
            }
            this.$errors.fail(constants_2.DebugCommandErrors.NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS);
        });
    }
    getEmulatorImages(options) {
        return __awaiter(this, void 0, void 0, function* () {
            const result = Object.create(null);
            if (this.$hostInfo.isDarwin &&
                (!options ||
                    !options.platform ||
                    this.$mobileHelper.isiOSPlatform(options.platform))) {
                result.ios = yield this.$iOSEmulatorServices.getEmulatorImages();
            }
            if (!options ||
                !options.platform ||
                this.$mobileHelper.isAndroidPlatform(options.platform)) {
                result.android = yield this.$androidEmulatorServices.getEmulatorImages();
            }
            return result;
        });
    }
    startEmulator(options) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!options || (!options.imageIdentifier && !options.emulatorIdOrName)) {
                return ["Missing mandatory image identifier or name option."];
            }
            const availableEmulatorsOutput = yield this.getEmulatorImages({
                platform: options.platform,
            });
            const emulators = this.$emulatorHelper.getEmulatorsFromAvailableEmulatorsOutput(availableEmulatorsOutput);
            const errors = this.$emulatorHelper.getErrorsFromAvailableEmulatorsOutput(availableEmulatorsOutput);
            let emulator = null;
            if (options.imageIdentifier) {
                emulator = this.$emulatorHelper.getEmulatorByImageIdentifier(options.imageIdentifier, emulators);
            }
            else if (options.emulatorIdOrName) {
                emulator = this.$emulatorHelper.getEmulatorByIdOrName(options.emulatorIdOrName, emulators);
            }
            if (!emulator) {
                const additionalErrors = errors && errors.length ? errors : [];
                return [
                    `Unable to find emulator with provided options: ${options}`,
                    ...additionalErrors,
                ];
            }
            if (emulator.status === constants.RUNNING_EMULATOR_STATUS) {
                return null;
            }
            options.emulator = emulator;
            const emulatorService = this.resolveEmulatorServices(emulator.platform);
            const result = yield emulatorService.startEmulator(options);
            return result.errors && result.errors.length ? result.errors : null;
        });
    }
    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, appId, projectName, projectDir) {
        this.$logger.trace(`Called isInstalledOnDevices for identifiers ${deviceIdentifiers}. AppIdentifier is ${appId}.`);
        return _.map(deviceIdentifiers, (deviceIdentifier) => this.isApplicationInstalledOnDevice(deviceIdentifier, {
            appId,
            projectName,
            projectDir,
        }));
    }
    getDeviceInstances() {
        return _.values(this._devices);
    }
    getInstalledApplications(deviceIdentifier) {
        return __awaiter(this, void 0, void 0, function* () {
            const device = yield this.getDevice(deviceIdentifier);
            return device.applicationManager.getInstalledApplications();
        });
    }
    addDeviceDiscovery(deviceDiscovery) {
        this._otherDeviceDiscoveries.push(deviceDiscovery);
        this._allDeviceDiscoveries.push(deviceDiscovery);
        this.attachToDeviceDiscoveryEvents(deviceDiscovery);
    }
    attachToKnownDeviceDiscoveryEvents() {
        [
            this.$iOSSimulatorDiscovery,
            this.$iOSDeviceDiscovery,
            this.$androidDeviceDiscovery,
        ].forEach(this.attachToDeviceDiscoveryEvents.bind(this));
    }
    attachToKnownEmulatorDiscoveryEvents() {
        this.$androidEmulatorDiscovery.on(constants.EmulatorDiscoveryNames.EMULATOR_IMAGE_FOUND, (emulator) => this.onEmulatorImageFound(emulator));
        this.$androidEmulatorDiscovery.on(constants.EmulatorDiscoveryNames.EMULATOR_IMAGE_LOST, (emulator) => this.onEmulatorImageLost(emulator));
        this.$iOSSimulatorDiscovery.on(constants.EmulatorDiscoveryNames.EMULATOR_IMAGE_FOUND, (emulator) => this.onEmulatorImageFound(emulator));
        this.$iOSSimulatorDiscovery.on(constants.EmulatorDiscoveryNames.EMULATOR_IMAGE_LOST, (emulator) => this.onEmulatorImageLost(emulator));
    }
    attachToDeviceDiscoveryEvents(deviceDiscovery) {
        deviceDiscovery.on(constants.DeviceDiscoveryEventNames.DEVICE_FOUND, (device) => this.onDeviceFound(device));
        deviceDiscovery.on(constants.DeviceDiscoveryEventNames.DEVICE_UPDATED, (device) => this.onDeviceUpdated(device));
        deviceDiscovery.on(constants.DeviceDiscoveryEventNames.DEVICE_LOST, (device) => this.onDeviceLost(device));
    }
    onDeviceFound(device) {
        this.$logger.trace(`Found device with identifier '${device.deviceInfo.identifier}'`);
        this._devices[device.deviceInfo.identifier] = device;
        this.emit(constants.DeviceDiscoveryEventNames.DEVICE_FOUND, device);
    }
    onDeviceUpdated(device) {
        this.$logger.trace(`Updated device with identifier '${device.deviceInfo.identifier}'`);
        this._devices[device.deviceInfo.identifier] = device;
        this.emit(constants.DeviceDiscoveryEventNames.DEVICE_UPDATED, device);
    }
    onDeviceLost(device) {
        this.$logger.trace(`Lost device with identifier '${device.deviceInfo.identifier}'`);
        if (device.detach) {
            device.detach();
        }
        delete this._devices[device.deviceInfo.identifier];
        this.emit(constants.DeviceDiscoveryEventNames.DEVICE_LOST, device);
    }
    onEmulatorImageFound(emulator) {
        this.$logger.trace(`Found emulator with image identifier: ${emulator.imageIdentifier}`);
        this._availableEmulators[emulator.imageIdentifier] = emulator;
        this.emit(constants.EmulatorDiscoveryNames.EMULATOR_IMAGE_FOUND, emulator);
    }
    onEmulatorImageLost(emulator) {
        this.$logger.trace(`Lost emulator with image identifier ${emulator.imageIdentifier}`);
        delete this._availableEmulators[emulator.imageIdentifier];
        this.emit(constants.EmulatorDiscoveryNames.EMULATOR_IMAGE_LOST, emulator);
    }
    detectCurrentlyAttachedDevices(deviceInitOpts) {
        return __awaiter(this, void 0, void 0, function* () {
            const options = this.getDeviceLookingOptions(deviceInitOpts);
            for (const deviceDiscovery of this._allDeviceDiscoveries) {
                try {
                    yield deviceDiscovery.startLookingForDevices(options);
                }
                catch (err) {
                    this.$logger.trace("Error while checking for devices.", err);
                }
            }
        });
    }
    detectCurrentlyAvailableEmulators() {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                yield this.$androidEmulatorDiscovery.startLookingForDevices();
            }
            catch (err) {
                this.$logger.trace(`Error while checking for Android emulators. ${err}`);
            }
            try {
                yield this.$iOSSimulatorDiscovery.checkForAvailableSimulators();
            }
            catch (err) {
                this.$logger.trace(`Error while checking for iOS simulators. ${err}`);
            }
        });
    }
    startDeviceDetectionInterval(deviceInitOpts = {}) {
        if (!this.deviceDetectionInterval) {
            let isDeviceDetectionIntervalInProgress = false;
            this.deviceDetectionInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
                if (isDeviceDetectionIntervalInProgress) {
                    return;
                }
                isDeviceDetectionIntervalInProgress = true;
                yield this.detectCurrentlyAttachedDevices(deviceInitOpts);
                try {
                    const trustedDevices = _.filter(this._devices, (device) => device.deviceInfo.status === constants.CONNECTED_STATUS);
                    yield (0, helpers_1.settlePromises)(_.map(trustedDevices, (device) => device.applicationManager.checkForApplicationUpdates()));
                }
                catch (err) {
                    this.$logger.trace("Error checking for application updates on devices.", err);
                }
                isDeviceDetectionIntervalInProgress = false;
            }), deviceInitOpts.detectionInterval || DevicesService.DEVICE_LOOKING_INTERVAL);
            this.deviceDetectionInterval.unref();
        }
    }
    startEmulatorDetectionInterval(opts = {}) {
        let isEmulatorDetectionIntervalRunning = false;
        this.emulatorDetectionInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
            if (isEmulatorDetectionIntervalRunning) {
                return;
            }
            isEmulatorDetectionIntervalRunning = true;
            yield this.detectCurrentlyAvailableEmulators();
            isEmulatorDetectionIntervalRunning = false;
        }), opts.detectionInterval || DevicesService.EMULATOR_IMAGES_DETECTION_INTERVAL);
        this.emulatorDetectionInterval.unref();
    }
    stopDeviceDetectionInterval() {
        if (this.deviceDetectionInterval) {
            clearInterval(this.deviceDetectionInterval);
        }
    }
    stopEmulatorDetectionInterval() {
        if (this.emulatorDetectionInterval) {
            clearInterval(this.emulatorDetectionInterval);
        }
    }
    getDeviceByIdentifier(identifier) {
        const searchedDevice = _.find(this.getDeviceInstances(), (device) => {
            if (this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform) &&
                device.isEmulator) {
                return (device.deviceInfo.identifier === identifier ||
                    device.deviceInfo.displayName === identifier);
            }
            return device.deviceInfo.identifier === identifier;
        });
        if (!searchedDevice) {
            this.$errors.fail(this.$messages.Devices
                .NotFoundDeviceByIdentifierErrorMessageWithIdentifier, identifier, this.$staticConfig.CLIENT_NAME.toLowerCase());
        }
        return searchedDevice;
    }
    startLookingForDevices(deviceInitOpts) {
        return __awaiter(this, void 0, void 0, function* () {
            this.$logger.trace("startLookingForDevices; platform is %s", this._platform);
            if (this._platform) {
                return this.detectCurrentlyAttachedDevices(deviceInitOpts);
            }
            yield this.detectCurrentlyAttachedDevices(deviceInitOpts);
            yield this.detectCurrentlyAvailableEmulators();
            yield this.startDeviceDetectionInterval(deviceInitOpts);
        });
    }
    getDeviceByIndex(index) {
        this.validateIndex(index - 1);
        return this.getDeviceInstances()[index - 1];
    }
    getDevice(deviceOption) {
        return __awaiter(this, void 0, void 0, function* () {
            let device = null;
            if (!device) {
                device = _.find(this.getDeviceInstances(), (d) => (d.deviceInfo.identifier &&
                    d.deviceInfo.identifier === deviceOption) ||
                    (d.deviceInfo.displayName &&
                        d.deviceInfo.displayName === deviceOption) ||
                    (d.deviceInfo.imageIdentifier &&
                        d.deviceInfo.imageIdentifier === deviceOption));
            }
            if (!device && helpers.isNumberWithoutExponent(deviceOption)) {
                device = this.getDeviceByIndex(parseInt(deviceOption, 10));
            }
            if (!device) {
                this.$errors.fail(this.$messages.Devices
                    .NotFoundDeviceByIdentifierErrorMessageWithIdentifier, deviceOption, this.$staticConfig.CLIENT_NAME.toLowerCase());
            }
            return device;
        });
    }
    executeOnDevice(action, canExecute) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!canExecute || canExecute(this._device)) {
                return {
                    deviceIdentifier: this._device.deviceInfo.identifier,
                    result: yield action(this._device),
                };
            }
        });
    }
    executeOnAllConnectedDevices(action, canExecute) {
        return __awaiter(this, void 0, void 0, function* () {
            const devices = this.filterDevicesByPlatform();
            const sortedDevices = _.sortBy(devices, (device) => device.deviceInfo.platform);
            const result = [];
            const errors = [];
            for (const device of sortedDevices) {
                try {
                    if (!canExecute || canExecute(device)) {
                        result.push({
                            deviceIdentifier: device.deviceInfo.identifier,
                            result: yield action(device),
                        });
                    }
                }
                catch (err) {
                    err.deviceIdentifier = device.deviceInfo.identifier;
                    this.$logger.trace(`Error while executing action on device ${device.deviceInfo.identifier}. The error is`, err);
                    errors.push(err);
                }
            }
            if (errors.length) {
                let preErrorMsg = "";
                if (errors.length > 1) {
                    preErrorMsg = "Multiple errors were thrown:" + os_1.EOL;
                }
                const errorMessage = `${preErrorMsg}${errors
                    .map((e) => e.message || e)
                    .join(os_1.EOL)}`;
                this.$errors.fail(errorMessage);
            }
            return result;
        });
    }
    deployOnDevices(deviceIdentifiers, packagePath, appId, projectName, projectDir) {
        this.$logger.trace(`Called deployOnDevices for identifiers ${deviceIdentifiers} for packageFile: ${packagePath}. Application identifier is ${appId}. Project Name is: ${projectName}`);
        return _.map(deviceIdentifiers, (deviceIdentifier) => __awaiter(this, void 0, void 0, function* () {
            const device = this.getDeviceByIdentifier(deviceIdentifier);
            let identifier;
            if (typeof appId === "string") {
                identifier = appId;
            }
            else {
                identifier = appId[device.deviceInfo.platform.toLowerCase()];
            }
            return this.deployOnDevice(device, {
                packagePath,
                appId: identifier,
                projectName,
                projectDir,
            });
        }));
    }
    execute(action, canExecute, options) {
        return __awaiter(this, void 0, void 0, 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()) {
                    const originalCanExecute = canExecute;
                    canExecute = (dev) => this.isiOSSimulator(dev) &&
                        (!originalCanExecute || !!originalCanExecute(dev));
                }
                return this.executeCore(action, canExecute);
            }
            else {
                const 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.fail(message);
                    }
                    else {
                        return this.executeCore(action, canExecute);
                    }
                }
            }
        });
    }
    startEmulatorIfNecessary(deviceInitOpts) {
        return __awaiter(this, void 0, void 0, function* () {
            if (deviceInitOpts && deviceInitOpts.deviceId && deviceInitOpts.emulator) {
                this.$errors.fail(`--device and --emulator are incompatible options.
			If you are trying to run on specific emulator, use "${this.$staticConfig.CLIENT_NAME} run --device <DeviceID>`);
            }
            if (deviceInitOpts &&
                deviceInitOpts.platform &&
                !deviceInitOpts.skipEmulatorStart) {
                this._platform = deviceInitOpts.platform;
                try {
                    yield this.startLookingForDevices(deviceInitOpts);
                }
                catch (err) {
                    this.$logger.trace("Error while checking for devices.", err);
                }
                const deviceInstances = this.getDeviceInstances();
                if (!deviceInitOpts.deviceId && _.isEmpty(deviceInstances)) {
                    if (!this.$hostInfo.isDarwin &&
                        this.$mobileHelper.isiOSPlatform(deviceInitOpts.platform)) {
                        this.$errors.fail(constants.ERROR_NO_DEVICES_CANT_USE_IOS_SIMULATOR);
                    }
                }
                try {
                    yield this._startEmulatorIfNecessary(deviceInitOpts);
                }
                catch (err) {
                    const errorMessage = this.getEmulatorError(err, deviceInitOpts.platform);
                    this.$errors.fail(errorMessage);
                }
            }
        });
    }
    _startEmulatorIfNecessary(data) {
        return __awaiter(this, void 0, void 0, function* () {
            const deviceInstances = this.getDeviceInstances();
            if (!data.deviceId && _.isEmpty(deviceInstances)) {
                return this.startEmulatorCore(data);
            }
            if (data.deviceId) {
                if (!helpers.isNumberWithoutExponent(data.deviceId)) {
                    const activeDeviceInstance = _.find(deviceInstances, (device) => device.deviceInfo.identifier === data.deviceId);
                    if (!activeDeviceInstance) {
                        return this.startEmulatorCore(data);
                    }
                }
            }
            if (data.emulator && deviceInstances.length) {
                const runningDeviceInstance = _.some(deviceInstances, (value) => value.isEmulator);
                if (!runningDeviceInstance) {
                    return this.startEmulatorCore(data);
                }
            }
        });
    }
    initialize(data) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this._deviceInitializePromise) {
                this._deviceInitializePromise = this.initializeCore(data);
            }
            try {
                yield this._deviceInitializePromise;
            }
            catch (err) {
                this.$logger.trace(`Error while initializing devicesService: ${err}`);
                this._deviceInitializePromise = null;
                throw err;
            }
        });
    }
    getPlatformsFromDeviceDescriptors(deviceDescriptors) {
        const platforms = _(deviceDescriptors)
            .map((device) => this.getDeviceByIdentifier(device.identifier))
            .map((device) => device.deviceInfo.platform.toLowerCase())
            .uniq()
            .value();
        return platforms;
    }
    initializeCore(deviceInitOpts) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this._isInitialized) {
                return;
            }
            if (!this.$options.json) {
                this.$logger.info("Searching for devices...");
            }
            deviceInitOpts = deviceInitOpts || {};
            this._data = deviceInitOpts;
            if (!deviceInitOpts.skipEmulatorStart) {
                yield this.startEmulatorIfNecessary(deviceInitOpts);
            }
            const platform = deviceInitOpts.platform;
            const deviceOption = deviceInitOpts.deviceId;
            const deviceLookingOptions = {
                emulator: deviceInitOpts.emulator,
                platform: deviceInitOpts.platform,
                shouldReturnImmediateResult: deviceInitOpts.shouldReturnImmediateResult,
                detectionInterval: deviceInitOpts.detectionInterval,
                fullDiscovery: deviceInitOpts.fullDiscovery,
            };
            if (platform && deviceOption) {
                this._platform = this.$mobileHelper.validatePlatformName(deviceInitOpts.platform);
                yield this.startLookingForDevices(deviceLookingOptions);
                this._device = yield this.getDevice(deviceOption);
                if (this._device.deviceInfo.platform !== this._platform) {
                    this.$errors.fail(constants.ERROR_CANNOT_RESOLVE_DEVICE);
                }
                this.$logger.warn("Your application will be deployed only on the device specified by the provided index or identifier.");
            }
            else if (!platform && deviceOption) {
                yield this.startLookingForDevices(deviceLookingOptions);
                this._device = yield this.getDevice(deviceOption);
                this._platform = this._device.deviceInfo.platform;
            }
            else if (platform && !deviceOption) {
                this._platform = this.$mobileHelper.validatePlatformName(platform);
                yield this.startLookingForDevices(deviceLookingOptions);
            }
            else {
                if (deviceInitOpts.skipInferPlatform) {
                    if (deviceInitOpts.skipDeviceDetectionInterval) {
                        yield this.detectCurrentlyAttachedDevices(deviceLookingOptions);
                    }
                    else {
                        deviceInitOpts.shouldReturnImmediateResult = true;
                        yield this.startLookingForDevices(deviceLookingOptions);
                    }
                }
                else {
                    yield this.startLookingForDevices(deviceLookingOptions);
                    const devices = this.getDeviceInstances();
                    const platforms = _(devices)
                        .map((device) => device.deviceInfo.platform)
                        .filter((pl) => {
                        try {
                            return !!this.$mobileHelper.validatePlatformName(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(constants.ERROR_NO_DEVICES);
                    }
                    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.fail(constants.ERROR_CANT_USE_SIMULATOR);
            }
            this._isInitialized = true;
        });
    }
    get hasDevices() {
        if (!this._platform) {
            return this.getDeviceInstances().length !== 0;
        }
        else {
            return this.filterDevicesByPlatform().length !== 0;
        }
    }
    isOnlyiOSSimultorRunning() {
        const 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 __awaiter(this, void 0, void 0, function* () {
            return this.$androidProcessService.mapAbstractToTcpPort(deviceIdentifier, appIdentifier, framework);
        });
    }
    getDebuggableApps(deviceIdentifiers) {
        return _.map(deviceIdentifiers, (deviceIdentifier) => this.getDebuggableAppsCore(deviceIdentifier));
    }
    getDebuggableViews(deviceIdentifier, appIdentifier) {
        return __awaiter(this, void 0, void 0, function* () {
            const device = this.getDeviceByIdentifier(deviceIdentifier), debuggableViewsPerApp = yield device.applicationManager.getDebuggableAppViews([appIdentifier]);
            return debuggableViewsPerApp && debuggableViewsPerApp[appIdentifier];
        });
    }
    getDebuggableAppsCore(deviceIdentifier) {
        const device = this.getDeviceByIdentifier(deviceIdentifier);
        return device.applicationManager.getDebuggableApps();
    }
    deployOnDevice(device, appData) {
        return __awaiter(this, void 0, void 0, function* () {
            yield device.applicationManager.reinstallApplication(appData.appId, appData.packagePath);
            this.$logger.info(`Successfully deployed on device with identifier '${device.deviceInfo.identifier}'.`);
            yield device.applicationManager.tryStartApplication(appData);
        });
    }
    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)) {
            return this.$injector.resolve("iOSEmulatorServices");
        }
        else if (this.$mobileHelper.isAndroidPlatform(platform)) {
            return this.$injector.resolve("androidEmulatorServices");
        }
        return null;
    }
    startEmulatorCore(deviceInitOpts = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            const { deviceId } = deviceInitOpts;
            const platform = deviceInitOpts.platform || this._platform;
            const emulatorServices = this.resolveEmulatorServices(platform);
            if (!emulatorServices) {
                this.$errors.fail("Unable to detect platform for which to start emulator.");
            }
            const result = yield emulatorServices.startEmulator({
                emulatorIdOrName: deviceId,
                imageIdentifier: deviceId,
                platform: platform,
                sdk: this._data && this._data.sdk,
            });
            if (result && result.errors && result.errors.length) {
                this.$errors.fail(result.errors.join("\n"));
            }
            const deviceLookingOptions = this.getDeviceLookingOptions(deviceInitOpts);
            if (this.$mobileHelper.isAndroidPlatform(platform)) {
                yield this.$androidDeviceDiscovery.startLookingForDevices(deviceLookingOptions);
            }
            else if (this.$mobileHelper.isiOSPlatform(platform) &&
                this.$hostInfo.isDarwin) {
                yield this.$iOSSimulatorDiscovery.startLookingForDevices(deviceLookingOptions);
            }
        });
    }
    executeCore(action, canExecute) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this._device) {
                return [yield this.executeOnDevice(action, canExecute)];
            }
            return this.executeOnAllConnectedDevices(action, canExecute);
        });
    }
    isApplicationInstalledOnDevice(deviceIdentifier, appData) {
        return __awaiter(this, void 0, void 0, function* () {
            let isInstalled = false;
            const device = this.getDeviceByIdentifier(deviceIdentifier);
            try {
                isInstalled = yield device.applicationManager.isApplicationInstalled(appData.appId);
                yield device.applicationManager.tryStartApplication(appData);
            }
            catch (err) {
                this.$logger.trace("Error while checking is application installed. Error is: ", err);
            }
            return {
                appIdentifier: appData.appId,
                deviceIdentifier,
                isInstalled,
            };
        });
    }
    getDeviceLookingOptions(deviceInitOpts = {}) {
        const { shouldReturnImmediateResult, emulator } = deviceInitOpts;
        const platform = deviceInitOpts.platform || this._platform;
        return {
            platform,
            shouldReturnImmediateResult: !!shouldReturnImmediateResult,
            emulator: !!emulator,
            fullDiscovery: deviceInitOpts.fullDiscovery,
        };
    }
    getEmulatorError(error, platform) {
        let emulatorName = constants.DeviceTypes.Emulator;
        if (this.$mobileHelper.isiOSPlatform(platform)) {
            emulatorName = constants.DeviceTypes.Simulator;
        }
        return (`Cannot find connected devices.${os_1.EOL}` +
            `${emulatorName} start failed with: ${error.message}${os_1.EOL}` +
            `To list currently connected devices and verify that the specified identifier exists, run '${this.$staticConfig.CLIENT_NAME.toLowerCase()} device'.${os_1.EOL}` +
            `To list available ${emulatorName.toLowerCase()} images, run '${this.$staticConfig.CLIENT_NAME.toLowerCase()} device <Platform> --available-devices'.`);
    }
}
DevicesService.DEVICE_LOOKING_INTERVAL = 200;
DevicesService.EMULATOR_IMAGES_DETECTION_INTERVAL = 60 * 1000;
__decorate([
    (0, decorators_2.performanceLog)()
], DevicesService.prototype, "pickSingleDevice", null);
__decorate([
    (0, decorators_1.exported)("devicesService")
], DevicesService.prototype, "getEmulatorImages", null);
__decorate([
    (0, decorators_1.exported)("devicesService")
], DevicesService.prototype, "startEmulator", null);
__decorate([
    (0, decorators_1.exported)("devicesService")
], DevicesService.prototype, "getDevices", null);
__decorate([
    (0, decorators_1.exported)("devicesService")
], DevicesService.prototype, "setLogLevel", null);
__decorate([
    (0, decorators_1.exported)("devicesService")
], DevicesService.prototype, "isAppInstalledOnDevices", null);
__decorate([
    (0, decorators_1.exported)("devicesService")
], DevicesService.prototype, "getInstalledApplications", null);
__decorate([
    (0, decorators_1.exported)("devicesService")
], DevicesService.prototype, "addDeviceDiscovery", null);
__decorate([
    (0, decorators_1.exported)("devicesService")
], DevicesService.prototype, "startDeviceDetectionInterval", null);
__decorate([
    (0, decorators_1.exported)("devicesService")
], DevicesService.prototype, "startEmulatorDetectionInterval", null);
__decorate([
    (0, decorators_1.exported)("devicesService")
], DevicesService.prototype, "stopDeviceDetectionInterval", null);
__decorate([
    (0, decorators_1.exported)("devicesService")
], DevicesService.prototype, "stopEmulatorDetectionInterval", null);
__decorate([
    (0, decorators_1.exported)("devicesService")
], DevicesService.prototype, "deployOnDevices", null);
__decorate([
    (0, decorators_1.exported)("devicesService")
], DevicesService.prototype, "initialize", null);
__decorate([
    (0, decorators_1.exported)("devicesService")
], DevicesService.prototype, "mapAbstractToTcpPort", null);
__decorate([
    (0, decorators_1.exported)("devicesService")
], DevicesService.prototype, "getDebuggableApps", null);
__decorate([
    (0, decorators_1.exported)("devicesService")
], DevicesService.prototype, "getDebuggableViews", null);
exports.DevicesService = DevicesService;
yok_1.injector.register("devicesService", DevicesService);
