"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) {
    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());
    });
};
const net = require("net");
const ref = require("ref");
const os = require("os");
const iOSProxyServices = require("./ios-proxy-services");
const helpers_1 = require("../../../helpers");
const application_manager_base_1 = require("../../application-manager-base");
const ios_core_1 = require("./ios-core");
class IOSApplicationManager extends application_manager_base_1.ApplicationManagerBase {
    constructor($logger, $hooksService, device, devicePointer, $coreFoundation, $errors, $injector, $mobileDevice, $hostInfo, $staticConfig, $processService) {
        super($logger, $hooksService);
        this.$logger = $logger;
        this.$hooksService = $hooksService;
        this.device = device;
        this.devicePointer = devicePointer;
        this.$coreFoundation = $coreFoundation;
        this.$errors = $errors;
        this.$injector = $injector;
        this.$mobileDevice = $mobileDevice;
        this.$hostInfo = $hostInfo;
        this.$staticConfig = $staticConfig;
        this.$processService = $processService;
        this.uninstallApplicationCallbackPtr = null;
        this._gdbServer = null;
        this.uninstallApplicationCallbackPtr = ios_core_1.CoreTypes.am_device_mount_image_callback.toPointer(IOSApplicationManager.uninstallCallback);
    }
    static uninstallCallback(dictionary, user) { }
    getInstallationProxy() {
        return this.$injector.resolve(iOSProxyServices.InstallationProxyClient, { device: this.device });
    }
    getInstalledApplications() {
        return __awaiter(this, void 0, void 0, function* () {
            const applicationsLiveSyncStatus = yield this.getApplicationsLiveSyncSupportedStatus();
            return _(applicationsLiveSyncStatus)
                .map(appLiveSyncStatus => appLiveSyncStatus.applicationIdentifier)
                .sortBy((identifier) => identifier.toLowerCase())
                .value();
        });
    }
    installApplication(packageFilePath) {
        return __awaiter(this, void 0, void 0, function* () {
            let installationProxy = this.getInstallationProxy();
            try {
                yield installationProxy.deployApplication(packageFilePath);
            }
            finally {
                installationProxy.closeSocket();
            }
        });
    }
    getApplicationInfo(applicationIdentifier) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.applicationsLiveSyncInfos || !this.applicationsLiveSyncInfos.length) {
                yield this.getApplicationsLiveSyncSupportedStatus();
            }
            return _.find(this.applicationsLiveSyncInfos, app => app.applicationIdentifier === applicationIdentifier);
        });
    }
    getApplicationsLiveSyncSupportedStatus() {
        return __awaiter(this, void 0, void 0, function* () {
            let installationProxy = this.getInstallationProxy();
            try {
                let result = yield installationProxy.sendMessage({
                    "Command": "Browse",
                    "ClientOptions": {
                        "ApplicationType": "User",
                        "ReturnAttributes": [
                            "CFBundleIdentifier",
                            "IceniumLiveSyncEnabled",
                            "configuration"
                        ]
                    }
                });
                this.$logger.trace("Result when getting applications for which LiveSync is enabled: ", JSON.stringify(result, null, 2));
                this.applicationsLiveSyncInfos = [];
                _.each(result, (singleResult) => {
                    let currentList = _.map(singleResult.CurrentList, (app) => ({
                        applicationIdentifier: app.CFBundleIdentifier,
                        isLiveSyncSupported: app.IceniumLiveSyncEnabled,
                        configuration: app.configuration,
                        deviceIdentifier: this.device.deviceInfo.identifier
                    }));
                    this.applicationsLiveSyncInfos = this.applicationsLiveSyncInfos.concat(currentList);
                });
                return this.applicationsLiveSyncInfos;
            }
            finally {
                installationProxy.closeSocket();
            }
        });
    }
    isLiveSyncSupported(appIdentifier) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.applicationsLiveSyncInfos || !this.applicationsLiveSyncInfos.length) {
                yield this.getApplicationsLiveSyncSupportedStatus();
            }
            let selectedApplication = _.find(this.applicationsLiveSyncInfos, app => app.applicationIdentifier === appIdentifier);
            return !!selectedApplication && selectedApplication.isLiveSyncSupported;
        });
    }
    uninstallApplication(appIdentifier) {
        return __awaiter(this, void 0, void 0, function* () {
            let afc = this.device.startService(iOSProxyServices.MobileServices.INSTALLATION_PROXY);
            try {
                let result = this.$mobileDevice.deviceUninstallApplication(afc, this.$coreFoundation.createCFString(appIdentifier), null, this.uninstallApplicationCallbackPtr);
                if (result) {
                    this.$errors.failWithoutHelp("AMDeviceUninstallApplication returned '%d'.", result);
                }
            }
            catch (e) {
                this.$logger.trace(`Error while uninstalling application ${e}.`);
            }
            this.$logger.trace("Application %s has been uninstalled successfully.", appIdentifier);
        });
    }
    startApplication(appIdentifier) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.$hostInfo.isWindows && !this.$staticConfig.enableDeviceRunCommandOnWindows) {
                this.$errors.fail("$%s device run command is not supported on Windows for iOS devices.", this.$staticConfig.CLIENT_NAME.toLowerCase());
            }
            this.validateApplicationId(appIdentifier);
            yield this.device.mountImage();
            yield this.runApplicationCore(appIdentifier);
            this.$logger.info(`Successfully run application ${appIdentifier} on device with ID ${this.device.deviceInfo.identifier}.`);
        });
    }
    stopApplication(appIdentifier) {
        return __awaiter(this, void 0, void 0, function* () {
            let application = this.getApplicationById(appIdentifier);
            let gdbServer = this.createGdbServer(this.device.deviceInfo.identifier);
            return gdbServer.kill([`${application.Path}`]);
        });
    }
    restartApplication(applicationId) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.stopApplication(applicationId);
            yield this.runApplicationCore(applicationId);
        });
    }
    canStartApplication() {
        return this.$hostInfo.isDarwin || (this.$hostInfo.isWindows && this.$staticConfig.enableDeviceRunCommandOnWindows);
    }
    getDebuggableApps() {
        return Promise.resolve([]);
    }
    getDebuggableAppViews(appIdentifiers) {
        return Promise.resolve(null);
    }
    lookupApplications() {
        let func = () => {
            let dictionaryPointer = ref.alloc(ios_core_1.CoreTypes.cfDictionaryRef);
            let result = this.$mobileDevice.deviceLookupApplications(this.devicePointer, 0, dictionaryPointer);
            if (result) {
                this.$errors.fail("Invalid result code %s from device lookup applications.", result);
            }
            let cfDictionary = dictionaryPointer.deref();
            let jsDictionary = this.$coreFoundation.cfTypeTo(cfDictionary);
            return jsDictionary;
        };
        return this.device.tryExecuteFunction(func);
    }
    validateApplicationId(appIdentifier) {
        let applications = this.lookupApplications();
        let application = applications[appIdentifier];
        if (!application) {
            let sortedKeys = _.sortBy(_.keys(applications));
            this.$errors.failWithoutHelp("Invalid application id: %s. All available application ids are: %s%s ", appIdentifier, os.EOL, sortedKeys.join(os.EOL));
        }
        return application;
    }
    runApplicationCore(appIdentifier) {
        this.destroyGdbServer();
        let application = this.getApplicationById(appIdentifier);
        let gdbServer = this.createGdbServer(this.device.deviceInfo.identifier);
        return gdbServer.run([`${application.Path}`]);
    }
    createGdbServer(deviceIdentifier) {
        if (!this._gdbServer) {
            let service = this.device.startService(iOSProxyServices.MobileServices.DEBUG_SERVER);
            let socket = this.$hostInfo.isWindows ? service : new net.Socket({ fd: service.toString() });
            this._gdbServer = this.$injector.resolve(ios_core_1.GDBServer, { socket: socket, deviceIdentifier: deviceIdentifier });
            this.$processService.attachToProcessExitSignals(this, this.destroyGdbServer);
        }
        return this._gdbServer;
    }
    destroyGdbServer() {
        if (this._gdbServer) {
            this._gdbServer.destroy();
            this._gdbServer = null;
        }
    }
    getApplicationById(appIdentifier) {
        return this.validateApplicationId(appIdentifier);
    }
}
__decorate([
    helpers_1.hook('install')
], IOSApplicationManager.prototype, "installApplication", null);
exports.IOSApplicationManager = IOSApplicationManager;
