"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 path = require("path");
var os = require("os");
var constants = require("../../constants");
var helpers_1 = require("../../helpers");
var decorators_1 = require("../../decorators");
var NpmService = (function () {
    function NpmService($childProcess, $errors, $fs, $hostInfo, $httpClient, $logger, $projectConstants) {
        this.$childProcess = $childProcess;
        this.$errors = $errors;
        this.$fs = $fs;
        this.$hostInfo = $hostInfo;
        this.$httpClient = $httpClient;
        this.$logger = $logger;
        this.$projectConstants = $projectConstants;
    }
    Object.defineProperty(NpmService.prototype, "npmBinary", {
        get: function () {
            if (!this._npmBinary) {
                try {
                    require(NpmService.NPM_MODULE_NAME);
                    var npmMainJsFile = require.resolve(NpmService.NPM_MODULE_NAME);
                    var pathToNpmBinary = path.join(npmMainJsFile.substring(0, npmMainJsFile.lastIndexOf(constants.NODE_MODULES_DIR_NAME) + constants.NODE_MODULES_DIR_NAME.length), ".bin", this.npmExecutableName);
                    if (!this.$fs.exists(pathToNpmBinary).wait()) {
                        throw new Error("The npm binary is not in " + pathToNpmBinary + " as expected.");
                    }
                    this._npmBinary = pathToNpmBinary;
                }
                catch (err) {
                    this.$logger.trace("Error while trying to get the npm binary: " + err);
                    this._npmBinary = this.npmExecutableName;
                }
            }
            return this._npmBinary;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(NpmService.prototype, "npmExecutableName", {
        get: function () {
            if (!this._npmExecutableName) {
                this._npmExecutableName = "npm";
                if (this.$hostInfo.isWindows) {
                    this._npmExecutableName += ".cmd";
                }
            }
            return this._npmExecutableName;
        },
        enumerable: true,
        configurable: true
    });
    NpmService.prototype.install = function (projectDir, dependencyToInstall) {
        var _this = this;
        return (function () {
            var npmInstallResult = {};
            if (dependencyToInstall) {
                npmInstallResult.result = {
                    isInstalled: false,
                    isTypesInstalled: false
                };
                try {
                    _this.npmInstall(projectDir, dependencyToInstall.name, dependencyToInstall.version, ["--save", "--save-exact"]).wait();
                    npmInstallResult.result.isInstalled = true;
                }
                catch (err) {
                    npmInstallResult.error = err;
                }
                if (dependencyToInstall.installTypes && npmInstallResult.result.isInstalled && _this.hasTypesForDependency(dependencyToInstall.name).wait()) {
                    try {
                        _this.installTypingsForDependency(projectDir, dependencyToInstall.name).wait();
                        npmInstallResult.result.isTypesInstalled = true;
                    }
                    catch (err) {
                        npmInstallResult.error = err;
                    }
                }
            }
            else {
                try {
                    _this.npmPrune(projectDir).wait();
                    _this.npmInstall(projectDir).wait();
                }
                catch (err) {
                    npmInstallResult.error = err;
                }
            }
            _this.generateReferencesFile(projectDir).wait();
            return npmInstallResult;
        }).future()();
    };
    NpmService.prototype.uninstall = function (projectDir, dependency) {
        var _this = this;
        return (function () {
            var packageJsonContent = _this.getPackageJsonContent(projectDir).wait();
            if (packageJsonContent && packageJsonContent.dependencies && packageJsonContent.dependencies[dependency]) {
                _this.npmUninstall(projectDir, dependency, ["--save"]).wait();
            }
            if (packageJsonContent && packageJsonContent.devDependencies && packageJsonContent.devDependencies[("" + NpmService.TYPES_DIRECTORY + dependency)]) {
                _this.npmUninstall(projectDir, "" + NpmService.TYPES_DIRECTORY + dependency, ["--save-dev"]).wait();
            }
            _this.generateReferencesFile(projectDir).wait();
        }).future()();
    };
    NpmService.prototype.search = function (projectDir, keywords, args) {
        var _this = this;
        if (args === void 0) { args = []; }
        return (function () {
            var result = [];
            var commandArguments = _.concat(["search"], args, keywords);
            var spawnResult = _this.executeNpmCommandCore(projectDir, commandArguments).wait();
            if (spawnResult.stderr) {
                var splitError = spawnResult.stderr.trim().split("\n");
                if (splitError.length > 1 || splitError[0].indexOf("Building the local index for the first time") === -1) {
                    _this.$errors.failWithoutHelp(spawnResult.stderr);
                }
            }
            var pluginsRows = spawnResult.stdout.split("\n");
            pluginsRows.shift();
            var npmNameGroup = "(\\S+)";
            var npmDateGroup = "(\\d+-\\d+-\\d+)\\s";
            var npmFreeTextGroup = "([^=]+)";
            var npmAuthorsGroup = "((?:=\\S+\\s?)+)\\s+";
            var pluginRowRegExp = new RegExp(npmNameGroup + "\\s+" + npmFreeTextGroup + npmAuthorsGroup + npmDateGroup + npmNameGroup + "(\\s+" + npmFreeTextGroup + ")?");
            _.each(pluginsRows, function (pluginRow) {
                var matches = pluginRowRegExp.exec(pluginRow.trim());
                if (!matches || !matches[0]) {
                    return;
                }
                result.push({
                    name: matches[1],
                    description: matches[2],
                    author: matches[3],
                    version: matches[5]
                });
            });
            return result;
        }).future()();
    };
    NpmService.prototype.getPackageJsonFromNpmRegistry = function (packageName, version) {
        var _this = this;
        return (function () {
            var packageJsonContent;
            version = version || "latest";
            try {
                var url = _this.buildNpmRegistryUrl(packageName, version);
                var result = _this.$httpClient.httpRequest(url).wait().body;
                packageJsonContent = JSON.parse(result);
            }
            catch (err) {
                _this.$logger.trace("Error caught while checking the NPM Registry for plugin with id: %s", packageName);
                _this.$logger.trace(err.message);
            }
            return packageJsonContent;
        }).future()();
    };
    NpmService.prototype.isScopedDependency = function (dependency) {
        var matches = dependency.match(NpmService.SCOPED_DEPENDENCY_REGEXP);
        return !!(matches && matches[0]);
    };
    NpmService.prototype.getDependencyInformation = function (dependency) {
        var regExp = this.isScopedDependency(dependency) ? NpmService.SCOPED_DEPENDENCY_REGEXP : NpmService.DEPENDENCY_REGEXP;
        var matches = dependency.match(regExp);
        return {
            name: matches[1],
            version: matches[2]
        };
    };
    NpmService.prototype.hasTypesForDependency = function (packageName) {
        var _this = this;
        return (function () {
            return !!_this.getPackageJsonFromNpmRegistry("" + NpmService.TYPES_DIRECTORY + packageName).wait();
        }).future()();
    };
    NpmService.prototype.buildNpmRegistryUrl = function (packageName, version) {
        return NpmService.NPM_REGISTRY_URL + "/" + packageName.replace("/", "%2F") + "?version=" + encodeURIComponent(version);
    };
    NpmService.prototype.getPackageJsonContent = function (projectDir) {
        var _this = this;
        return (function () {
            var pathToPackageJson = _this.getPathToPackageJson(projectDir);
            try {
                return _this.$fs.readJson(pathToPackageJson).wait();
            }
            catch (err) {
                if (err.code === "ENOENT") {
                    _this.$errors.failWithoutHelp("Unable to find " + _this.$projectConstants.PACKAGE_JSON_NAME + " in " + projectDir + ".");
                }
                throw err;
            }
        }).future()();
    };
    NpmService.prototype.getPathToPackageJson = function (projectDir) {
        return path.join(projectDir, this.$projectConstants.PACKAGE_JSON_NAME);
    };
    NpmService.prototype.getPathToReferencesFile = function (projectDir) {
        return path.join(projectDir, this.$projectConstants.REFERENCES_FILE_NAME);
    };
    NpmService.prototype.installTypingsForDependency = function (projectDir, dependency) {
        return this.npmInstall(projectDir, "" + NpmService.TYPES_DIRECTORY + dependency, null, ["--save-dev", "--save-exact"]);
    };
    NpmService.prototype.generateReferencesFile = function (projectDir) {
        var _this = this;
        return (function () {
            var packageJsonContent = _this.getPackageJsonContent(projectDir).wait();
            var pathToReferenceFile = _this.getPathToReferencesFile(projectDir), lines = [];
            if (packageJsonContent && packageJsonContent.dependencies && packageJsonContent.dependencies[constants.TNS_CORE_MODULES]) {
                var relativePathToTnsCoreModulesDts = "./" + constants.NODE_MODULES_DIR_NAME + "/" + constants.TNS_CORE_MODULES + "/" + NpmService.TNS_CORE_MODULES_DEFINITION_FILE_NAME;
                if (_this.$fs.exists(path.join(projectDir, relativePathToTnsCoreModulesDts)).wait()) {
                    lines.push(_this.getReferenceLine(relativePathToTnsCoreModulesDts));
                }
            }
            _(packageJsonContent.devDependencies)
                .keys()
                .each(function (devDependency) {
                if (_this.isFromTypesRepo(devDependency)) {
                    var nodeModulesDirectory = path.join(projectDir, constants.NODE_MODULES_DIR_NAME);
                    var definitionFiles = _this.$fs.enumerateFilesInDirectorySync(path.join(nodeModulesDirectory, devDependency), function (file, stat) { return _.endsWith(file, constants.FileExtensions.TYPESCRIPT_DEFINITION_FILE) || stat.isDirectory(); }, { enumerateDirectories: false });
                    var defs = _.map(definitionFiles, function (def) { return _this.getReferenceLine(helpers_1.fromWindowsRelativePathToUnix(path.relative(projectDir, def))); });
                    _this.$logger.trace("Adding lines for definition files: " + definitionFiles.join(", "));
                    lines.push.apply(lines, defs);
                }
            });
            if (lines.length) {
                _this.$logger.trace("Updating reference file with new entries...");
                _this.$fs.writeFile(pathToReferenceFile, lines.join(os.EOL), "utf8").wait();
            }
            else {
                _this.$logger.trace("Could not find any .d.ts files for " + _this.$projectConstants.REFERENCES_FILE_NAME + " file. Deleting the old file.");
                _this.$fs.deleteFile(pathToReferenceFile).wait();
            }
        }).future()();
    };
    NpmService.prototype.isFromTypesRepo = function (dependency) {
        return !!dependency.match(/^@types\//);
    };
    NpmService.prototype.getReferenceLine = function (pathToReferencedFile) {
        return "/// <reference path=\"" + pathToReferencedFile + "\" />";
    };
    NpmService.prototype.getNpmArguments = function (command, npmArguments) {
        if (npmArguments === void 0) { npmArguments = []; }
        return npmArguments.concat([command]);
    };
    NpmService.prototype.npmInstall = function (projectDir, dependency, version, npmArguments) {
        return this.executeNpmCommand(projectDir, this.getNpmArguments("install", npmArguments), dependency, version);
    };
    NpmService.prototype.npmUninstall = function (projectDir, dependency, npmArguments) {
        return this.executeNpmCommand(projectDir, this.getNpmArguments("uninstall", npmArguments), dependency, null);
    };
    NpmService.prototype.npmPrune = function (projectDir, dependency, version) {
        return this.executeNpmCommand(projectDir, this.getNpmArguments("prune"), dependency, version);
    };
    NpmService.prototype.executeNpmCommand = function (projectDir, npmArguments, dependency, version) {
        var _this = this;
        return (function () {
            if (dependency) {
                var dependencyToInstall = dependency;
                if (version) {
                    dependencyToInstall += "@" + version;
                }
                npmArguments.push(dependencyToInstall);
            }
            return _this.executeNpmCommandCore(projectDir, npmArguments).wait();
        }).future()();
    };
    NpmService.prototype.executeNpmCommandCore = function (projectDir, npmArguments) {
        return this.$childProcess.spawnFromEvent(this.npmExecutableName, npmArguments, "close", { cwd: projectDir });
    };
    NpmService.NPM_MODULE_NAME = "npm";
    NpmService.TYPES_DIRECTORY = "@types/";
    NpmService.TNS_CORE_MODULES_DEFINITION_FILE_NAME = "" + constants.TNS_CORE_MODULES + constants.FileExtensions.TYPESCRIPT_DEFINITION_FILE;
    NpmService.NPM_REGISTRY_URL = "https://registry.npmjs.org";
    NpmService.SCOPED_DEPENDENCY_REGEXP = /^(@.+?)(?:@(.+?))?$/;
    NpmService.DEPENDENCY_REGEXP = /^(.+?)(?:@(.+?))?$/;
    __decorate([
        decorators_1.exportedPromise("npmService")
    ], NpmService.prototype, "install", null);
    __decorate([
        decorators_1.exportedPromise("npmService")
    ], NpmService.prototype, "uninstall", null);
    return NpmService;
}());
exports.NpmService = NpmService;
$injector.register("npmService", NpmService);
