"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const constants_1 = require("../../constants");
const helpers_1 = require("../../helpers");
const os_1 = require("os");
class AndroidEmulatorServices {
    constructor($androidGenymotionService, $androidVirtualDeviceService, $adb, $childProcess, $emulatorHelper, $logger, $utils) {
        this.$androidGenymotionService = $androidGenymotionService;
        this.$androidVirtualDeviceService = $androidVirtualDeviceService;
        this.$adb = $adb;
        this.$childProcess = $childProcess;
        this.$emulatorHelper = $emulatorHelper;
        this.$logger = $logger;
        this.$utils = $utils;
    }
    getEmulatorImages() {
        return __awaiter(this, void 0, void 0, function* () {
            const adbDevicesOutput = yield this.$adb.getDevicesSafe();
            const avdAvailableEmulatorsOutput = yield this.$androidVirtualDeviceService.getEmulatorImages(adbDevicesOutput);
            const genyAvailableDevicesOutput = yield this.$androidGenymotionService.getEmulatorImages(adbDevicesOutput);
            return {
                devices: avdAvailableEmulatorsOutput.devices.concat(genyAvailableDevicesOutput.devices),
                errors: avdAvailableEmulatorsOutput.errors.concat(genyAvailableDevicesOutput.errors)
            };
        });
    }
    getRunningEmulatorIds() {
        return __awaiter(this, void 0, void 0, function* () {
            const adbDevicesOutput = yield this.$adb.getDevicesSafe();
            const avds = yield this.$androidVirtualDeviceService.getRunningEmulatorIds(adbDevicesOutput);
            const genies = yield this.$androidGenymotionService.getRunningEmulatorIds(adbDevicesOutput);
            return avds.concat(genies);
        });
    }
    getRunningEmulatorName(emulatorId) {
        return __awaiter(this, void 0, void 0, function* () {
            let result = yield this.$androidVirtualDeviceService.getRunningEmulatorName(emulatorId);
            if (!result) {
                result = yield this.$androidGenymotionService.getRunningEmulatorName(emulatorId);
            }
            return result;
        });
    }
    getRunningEmulatorImageIdentifier(emulatorId) {
        return __awaiter(this, void 0, void 0, function* () {
            let result = yield this.$androidVirtualDeviceService.getRunningEmulatorImageIdentifier(emulatorId);
            if (!result) {
                result = yield this.$androidGenymotionService.getRunningEmulatorImageIdentifier(emulatorId);
            }
            return result;
        });
    }
    startEmulator(options) {
        return __awaiter(this, void 0, void 0, function* () {
            const output = yield this.startEmulatorCore(options);
            let bootToCompleteOutput = null;
            if (output && output.runningEmulator) {
                bootToCompleteOutput = yield this.waitForEmulatorBootToComplete(output.runningEmulator, output.endTimeEpoch, options.timeout);
            }
            return {
                errors: ((output && output.errors) || []).concat((bootToCompleteOutput && bootToCompleteOutput.errors) || [])
            };
        });
    }
    startEmulatorCore(options) {
        return __awaiter(this, void 0, void 0, function* () {
            const timeout = options.timeout || constants_1.AndroidVirtualDevice.TIMEOUT_SECONDS;
            const endTimeEpoch = helpers_1.getCurrentEpochTime() + this.$utils.getMilliSecondsTimeout(timeout);
            const availableEmulators = (yield this.getEmulatorImages()).devices;
            let emulator = this.$emulatorHelper.getEmulatorByStartEmulatorOptions(options, availableEmulators);
            if (!emulator && !options.emulatorIdOrName && !options.imageIdentifier && !options.emulator) {
                emulator = this.getBestFit(availableEmulators);
            }
            if (!emulator) {
                return {
                    runningEmulator: null,
                    errors: [`No emulator image available for device identifier '${options.emulatorIdOrName || options.imageIdentifier}'.`],
                    endTimeEpoch
                };
            }
            if (emulator.errorHelp) {
                return {
                    runningEmulator: null,
                    errors: [emulator.errorHelp],
                    endTimeEpoch
                };
            }
            this.spawnEmulator(emulator);
            const isInfiniteWait = this.$utils.getMilliSecondsTimeout(timeout) === 0;
            let hasTimeLeft = helpers_1.getCurrentEpochTime() < endTimeEpoch;
            while (hasTimeLeft || isInfiniteWait) {
                const emulators = (yield this.getEmulatorImages()).devices;
                emulator = _.find(emulators, e => e.imageIdentifier === emulator.imageIdentifier);
                if (emulator && this.$emulatorHelper.isEmulatorRunning(emulator)) {
                    return {
                        runningEmulator: emulator,
                        errors: [],
                        endTimeEpoch
                    };
                }
                yield helpers_1.sleep(10000);
                hasTimeLeft = helpers_1.getCurrentEpochTime() < endTimeEpoch;
            }
            if (!hasTimeLeft && !isInfiniteWait) {
                return {
                    runningEmulator: null,
                    errors: [constants_1.AndroidVirtualDevice.UNABLE_TO_START_EMULATOR_MESSAGE],
                    endTimeEpoch
                };
            }
        });
    }
    spawnEmulator(emulator) {
        let pathToEmulatorExecutable = null;
        let startEmulatorArgs = null;
        if (emulator.vendor === constants_1.AndroidVirtualDevice.AVD_VENDOR_NAME) {
            pathToEmulatorExecutable = this.$androidVirtualDeviceService.pathToEmulatorExecutable;
            startEmulatorArgs = this.$androidVirtualDeviceService.startEmulatorArgs(emulator.imageIdentifier);
        }
        else if (emulator.vendor === constants_1.AndroidVirtualDevice.GENYMOTION_VENDOR_NAME) {
            pathToEmulatorExecutable = this.$androidGenymotionService.pathToEmulatorExecutable;
            startEmulatorArgs = this.$androidGenymotionService.startEmulatorArgs(emulator.imageIdentifier);
        }
        this.$logger.info(`Starting Android emulator with image ${emulator.imageIdentifier}`);
        const childProcess = this.$childProcess.spawn(pathToEmulatorExecutable, startEmulatorArgs, { stdio: "ignore", detached: true });
        childProcess.unref();
        childProcess.on("error", (err) => {
            this.$logger.trace(`Error when starting emulator. More info: ${err}`);
        });
    }
    getBestFit(emulators) {
        const best = _(emulators).maxBy(emulator => emulator.version);
        return (best && best.version >= constants_1.AndroidVirtualDevice.MIN_ANDROID_VERSION) ? best : null;
    }
    waitForEmulatorBootToComplete(emulator, endTimeEpoch, timeout) {
        return __awaiter(this, void 0, void 0, function* () {
            this.$logger.printInfoMessageOnSameLine("Waiting for emulator device initialization...");
            const isInfiniteWait = this.$utils.getMilliSecondsTimeout(timeout || constants_1.AndroidVirtualDevice.TIMEOUT_SECONDS) === 0;
            while (helpers_1.getCurrentEpochTime() < endTimeEpoch || isInfiniteWait) {
                const isEmulatorBootCompleted = yield this.isEmulatorBootCompleted(emulator.identifier);
                if (isEmulatorBootCompleted) {
                    this.$logger.printInfoMessageOnSameLine(os_1.EOL);
                    return {
                        runningEmulator: emulator,
                        errors: []
                    };
                }
                this.$logger.printInfoMessageOnSameLine(".");
                yield helpers_1.sleep(10000);
            }
            return {
                runningEmulator: null,
                errors: [constants_1.AndroidVirtualDevice.UNABLE_TO_START_EMULATOR_MESSAGE]
            };
        });
    }
    isEmulatorBootCompleted(emulatorId) {
        return __awaiter(this, void 0, void 0, function* () {
            const output = yield this.$adb.getPropertyValue(emulatorId, "dev.bootcomplete");
            const matches = output.match("1");
            return matches && matches.length > 0;
        });
    }
}
exports.AndroidEmulatorServices = AndroidEmulatorServices;
$injector.register("androidEmulatorServices", AndroidEmulatorServices);
