"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 path = require("path");
const shell = require("shelljs");
const constants = require("../constants");
const helpers = require("../common/helpers");
const semver = require("semver");
const events_1 = require("events");
const app_files_updater_1 = require("./app-files-updater");
const temp = require("temp");
temp.track();
let clui = require("clui");
const buildInfoFileName = ".nsbuildinfo";
class PlatformService extends events_1.EventEmitter {
    constructor($devicesService, $errors, $fs, $logger, $npmInstallationManager, $platformsData, $projectDataService, $hooksService, $nodeModulesBuilder, $pluginsService, $projectFilesManager, $mobileHelper, $hostInfo, $xmlValidator, $npm, $sysInfo, $devicePlatformsConstants, $deviceAppDataFactory, $projectChangesService, $emulatorPlatformService, $analyticsService, $messages, $staticConfig) {
        super();
        this.$devicesService = $devicesService;
        this.$errors = $errors;
        this.$fs = $fs;
        this.$logger = $logger;
        this.$npmInstallationManager = $npmInstallationManager;
        this.$platformsData = $platformsData;
        this.$projectDataService = $projectDataService;
        this.$hooksService = $hooksService;
        this.$nodeModulesBuilder = $nodeModulesBuilder;
        this.$pluginsService = $pluginsService;
        this.$projectFilesManager = $projectFilesManager;
        this.$mobileHelper = $mobileHelper;
        this.$hostInfo = $hostInfo;
        this.$xmlValidator = $xmlValidator;
        this.$npm = $npm;
        this.$sysInfo = $sysInfo;
        this.$devicePlatformsConstants = $devicePlatformsConstants;
        this.$deviceAppDataFactory = $deviceAppDataFactory;
        this.$projectChangesService = $projectChangesService;
        this.$emulatorPlatformService = $emulatorPlatformService;
        this.$analyticsService = $analyticsService;
        this.$messages = $messages;
        this.$staticConfig = $staticConfig;
        this._trackedProjectFilePath = null;
    }
    get _hooksService() {
        return this.$hooksService;
    }
    addPlatforms(platforms, platformTemplate, projectData, platformSpecificData, frameworkPath) {
        return __awaiter(this, void 0, void 0, function* () {
            let platformsDir = projectData.platformsDir;
            this.$fs.ensureDirectoryExists(platformsDir);
            for (let platform of platforms) {
                yield this.addPlatform(platform.toLowerCase(), platformTemplate, projectData, platformSpecificData, frameworkPath);
            }
        });
    }
    addPlatform(platformParam, platformTemplate, projectData, platformSpecificData, frameworkPath) {
        return __awaiter(this, void 0, void 0, function* () {
            let data = platformParam.split("@"), platform = data[0].toLowerCase(), version = data[1];
            this.validatePlatform(platform, projectData);
            let platformPath = path.join(projectData.platformsDir, platform);
            if (this.$fs.exists(platformPath)) {
                this.$errors.failWithoutHelp("Platform %s already added", platform);
            }
            let platformData = this.$platformsData.getPlatformData(platform, projectData);
            let currentPlatformData = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName);
            if (currentPlatformData && currentPlatformData[constants.VERSION_STRING]) {
                version = currentPlatformData[constants.VERSION_STRING];
            }
            let platformProjectService = platformData.platformProjectService;
            yield platformProjectService.validate(projectData);
            this.$logger.trace("Creating NativeScript project for the %s platform", platform);
            this.$logger.trace("Path: %s", platformData.projectRoot);
            this.$logger.trace("Package: %s", projectData.projectId);
            this.$logger.trace("Name: %s", projectData.projectName);
            this.$logger.out("Copying template files...");
            let packageToInstall = "";
            let npmOptions = {
                pathToSave: path.join(projectData.platformsDir, platform),
                dependencyType: "save"
            };
            if (!frameworkPath) {
                packageToInstall = platformData.frameworkPackageName;
                npmOptions["version"] = version;
            }
            let spinner = new clui.Spinner("Installing " + packageToInstall);
            let projectDir = projectData.projectDir;
            try {
                spinner.start();
                let downloadedPackagePath = yield this.$npmInstallationManager.install(packageToInstall, projectDir, npmOptions);
                let frameworkDir = path.join(downloadedPackagePath, constants.PROJECT_FRAMEWORK_FOLDER_NAME);
                frameworkDir = path.resolve(frameworkDir);
                let coreModuleName = yield this.addPlatformCore(platformData, frameworkDir, platformTemplate, projectData, platformSpecificData);
                yield this.$npm.uninstall(coreModuleName, { save: true }, projectData.projectDir);
            }
            catch (err) {
                this.$fs.deleteDirectory(platformPath);
                throw err;
            }
            finally {
                spinner.stop();
            }
            this.$logger.out("Project successfully created.");
        });
    }
    addPlatformCore(platformData, frameworkDir, platformTemplate, projectData, platformSpecificData) {
        return __awaiter(this, void 0, void 0, function* () {
            let coreModuleData = this.$fs.readJson(path.join(frameworkDir, "../", "package.json"));
            let installedVersion = coreModuleData.version;
            let coreModuleName = coreModuleData.name;
            let customTemplateOptions = yield this.getPathToPlatformTemplate(platformTemplate, platformData.frameworkPackageName, projectData.projectDir);
            let pathToTemplate = customTemplateOptions && customTemplateOptions.pathToTemplate;
            yield platformData.platformProjectService.createProject(path.resolve(frameworkDir), installedVersion, projectData, pathToTemplate);
            platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData);
            yield platformData.platformProjectService.interpolateData(projectData, platformSpecificData);
            platformData.platformProjectService.afterCreateProject(platformData.projectRoot, projectData);
            let frameworkPackageNameData = { version: installedVersion };
            if (customTemplateOptions) {
                frameworkPackageNameData.template = customTemplateOptions.selectedTemplate;
            }
            this.$projectDataService.setNSValue(projectData.projectDir, platformData.frameworkPackageName, frameworkPackageNameData);
            return coreModuleName;
        });
    }
    getPathToPlatformTemplate(selectedTemplate, frameworkPackageName, projectDir) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!selectedTemplate) {
                const nativescriptPlatformData = this.$projectDataService.getNSValue(projectDir, frameworkPackageName);
                selectedTemplate = nativescriptPlatformData && nativescriptPlatformData.template;
            }
            if (selectedTemplate) {
                let tempDir = temp.mkdirSync("platform-template");
                try {
                    let pathToTemplate = (yield this.$npm.install(selectedTemplate, tempDir))[0];
                    return { selectedTemplate, pathToTemplate };
                }
                catch (err) {
                    this.$logger.trace("Error while trying to install specified template: ", err);
                    this.$errors.failWithoutHelp(`Unable to install platform template ${selectedTemplate}. Make sure the specified value is valid.`);
                }
            }
            return null;
        });
    }
    getInstalledPlatforms(projectData) {
        if (!this.$fs.exists(projectData.platformsDir)) {
            return [];
        }
        let subDirs = this.$fs.readDirectory(projectData.platformsDir);
        return _.filter(subDirs, p => this.$platformsData.platformsNames.indexOf(p) > -1);
    }
    getAvailablePlatforms(projectData) {
        let installedPlatforms = this.getInstalledPlatforms(projectData);
        return _.filter(this.$platformsData.platformsNames, p => {
            return installedPlatforms.indexOf(p) < 0 && this.isPlatformSupportedForOS(p, projectData);
        });
    }
    getPreparedPlatforms(projectData) {
        return _.filter(this.$platformsData.platformsNames, p => { return this.isPlatformPrepared(p, projectData); });
    }
    preparePlatform(platform, appFilesUpdaterOptions, platformTemplate, projectData, platformSpecificData, filesToSync) {
        return __awaiter(this, void 0, void 0, function* () {
            this.validatePlatform(platform, projectData);
            yield this.trackProjectType(projectData);
            try {
                yield this.$pluginsService.ensureAllDependenciesAreInstalled(projectData);
            }
            catch (err) {
                this.$logger.trace(err);
                this.$errors.failWithoutHelp(`Unable to install dependencies. Make sure your package.json is valid and all dependencies are correct. Error is: ${err.message}`);
            }
            if (platform === "ios") {
                for (let pluginData of yield this.$pluginsService.getAllInstalledPlugins(projectData)) {
                    if (this.$fs.exists(path.join(pluginData.pluginPlatformsFolderPath(platform), "Podfile")) &&
                        !(yield this.$sysInfo.getCocoapodVersion())) {
                        this.$errors.failWithoutHelp(`${pluginData.name} has Podfile and you don't have Cocoapods installed or it is not configured correctly. Please verify Cocoapods can work on your machine.`);
                    }
                }
            }
            yield this.ensurePlatformInstalled(platform, platformTemplate, projectData, platformSpecificData);
            let changesInfo = this.$projectChangesService.checkForChanges(platform, projectData);
            this.$logger.trace("Changes info in prepare platform:", changesInfo);
            if (changesInfo.hasChanges) {
                if (platform.toLowerCase() === "android") {
                    let previousPrepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData);
                    if (previousPrepareInfo && previousPrepareInfo.release !== appFilesUpdaterOptions.release) {
                        let platformData = this.$platformsData.getPlatformData(platform, projectData);
                        yield platformData.platformProjectService.cleanProject(platformData.projectRoot, [], projectData);
                    }
                }
                yield this.preparePlatformCore(platform, appFilesUpdaterOptions, projectData, platformSpecificData, changesInfo, filesToSync);
                this.$projectChangesService.savePrepareInfo(platform, projectData);
            }
            else {
                this.$logger.out("Skipping prepare.");
            }
            return true;
        });
    }
    validateOptions(provision, projectData, platform) {
        return __awaiter(this, void 0, void 0, function* () {
            if (platform) {
                platform = this.$mobileHelper.normalizePlatformName(platform);
                this.$logger.trace("Validate options for platform: " + platform);
                let platformData = this.$platformsData.getPlatformData(platform, projectData);
                return yield platformData.platformProjectService.validateOptions(provision);
            }
            else {
                let valid = true;
                for (let availablePlatform in this.$platformsData.availablePlatforms) {
                    this.$logger.trace("Validate options for platform: " + availablePlatform);
                    let platformData = this.$platformsData.getPlatformData(availablePlatform, projectData);
                    valid = valid && (yield platformData.platformProjectService.validateOptions(provision));
                }
                return valid;
            }
        });
    }
    preparePlatformCore(platform, appFilesUpdaterOptions, projectData, platformSpecificData, changesInfo, filesToSync) {
        return __awaiter(this, void 0, void 0, function* () {
            this.$logger.out("Preparing project...");
            let platformData = this.$platformsData.getPlatformData(platform, projectData);
            if (!changesInfo || changesInfo.appFilesChanged) {
                yield this.copyAppFiles(platform, appFilesUpdaterOptions, projectData);
            }
            if (!changesInfo || changesInfo.appResourcesChanged) {
                this.copyAppResources(platform, projectData);
                yield platformData.platformProjectService.prepareProject(projectData, platformSpecificData);
            }
            if (!changesInfo || changesInfo.modulesChanged) {
                yield this.copyTnsModules(platform, projectData);
            }
            let directoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);
            let excludedDirs = [constants.APP_RESOURCES_FOLDER_NAME];
            if (!changesInfo || !changesInfo.modulesChanged) {
                excludedDirs.push(constants.TNS_MODULES_FOLDER_NAME);
            }
            this.$projectFilesManager.processPlatformSpecificFiles(directoryPath, platform, excludedDirs);
            if (!changesInfo || changesInfo.configChanged || changesInfo.modulesChanged) {
                yield platformData.platformProjectService.processConfigurationFilesFromAppResources(appFilesUpdaterOptions.release, projectData);
            }
            platformData.platformProjectService.interpolateConfigurationFile(projectData, platformSpecificData);
            this.$logger.out("Project successfully prepared (" + platform + ")");
        });
    }
    copyAppFiles(platform, appFilesUpdaterOptions, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            let platformData = this.$platformsData.getPlatformData(platform, projectData);
            platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData);
            let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);
            this.$fs.ensureDirectoryExists(appDestinationDirectoryPath);
            let appSourceDirectoryPath = path.join(projectData.projectDir, constants.APP_FOLDER_NAME);
            const appUpdater = new app_files_updater_1.AppFilesUpdater(appSourceDirectoryPath, appDestinationDirectoryPath, appFilesUpdaterOptions, this.$fs);
            appUpdater.updateApp(sourceFiles => {
                this.$xmlValidator.validateXmlFiles(sourceFiles);
            });
        });
    }
    copyAppResources(platform, projectData) {
        let platformData = this.$platformsData.getPlatformData(platform, projectData);
        let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);
        let appResourcesDirectoryPath = path.join(appDestinationDirectoryPath, constants.APP_RESOURCES_FOLDER_NAME);
        if (this.$fs.exists(appResourcesDirectoryPath)) {
            platformData.platformProjectService.prepareAppResources(appResourcesDirectoryPath, projectData);
            let appResourcesDestination = platformData.platformProjectService.getAppResourcesDestinationDirectoryPath(projectData);
            this.$fs.ensureDirectoryExists(appResourcesDestination);
            shell.cp("-Rf", path.join(appResourcesDirectoryPath, platformData.normalizedPlatformName, "*"), appResourcesDestination);
            this.$fs.deleteDirectory(appResourcesDirectoryPath);
        }
    }
    copyTnsModules(platform, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            let platformData = this.$platformsData.getPlatformData(platform, projectData);
            let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);
            let lastModifiedTime = this.$fs.exists(appDestinationDirectoryPath) ? this.$fs.getFsStats(appDestinationDirectoryPath).mtime : null;
            try {
                let tnsModulesDestinationPath = path.join(appDestinationDirectoryPath, constants.TNS_MODULES_FOLDER_NAME);
                yield this.$nodeModulesBuilder.prepareNodeModules(tnsModulesDestinationPath, platform, lastModifiedTime, projectData);
            }
            catch (error) {
                this.$logger.debug(error);
                shell.rm("-rf", appDestinationDirectoryPath);
                this.$errors.failWithoutHelp(`Processing node_modules failed. ${error}`);
            }
        });
    }
    shouldBuild(platform, projectData, buildConfig) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.$projectChangesService.currentChanges.changesRequireBuild) {
                return true;
            }
            let platformData = this.$platformsData.getPlatformData(platform, projectData);
            let forDevice = !buildConfig || buildConfig.buildForDevice;
            let outputPath = forDevice ? platformData.deviceBuildOutputPath : platformData.emulatorBuildOutputPath;
            if (!this.$fs.exists(outputPath)) {
                return true;
            }
            let packageNames = forDevice ? platformData.validPackageNamesForDevice : platformData.validPackageNamesForEmulator;
            let packages = this.getApplicationPackages(outputPath, packageNames);
            if (packages.length === 0) {
                return true;
            }
            let prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData);
            let buildInfo = this.getBuildInfo(platform, platformData, buildConfig);
            if (!prepareInfo || !buildInfo) {
                return true;
            }
            if (buildConfig.clean) {
                return prepareInfo.time !== buildInfo.prepareTime;
            }
            if (prepareInfo.time === buildInfo.prepareTime) {
                return false;
            }
            return prepareInfo.changesRequireBuildTime !== buildInfo.prepareTime;
        });
    }
    trackProjectType(projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            if (projectData && (projectData.projectFilePath !== this._trackedProjectFilePath)) {
                this._trackedProjectFilePath = projectData.projectFilePath;
                yield this.$analyticsService.track("Working with project type", projectData.projectType);
            }
        });
    }
    buildPlatform(platform, buildConfig, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            this.$logger.out("Building project...");
            yield this.trackProjectType(projectData);
            let platformData = this.$platformsData.getPlatformData(platform, projectData);
            platformData.platformProjectService.on(constants.BUILD_OUTPUT_EVENT_NAME, (data) => {
                this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data);
            });
            yield platformData.platformProjectService.buildProject(platformData.projectRoot, projectData, buildConfig);
            let prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData);
            let buildInfoFilePath = this.getBuildOutputPath(platform, platformData, buildConfig);
            let buildInfoFile = path.join(buildInfoFilePath, buildInfoFileName);
            let buildInfo = {
                prepareTime: prepareInfo.changesRequireBuildTime,
                buildTime: new Date().toString()
            };
            this.$fs.writeJson(buildInfoFile, buildInfo);
            this.$logger.out("Project successfully built.");
        });
    }
    shouldInstall(device, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            let platform = device.deviceInfo.platform;
            let platformData = this.$platformsData.getPlatformData(platform, projectData);
            if (!(yield device.applicationManager.isApplicationInstalled(projectData.projectId))) {
                return true;
            }
            let deviceBuildInfo = yield this.getDeviceBuildInfo(device, projectData);
            let localBuildInfo = this.getBuildInfo(platform, platformData, { buildForDevice: !device.isEmulator });
            return !localBuildInfo || !deviceBuildInfo || deviceBuildInfo.buildTime !== localBuildInfo.buildTime;
        });
    }
    installApplication(device, options, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            this.$logger.out("Installing...");
            let platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData);
            let packageFile = "";
            if (this.$devicesService.isiOSSimulator(device)) {
                packageFile = this.getLatestApplicationPackageForEmulator(platformData).packageName;
            }
            else {
                packageFile = this.getLatestApplicationPackageForDevice(platformData).packageName;
            }
            yield platformData.platformProjectService.deploy(device.deviceInfo.identifier, projectData);
            yield device.applicationManager.reinstallApplication(projectData.projectId, packageFile);
            if (!options.release) {
                let deviceFilePath = yield this.getDeviceBuildInfoFilePath(device, projectData);
                let buildInfoFilePath = this.getBuildOutputPath(device.deviceInfo.platform, platformData, { buildForDevice: !device.isEmulator });
                let appIdentifier = projectData.projectId;
                yield device.fileSystem.putFile(path.join(buildInfoFilePath, buildInfoFileName), deviceFilePath, appIdentifier);
            }
            this.$logger.out(`Successfully installed on device with identifier '${device.deviceInfo.identifier}'.`);
        });
    }
    deployPlatform(platform, appFilesUpdaterOptions, deployOptions, projectData, platformSpecificData) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.preparePlatform(platform, appFilesUpdaterOptions, deployOptions.platformTemplate, projectData, platformSpecificData);
            this.$logger.out("Searching for devices...");
            yield this.$devicesService.initialize({ platform: platform, deviceId: deployOptions.device });
            let action = (device) => __awaiter(this, void 0, void 0, function* () {
                let buildConfig = {
                    buildForDevice: !this.$devicesService.isiOSSimulator(device),
                    projectDir: deployOptions.projectDir,
                    release: deployOptions.release,
                    device: deployOptions.device,
                    provision: deployOptions.provision,
                    teamId: deployOptions.teamId,
                    keyStoreAlias: deployOptions.keyStoreAlias,
                    keyStoreAliasPassword: deployOptions.keyStoreAliasPassword,
                    keyStorePassword: deployOptions.keyStorePassword,
                    keyStorePath: deployOptions.keyStorePath
                };
                let shouldBuild = yield this.shouldBuild(platform, projectData, buildConfig);
                if (shouldBuild) {
                    yield this.buildPlatform(platform, buildConfig, projectData);
                }
                else {
                    this.$logger.out("Skipping package build. No changes detected on the native side. This will be fast!");
                }
                if (deployOptions.forceInstall || shouldBuild || (yield this.shouldInstall(device, projectData))) {
                    yield this.installApplication(device, deployOptions, projectData);
                }
                else {
                    this.$logger.out("Skipping install.");
                }
            });
            yield this.$devicesService.execute(action, this.getCanExecuteAction(platform, deployOptions));
        });
    }
    runPlatform(platform, runOptions, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.trackProjectType(projectData);
            this.$logger.out("Starting...");
            let action = (device) => __awaiter(this, void 0, void 0, function* () {
                yield device.applicationManager.startApplication(projectData.projectId);
                this.$logger.out(`Successfully started on device with identifier '${device.deviceInfo.identifier}'.`);
            });
            yield this.$devicesService.initialize({ platform: platform, deviceId: runOptions.device });
            yield this.$devicesService.execute(action, this.getCanExecuteAction(platform, runOptions));
        });
    }
    emulatePlatform(platform, appFilesUpdaterOptions, emulateOptions, projectData, platformSpecificData) {
        return __awaiter(this, void 0, void 0, function* () {
            if (emulateOptions.avd) {
                this.$logger.warn(`Option --avd is no longer supported. Please use --device instead!`);
                return Promise.resolve();
            }
            if (emulateOptions.availableDevices) {
                return this.$emulatorPlatformService.listAvailableEmulators(platform);
            }
            if (emulateOptions.device) {
                let info = yield this.$emulatorPlatformService.getEmulatorInfo(platform, emulateOptions.device);
                if (info) {
                    if (!info.isRunning) {
                        yield this.$emulatorPlatformService.startEmulator(info, projectData);
                    }
                    emulateOptions.device = null;
                }
                else {
                    yield this.$devicesService.initialize({ platform: platform, deviceId: emulateOptions.device });
                    let found = [];
                    if (this.$devicesService.hasDevices) {
                        found = this.$devicesService.getDevices().filter((deviceInfo) => deviceInfo.identifier === emulateOptions.device);
                    }
                    if (found.length === 0) {
                        this.$errors.fail(this.$messages.Devices.NotFoundDeviceByIdentifierErrorMessage, this.$staticConfig.CLIENT_NAME.toLowerCase());
                    }
                }
            }
            yield this.deployPlatform(platform, appFilesUpdaterOptions, emulateOptions, projectData, platformSpecificData);
            return this.runPlatform(platform, emulateOptions, projectData);
        });
    }
    getBuildOutputPath(platform, platformData, options) {
        if (platform.toLowerCase() === this.$devicePlatformsConstants.iOS.toLowerCase()) {
            return options.buildForDevice ? platformData.deviceBuildOutputPath : platformData.emulatorBuildOutputPath;
        }
        return platformData.deviceBuildOutputPath;
    }
    getDeviceBuildInfoFilePath(device, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            let deviceAppData = this.$deviceAppDataFactory.create(projectData.projectId, device.deviceInfo.platform, device);
            let deviceRootPath = path.dirname(yield deviceAppData.getDeviceProjectRootPath());
            return helpers.fromWindowsRelativePathToUnix(path.join(deviceRootPath, buildInfoFileName));
        });
    }
    getDeviceBuildInfo(device, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            let deviceFilePath = yield this.getDeviceBuildInfoFilePath(device, projectData);
            try {
                return JSON.parse(yield this.readFile(device, deviceFilePath, projectData));
            }
            catch (e) {
                return null;
            }
        });
    }
    getBuildInfo(platform, platformData, options) {
        let buildInfoFilePath = this.getBuildOutputPath(platform, platformData, options);
        let buildInfoFile = path.join(buildInfoFilePath, buildInfoFileName);
        if (this.$fs.exists(buildInfoFile)) {
            try {
                let buildInfoTime = this.$fs.readJson(buildInfoFile);
                return buildInfoTime;
            }
            catch (e) {
                return null;
            }
        }
        return null;
    }
    cleanDestinationApp(platform, appFilesUpdaterOptions, platformTemplate, projectData, platformSpecificData) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.ensurePlatformInstalled(platform, platformTemplate, projectData, platformSpecificData);
            const appSourceDirectoryPath = path.join(projectData.projectDir, constants.APP_FOLDER_NAME);
            let platformData = this.$platformsData.getPlatformData(platform, projectData);
            let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);
            const appUpdater = new app_files_updater_1.AppFilesUpdater(appSourceDirectoryPath, appDestinationDirectoryPath, appFilesUpdaterOptions, this.$fs);
            appUpdater.cleanDestinationApp();
        });
    }
    lastOutputPath(platform, settings, projectData) {
        let packageFile;
        let platformData = this.$platformsData.getPlatformData(platform, projectData);
        if (settings.isForDevice) {
            packageFile = this.getLatestApplicationPackageForDevice(platformData).packageName;
        }
        else {
            packageFile = this.getLatestApplicationPackageForEmulator(platformData).packageName;
        }
        if (!packageFile || !this.$fs.exists(packageFile)) {
            this.$errors.failWithoutHelp("Unable to find built application. Try 'tns build %s'.", platform);
        }
        return packageFile;
    }
    copyLastOutput(platform, targetPath, settings, projectData) {
        platform = platform.toLowerCase();
        targetPath = path.resolve(targetPath);
        let packageFile = this.lastOutputPath(platform, settings, projectData);
        this.$fs.ensureDirectoryExists(path.dirname(targetPath));
        if (this.$fs.exists(targetPath) && this.$fs.getFsStats(targetPath).isDirectory()) {
            let sourceFileName = path.basename(packageFile);
            this.$logger.trace(`Specified target path: '${targetPath}' is directory. Same filename will be used: '${sourceFileName}'.`);
            targetPath = path.join(targetPath, sourceFileName);
        }
        this.$fs.copyFile(packageFile, targetPath);
        this.$logger.info(`Copied file '${packageFile}' to '${targetPath}'.`);
    }
    removePlatforms(platforms, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            for (let platform of platforms) {
                this.validatePlatformInstalled(platform, projectData);
                let platformData = this.$platformsData.getPlatformData(platform, projectData);
                yield platformData.platformProjectService.stopServices(platformData.projectRoot);
                let platformDir = path.join(projectData.platformsDir, platform);
                this.$fs.deleteDirectory(platformDir);
                this.$projectDataService.removeNSProperty(projectData.projectDir, platformData.frameworkPackageName);
                this.$logger.out(`Platform ${platform} successfully removed.`);
            }
        });
    }
    updatePlatforms(platforms, platformTemplate, projectData, platformSpecificData) {
        return __awaiter(this, void 0, void 0, function* () {
            for (let platformParam of platforms) {
                let data = platformParam.split("@"), platform = data[0], version = data[1];
                if (this.isPlatformInstalled(platform, projectData)) {
                    yield this.updatePlatform(platform, version, platformTemplate, projectData, platformSpecificData);
                }
                else {
                    yield this.addPlatform(platformParam, platformTemplate, projectData, platformSpecificData);
                }
            }
            ;
        });
    }
    getCanExecuteAction(platform, options) {
        let canExecute = (currentDevice) => {
            if (options.device && currentDevice && currentDevice.deviceInfo) {
                let device = this.$devicesService.getDeviceByDeviceOption();
                if (device && device.deviceInfo) {
                    return currentDevice.deviceInfo.identifier === device.deviceInfo.identifier;
                }
            }
            if (this.$mobileHelper.isiOSPlatform(platform) && this.$hostInfo.isDarwin) {
                if (this.$devicesService.isOnlyiOSSimultorRunning() || options.emulator || this.$devicesService.isiOSSimulator(currentDevice)) {
                    return true;
                }
                return this.$devicesService.isiOSDevice(currentDevice);
            }
            return true;
        };
        return canExecute;
    }
    validatePlatform(platform, projectData) {
        if (!platform) {
            this.$errors.fail("No platform specified.");
        }
        platform = platform.split("@")[0].toLowerCase();
        if (!this.isValidPlatform(platform, projectData)) {
            this.$errors.fail("Invalid platform %s. Valid platforms are %s.", platform, helpers.formatListOfNames(this.$platformsData.platformsNames));
        }
        if (!this.isPlatformSupportedForOS(platform, projectData)) {
            this.$errors.fail("Applications for platform %s can not be built on this OS - %s", platform, process.platform);
        }
    }
    validatePlatformInstalled(platform, projectData) {
        this.validatePlatform(platform, projectData);
        if (!this.isPlatformInstalled(platform, projectData)) {
            this.$errors.fail("The platform %s is not added to this project. Please use 'tns platform add <platform>'", platform);
        }
    }
    ensurePlatformInstalled(platform, platformTemplate, projectData, platformSpecificData) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.isPlatformInstalled(platform, projectData)) {
                yield this.addPlatform(platform, platformTemplate, projectData, platformSpecificData);
            }
        });
    }
    isPlatformInstalled(platform, projectData) {
        return this.$fs.exists(path.join(projectData.platformsDir, platform.toLowerCase()));
    }
    isValidPlatform(platform, projectData) {
        return this.$platformsData.getPlatformData(platform, projectData);
    }
    isPlatformSupportedForOS(platform, projectData) {
        let targetedOS = this.$platformsData.getPlatformData(platform, projectData).targetedOS;
        let res = !targetedOS || targetedOS.indexOf("*") >= 0 || targetedOS.indexOf(process.platform) >= 0;
        return res;
    }
    isPlatformPrepared(platform, projectData) {
        let platformData = this.$platformsData.getPlatformData(platform, projectData);
        return platformData.platformProjectService.isPlatformPrepared(platformData.projectRoot, projectData);
    }
    getApplicationPackages(buildOutputPath, validPackageNames) {
        let candidates = this.$fs.readDirectory(buildOutputPath);
        let packages = _.filter(candidates, candidate => {
            return _.includes(validPackageNames, candidate);
        }).map(currentPackage => {
            currentPackage = path.join(buildOutputPath, currentPackage);
            return {
                packageName: currentPackage,
                time: this.$fs.getFsStats(currentPackage).mtime
            };
        });
        return packages;
    }
    getLatestApplicationPackage(buildOutputPath, validPackageNames) {
        let packages = this.getApplicationPackages(buildOutputPath, validPackageNames);
        if (packages.length === 0) {
            let packageExtName = path.extname(validPackageNames[0]);
            this.$errors.fail("No %s found in %s directory", packageExtName, buildOutputPath);
        }
        packages = _.sortBy(packages, pkg => pkg.time).reverse();
        return packages[0];
    }
    getLatestApplicationPackageForDevice(platformData) {
        return this.getLatestApplicationPackage(platformData.deviceBuildOutputPath, platformData.validPackageNamesForDevice);
    }
    getLatestApplicationPackageForEmulator(platformData) {
        return this.getLatestApplicationPackage(platformData.emulatorBuildOutputPath || platformData.deviceBuildOutputPath, platformData.validPackageNamesForEmulator || platformData.validPackageNamesForDevice);
    }
    updatePlatform(platform, version, platformTemplate, projectData, platformSpecificData) {
        return __awaiter(this, void 0, void 0, function* () {
            let platformData = this.$platformsData.getPlatformData(platform, projectData);
            let data = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName);
            let currentVersion = data && data.version ? data.version : "0.2.0";
            let newVersion = version === constants.PackageVersion.NEXT ?
                yield this.$npmInstallationManager.getNextVersion(platformData.frameworkPackageName) :
                version || (yield this.$npmInstallationManager.getLatestCompatibleVersion(platformData.frameworkPackageName));
            let installedModuleDir = yield this.$npmInstallationManager.install(platformData.frameworkPackageName, projectData.projectDir, { version: newVersion, dependencyType: "save" });
            let cachedPackageData = this.$fs.readJson(path.join(installedModuleDir, "package.json"));
            newVersion = (cachedPackageData && cachedPackageData.version) || newVersion;
            let canUpdate = platformData.platformProjectService.canUpdatePlatform(installedModuleDir, projectData);
            yield this.$npm.uninstall(platformData.frameworkPackageName, { save: true }, projectData.projectDir);
            if (canUpdate) {
                if (!semver.valid(newVersion)) {
                    this.$errors.fail("The version %s is not valid. The version should consists from 3 parts separated by dot.", newVersion);
                }
                if (!semver.gt(currentVersion, newVersion)) {
                    yield this.updatePlatformCore(platformData, { currentVersion, newVersion, canUpdate, platformTemplate }, projectData, platformSpecificData);
                }
                else if (semver.eq(currentVersion, newVersion)) {
                    this.$errors.fail("Current and new version are the same.");
                }
                else {
                    this.$errors.fail(`Your current version: ${currentVersion} is higher than the one you're trying to install ${newVersion}.`);
                }
            }
            else {
                this.$errors.failWithoutHelp("Native Platform cannot be updated.");
            }
        });
    }
    updatePlatformCore(platformData, updateOptions, projectData, platformSpecificData) {
        return __awaiter(this, void 0, void 0, function* () {
            let packageName = platformData.normalizedPlatformName.toLowerCase();
            yield this.removePlatforms([packageName], projectData);
            packageName = updateOptions.newVersion ? `${packageName}@${updateOptions.newVersion}` : packageName;
            yield this.addPlatform(packageName, updateOptions.platformTemplate, projectData, platformSpecificData);
            this.$logger.out("Successfully updated to version ", updateOptions.newVersion);
        });
    }
    readFile(device, deviceFilePath, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            temp.track();
            let uniqueFilePath = temp.path({ suffix: ".tmp" });
            try {
                yield device.fileSystem.getFile(deviceFilePath, projectData.projectId, uniqueFilePath);
            }
            catch (e) {
                return null;
            }
            if (this.$fs.exists(uniqueFilePath)) {
                let text = this.$fs.readText(uniqueFilePath);
                shell.rm(uniqueFilePath);
                return text;
            }
            return null;
        });
    }
}
__decorate([
    helpers.hook('prepare')
], PlatformService.prototype, "preparePlatformCore", null);
exports.PlatformService = PlatformService;
$injector.register("platformService", PlatformService);
