"use strict";
const path = require("path");
const shelljs = require("shelljs");
const semver = require("semver");
const constants = require("../constants");
class PluginsService {
    constructor($npm, $fs, $childProcess, $options, $logger, $errors, $injector) {
        this.$npm = $npm;
        this.$fs = $fs;
        this.$childProcess = $childProcess;
        this.$options = $options;
        this.$logger = $logger;
        this.$errors = $errors;
        this.$injector = $injector;
    }
    get $projectData() {
        return this.$injector.resolve("projectData");
    }
    get $platformsData() {
        return this.$injector.resolve("platformsData");
    }
    get $pluginVariablesService() {
        return this.$injector.resolve("pluginVariablesService");
    }
    get $projectDataService() {
        return this.$injector.resolve("projectDataService");
    }
    get $projectFilesManager() {
        return this.$injector.resolve("projectFilesManager");
    }
    add(plugin) {
        return (() => {
            this.ensure().wait();
            const possiblePackageName = path.resolve(plugin);
            if (possiblePackageName.indexOf(".tgz") !== -1 && this.$fs.exists(possiblePackageName).wait()) {
                plugin = possiblePackageName;
            }
            let name = this.$npm.install(plugin, this.$projectData.projectDir, PluginsService.NPM_CONFIG).wait()[0];
            let pathToRealNpmPackageJson = path.join(this.$projectData.projectDir, "node_modules", name, "package.json");
            let realNpmPackageJson = this.$fs.readJson(pathToRealNpmPackageJson).wait();
            if (realNpmPackageJson.nativescript) {
                let pluginData = this.convertToPluginData(realNpmPackageJson);
                let action = (pluginDestinationPath, platform, platformData) => {
                    return (() => {
                        this.isPluginDataValidForPlatform(pluginData, platform).wait();
                    }).future()();
                };
                this.executeForAllInstalledPlatforms(action).wait();
                try {
                    this.$pluginVariablesService.savePluginVariablesInProjectFile(pluginData).wait();
                }
                catch (err) {
                    this.$projectDataService.initialize(this.$projectData.projectDir);
                    this.$projectDataService.removeProperty(this.$pluginVariablesService.getPluginVariablePropertyName(pluginData.name)).wait();
                    this.$npm.uninstall(plugin, PluginsService.NPM_CONFIG, this.$projectData.projectDir).wait();
                    throw err;
                }
                this.$logger.out(`Successfully installed plugin ${realNpmPackageJson.name}.`);
            }
            else {
                this.$npm.uninstall(realNpmPackageJson.name, { save: true }, this.$projectData.projectDir);
                this.$errors.failWithoutHelp(`${plugin} is not a valid NativeScript plugin. Verify that the plugin package.json file contains a nativescript key and try again.`);
            }
        }).future()();
    }
    remove(pluginName) {
        return (() => {
            let removePluginNativeCodeAction = (modulesDestinationPath, platform, platformData) => {
                return (() => {
                    let pluginData = this.convertToPluginData(this.getNodeModuleData(pluginName).wait());
                    platformData.platformProjectService.removePluginNativeCode(pluginData).wait();
                }).future()();
            };
            this.$pluginVariablesService.removePluginVariablesFromProjectFile(pluginName.toLowerCase()).wait();
            this.executeForAllInstalledPlatforms(removePluginNativeCodeAction).wait();
            this.executeNpmCommand(PluginsService.UNINSTALL_COMMAND_NAME, pluginName).wait();
            let showMessage = true;
            let action = (modulesDestinationPath, platform, platformData) => {
                return (() => {
                    shelljs.rm("-rf", path.join(modulesDestinationPath, pluginName));
                    this.$logger.out(`Successfully removed plugin ${pluginName} for ${platform}.`);
                    showMessage = false;
                }).future()();
            };
            this.executeForAllInstalledPlatforms(action).wait();
            if (showMessage) {
                this.$logger.out(`Succsessfully removed plugin ${pluginName}`);
            }
        }).future()();
    }
    getAvailable(filter) {
        let silent = true;
        return this.$npm.search(filter, { "silent": silent });
    }
    prepare(dependencyData, platform) {
        return (() => {
            platform = platform.toLowerCase();
            let platformData = this.$platformsData.getPlatformData(platform);
            let pluginData = this.convertToPluginData(dependencyData);
            let appFolderExists = this.$fs.exists(path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME)).wait();
            if (appFolderExists) {
                this.preparePluginScripts(pluginData, platform);
                this.preparePluginNativeCode(pluginData, platform);
                this.$logger.out(`Successfully prepared plugin ${pluginData.name} for ${platform}.`);
            }
        }).future()();
    }
    preparePluginScripts(pluginData, platform) {
        let platformData = this.$platformsData.getPlatformData(platform);
        let pluginScriptsDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, "tns_modules");
        let scriptsDestinationExists = this.$fs.exists(pluginScriptsDestinationPath).wait();
        if (!scriptsDestinationExists) {
            return;
        }
        if (!this.isPluginDataValidForPlatform(pluginData, platform).wait()) {
            return;
        }
        this.$projectFilesManager.processPlatformSpecificFiles(pluginScriptsDestinationPath, platform).wait();
    }
    preparePluginNativeCode(pluginData, platform) {
        let platformData = this.$platformsData.getPlatformData(platform);
        pluginData.pluginPlatformsFolderPath = (_platform) => path.join(pluginData.fullPath, "platforms", _platform);
        platformData.platformProjectService.preparePluginNativeCode(pluginData).wait();
    }
    ensureAllDependenciesAreInstalled() {
        return (() => {
            let installedDependencies = this.$fs.exists(this.nodeModulesPath).wait() ? this.$fs.readDirectory(this.nodeModulesPath).wait() : [];
            _(installedDependencies)
                .filter(dependencyName => _.startsWith(dependencyName, "@"))
                .each(scopedDependencyDir => {
                let contents = this.$fs.readDirectory(path.join(this.nodeModulesPath, scopedDependencyDir)).wait();
                installedDependencies = installedDependencies.concat(contents.map(dependencyName => `${scopedDependencyDir}/${dependencyName}`));
            });
            let packageJsonContent = this.$fs.readJson(this.getPackageJsonFilePath()).wait();
            let allDependencies = _.keys(packageJsonContent.dependencies).concat(_.keys(packageJsonContent.devDependencies));
            let notInstalledDependencies = _.difference(allDependencies, installedDependencies);
            if (this.$options.force || notInstalledDependencies.length) {
                this.$logger.trace("Npm install will be called from CLI. Force option is: ", this.$options.force, " Not installed dependencies are: ", notInstalledDependencies);
                this.$npm.install(this.$projectData.projectDir, this.$projectData.projectDir, { "ignore-scripts": this.$options.ignoreScripts }).wait();
            }
        }).future()();
    }
    getAllInstalledPlugins() {
        return (() => {
            let nodeModules = this.getAllInstalledModules().wait().map(nodeModuleData => this.convertToPluginData(nodeModuleData));
            return _.filter(nodeModules, nodeModuleData => nodeModuleData && nodeModuleData.isPlugin);
        }).future()();
    }
    getDependenciesFromPackageJson() {
        return (() => {
            let packageJson = this.$fs.readJson(this.getPackageJsonFilePath()).wait();
            let dependencies = this.getBasicPluginInformation(packageJson.dependencies);
            let devDependencies = this.getBasicPluginInformation(packageJson.devDependencies);
            return {
                dependencies,
                devDependencies
            };
        }).future()();
    }
    getBasicPluginInformation(dependencies) {
        return _.map(dependencies, (version, key) => ({
            name: key,
            version: version
        }));
    }
    get nodeModulesPath() {
        return path.join(this.$projectData.projectDir, "node_modules");
    }
    getPackageJsonFilePath() {
        return path.join(this.$projectData.projectDir, "package.json");
    }
    getPackageJsonFilePathForModule(moduleName) {
        return path.join(this.nodeModulesPath, moduleName, "package.json");
    }
    getDependencies() {
        let packageJsonFilePath = this.getPackageJsonFilePath();
        return _.keys(require(packageJsonFilePath).dependencies);
    }
    getNodeModuleData(module) {
        return (() => {
            if (!this.$fs.exists(module).wait() || path.basename(module) !== "package.json") {
                module = this.getPackageJsonFilePathForModule(module);
            }
            let data = this.$fs.readJson(module).wait();
            return {
                name: data.name,
                version: data.version,
                fullPath: path.dirname(module),
                isPlugin: data.nativescript !== undefined,
                moduleInfo: data.nativescript
            };
        }).future()();
    }
    convertToPluginData(cacheData) {
        let pluginData = {};
        pluginData.name = cacheData.name;
        pluginData.version = cacheData.version;
        pluginData.fullPath = cacheData.directory || path.dirname(this.getPackageJsonFilePathForModule(cacheData.name));
        pluginData.isPlugin = !!cacheData.nativescript || !!cacheData.moduleInfo;
        pluginData.pluginPlatformsFolderPath = (platform) => path.join(pluginData.fullPath, "platforms", platform);
        let data = cacheData.nativescript || cacheData.moduleInfo;
        if (pluginData.isPlugin) {
            pluginData.platformsData = data.platforms;
            pluginData.pluginVariables = data.variables;
        }
        return pluginData;
    }
    ensure() {
        return (() => {
            this.ensureAllDependenciesAreInstalled().wait();
            this.$fs.ensureDirectoryExists(this.nodeModulesPath).wait();
        }).future()();
    }
    getAllInstalledModules() {
        return (() => {
            this.ensure().wait();
            let nodeModules = this.getDependencies();
            return _.map(nodeModules, nodeModuleName => this.getNodeModuleData(nodeModuleName).wait());
        }).future()();
    }
    executeNpmCommand(npmCommandName, npmCommandArguments) {
        return (() => {
            if (npmCommandName === PluginsService.INSTALL_COMMAND_NAME) {
                this.$npm.install(npmCommandArguments, this.$projectData.projectDir, PluginsService.NPM_CONFIG).wait();
            }
            else if (npmCommandName === PluginsService.UNINSTALL_COMMAND_NAME) {
                this.$npm.uninstall(npmCommandArguments, PluginsService.NPM_CONFIG, this.$projectData.projectDir).wait();
            }
            return this.parseNpmCommandResult(npmCommandArguments);
        }).future()();
    }
    parseNpmCommandResult(npmCommandResult) {
        return npmCommandResult.split("@")[0];
    }
    executeForAllInstalledPlatforms(action) {
        return (() => {
            let availablePlatforms = _.keys(this.$platformsData.availablePlatforms);
            _.each(availablePlatforms, platform => {
                let isPlatformInstalled = this.$fs.exists(path.join(this.$projectData.platformsDir, platform.toLowerCase())).wait();
                if (isPlatformInstalled) {
                    let platformData = this.$platformsData.getPlatformData(platform.toLowerCase());
                    let pluginDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, "tns_modules");
                    action(pluginDestinationPath, platform.toLowerCase(), platformData).wait();
                }
            });
        }).future()();
    }
    getInstalledFrameworkVersion(platform) {
        return (() => {
            let platformData = this.$platformsData.getPlatformData(platform);
            this.$projectDataService.initialize(this.$projectData.projectDir);
            let frameworkData = this.$projectDataService.getValue(platformData.frameworkPackageName).wait();
            return frameworkData.version;
        }).future()();
    }
    isPluginDataValidForPlatform(pluginData, platform) {
        return (() => {
            let isValid = true;
            let installedFrameworkVersion = this.getInstalledFrameworkVersion(platform).wait();
            let pluginPlatformsData = pluginData.platformsData;
            if (pluginPlatformsData) {
                let pluginVersion = pluginPlatformsData[platform];
                if (!pluginVersion) {
                    this.$logger.warn(`${pluginData.name} is not supported for ${platform}.`);
                    isValid = false;
                }
                else if (semver.gt(pluginVersion, installedFrameworkVersion)) {
                    this.$logger.warn(`${pluginData.name} ${pluginVersion} for ${platform} is not compatible with the currently installed framework version ${installedFrameworkVersion}.`);
                    isValid = false;
                }
            }
            return isValid;
        }).future()();
    }
}
PluginsService.INSTALL_COMMAND_NAME = "install";
PluginsService.UNINSTALL_COMMAND_NAME = "uninstall";
PluginsService.NPM_CONFIG = {
    save: true
};
exports.PluginsService = PluginsService;
$injector.register("pluginsService", PluginsService);
