"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 __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var path = require("path");
var os = require("os");
var temp = require("temp");
var decorators_1 = require("../decorators");
var constants_1 = require("../constants");
temp.track();
var TypeScriptService = (function () {
    function TypeScriptService($childProcess, $fs, $logger, $npmService, $options, $projectConstants, $processService, $errors) {
        this.$childProcess = $childProcess;
        this.$fs = $fs;
        this.$logger = $logger;
        this.$npmService = $npmService;
        this.$options = $options;
        this.$projectConstants = $projectConstants;
        this.$processService = $processService;
        this.$errors = $errors;
    }
    TypeScriptService.prototype.transpile = function (projectDir, typeScriptFiles, definitionFiles, options) {
        var _this = this;
        return (function () {
            options = options || {};
            var compilerOptions = _this.getCompilerOptions(projectDir, options).wait();
            var typeScriptCompilerSettings = _this.getTypeScriptCompilerSettings({ useLocalTypeScriptCompiler: options.useLocalTypeScriptCompiler }).wait();
            _this.noEmitOnError = compilerOptions.noEmitOnError;
            _this.typeScriptFiles = typeScriptFiles || [];
            _this.definitionFiles = definitionFiles || [];
            var runTranspilationOptions = { compilerOptions: compilerOptions };
            if (_this.typeScriptFiles.length > 0) {
                var typeScriptDefinitionsFiles = [];
                if (!_this.hasTsConfigFile(projectDir).wait()) {
                    typeScriptDefinitionsFiles = _this.getDefaultTypeScriptDefinitionsFiles(options.pathToDefaultDefinitionFiles).wait();
                }
                typeScriptDefinitionsFiles = typeScriptDefinitionsFiles.concat(_this.getTypeScriptFilesData(projectDir).wait().definitionFiles);
                var filesToTranspile = _this.typeScriptFiles.concat(typeScriptDefinitionsFiles);
                _this.$logger.out("Compiling...".yellow);
                _.each(_this.typeScriptFiles, function (file) {
                    _this.$logger.out(("### Compile " + file).cyan);
                });
                runTranspilationOptions.filesToTranspile = filesToTranspile;
            }
            _this.$logger.out(("Using tsc version " + typeScriptCompilerSettings.version).cyan);
            return _this.runTranspilation(projectDir, typeScriptCompilerSettings.pathToCompiler, runTranspilationOptions).wait();
        }).future()();
    };
    TypeScriptService.prototype.getTypeScriptFilesData = function (projectDir) {
        var _this = this;
        return (function () {
            var rootNodeModules = path.join(projectDir, constants_1.NODE_MODULES_DIR_NAME);
            var projectFiles = _this.$fs.enumerateFilesInDirectorySync(projectDir, function (fileName, fstat) { return fileName !== rootNodeModules; });
            var typeScriptFiles = _.filter(projectFiles, _this.isTypeScriptFile);
            var definitionFiles = _.filter(typeScriptFiles, function (file) { return _.endsWith(file, constants_1.FileExtensions.TYPESCRIPT_DEFINITION_FILE); });
            return { definitionFiles: definitionFiles, typeScriptFiles: _.difference(typeScriptFiles, definitionFiles) };
        }).future()();
    };
    TypeScriptService.prototype.isTypeScriptProject = function (projectDir) {
        var _this = this;
        return (function () {
            var typeScriptFilesData = _this.getTypeScriptFilesData(projectDir).wait();
            return !!typeScriptFilesData.typeScriptFiles.length;
        }).future()();
    };
    TypeScriptService.prototype.isTypeScriptFile = function (file) {
        return path.extname(file) === constants_1.FileExtensions.TYPESCRIPT_FILE;
    };
    TypeScriptService.prototype.hasTsConfigFile = function (projectDir) {
        return this.$fs.exists(this.getPathToTsConfigFile(projectDir));
    };
    TypeScriptService.prototype.getPathToTsConfigFile = function (projectDir) {
        return path.join(projectDir, this.$projectConstants.TSCONFIG_JSON_NAME);
    };
    TypeScriptService.prototype.getCompilerOptions = function (projectDir, options) {
        var _this = this;
        return (function () {
            var tsConfigFile;
            var pathToConfigJsonFile = _this.getPathToTsConfigFile(projectDir);
            if (_this.hasTsConfigFile(projectDir).wait()) {
                tsConfigFile = _this.$fs.readJson(pathToConfigJsonFile).wait();
            }
            tsConfigFile = tsConfigFile || { compilerOptions: {} };
            var compilerOptions = options.compilerOptions || {};
            var defaultOptions = options.defaultCompilerOptions || {};
            var compilerOptionsKeys = _.union(_.keys(compilerOptions), _.keys(tsConfigFile.compilerOptions), _.keys(defaultOptions));
            var result = {};
            _.each(compilerOptionsKeys, function (key) {
                result[key] = _this.getCompilerOptionByKey(key, compilerOptions, tsConfigFile.compilerOptions, defaultOptions);
            });
            result.noEmitOnError = result.noEmitOnError || false;
            return result;
        }).future()();
    };
    TypeScriptService.prototype.getCompilerOptionByKey = function (key, compilerOptions, tsConfigFileOptions, defaultOptions) {
        if (_.has(compilerOptions, key)) {
            return compilerOptions[key];
        }
        if (_.has(tsConfigFileOptions, key)) {
            return tsConfigFileOptions[key];
        }
        return defaultOptions[key];
    };
    TypeScriptService.prototype.getTypeScriptCompilerSettings = function (options) {
        var _this = this;
        return (function () {
            var typeScriptInNodeModulesDir = path.join(constants_1.NODE_MODULES_DIR_NAME, TypeScriptService.TYPESCRIPT_MODULE_NAME);
            if (!_this.typeScriptModuleFilePath) {
                if (options.useLocalTypeScriptCompiler) {
                    var typeScriptJsFilePath = require.resolve(TypeScriptService.TYPESCRIPT_MODULE_NAME);
                    _this.typeScriptModuleFilePath = typeScriptJsFilePath.substring(0, typeScriptJsFilePath.indexOf(typeScriptInNodeModulesDir) + typeScriptInNodeModulesDir.length);
                }
                else {
                    var typeScriptModuleInstallationDir = _this.createTempDirectoryForTsc().wait();
                    var pluginToInstall = {
                        name: TypeScriptService.TYPESCRIPT_MODULE_NAME,
                        version: TypeScriptService.DEFAULT_TSC_VERSION,
                        installTypes: false
                    };
                    _this.$npmService.install(typeScriptModuleInstallationDir, pluginToInstall).wait();
                    _this.typeScriptModuleFilePath = path.join(typeScriptModuleInstallationDir, typeScriptInNodeModulesDir);
                }
            }
            var typeScriptCompilerPath = path.join(_this.typeScriptModuleFilePath, "lib", "tsc");
            var typeScriptCompilerVersion = _this.$fs.readJson(path.join(_this.typeScriptModuleFilePath, _this.$projectConstants.PACKAGE_JSON_NAME)).wait().version;
            return { pathToCompiler: typeScriptCompilerPath, version: typeScriptCompilerVersion };
        }).future()();
    };
    TypeScriptService.prototype.runTranspilation = function (projectDir, typeScriptCompilerPath, options) {
        var _this = this;
        return (function () {
            options = options || {};
            var startTime = new Date().getTime();
            var params = _([])
                .concat(typeScriptCompilerPath)
                .concat(options.filesToTranspile || [])
                .concat(_this.getTypeScriptCompilerOptionsAsArguments(options.compilerOptions) || [])
                .value();
            var output = _this.$childProcess.spawnFromEvent(process.argv[0], params, "close", { cwd: projectDir }, { throwError: false }).wait();
            var compilerOutput = output.stderr || output.stdout;
            var compilerMessages = _this.getCompilerMessages(compilerOutput);
            _this.logCompilerMessages(compilerMessages, compilerOutput);
            var endTime = new Date().getTime();
            var time = (endTime - startTime) / 1000;
            _this.$logger.out((os.EOL + "Success: " + time.toFixed(2) + "s" + os.EOL + ".").green);
            _this.startWatchProcess(params, projectDir);
            return compilerOutput;
        }).future()();
    };
    TypeScriptService.prototype.startWatchProcess = function (params, projectDir) {
        if (!this._watchProcess && this.$options.watch) {
            params.push("--watch");
            this._watchProcess = this.$childProcess.spawn(process.argv[0], params, { cwd: projectDir });
            this.$processService.attachToProcessExitSignals(null, this._watchProcess.kill);
        }
    };
    TypeScriptService.prototype.getCompilerMessages = function (compilerOutput) {
        var _this = this;
        var level1ErrorCount = 0, level5ErrorCount = 0, nonEmitPreventingWarningCount = 0;
        var hasPreventEmitErrors = _.reduce(compilerOutput.split("\n"), function (memo, errorMsg) {
            var isPreventEmitError = !!_this.noEmitOnError;
            if (errorMsg.search(/error TS1\d+:/) >= 0) {
                level1ErrorCount += 1;
            }
            else if (errorMsg.search(/error TS5\d+:/) >= 0) {
                level5ErrorCount += 1;
            }
            else if (errorMsg.search(/error TS\d+:/) >= 0) {
                nonEmitPreventingWarningCount += 1;
            }
            return memo || isPreventEmitError;
        }, false) || false;
        return {
            level1ErrorCount: level1ErrorCount,
            level5ErrorCount: level5ErrorCount,
            nonEmitPreventingWarningCount: nonEmitPreventingWarningCount,
            hasPreventEmitErrors: hasPreventEmitErrors
        };
    };
    TypeScriptService.prototype.logCompilerMessages = function (compilerMessages, errorMessage) {
        var level1ErrorCount = compilerMessages.level1ErrorCount, level5ErrorCount = compilerMessages.level5ErrorCount, nonEmitPreventingWarningCount = compilerMessages.nonEmitPreventingWarningCount, hasPreventEmitErrors = compilerMessages.hasPreventEmitErrors;
        if (level1ErrorCount + level5ErrorCount + nonEmitPreventingWarningCount > 0) {
            var colorizedMessage = (level1ErrorCount + level5ErrorCount > 0) ? ">>>".red : ">>>".green;
            this.$logger.out(colorizedMessage);
            var errorTitle = "";
            if (level5ErrorCount > 0) {
                errorTitle += this.composeErrorTitle(level5ErrorCount, "compiler flag error");
            }
            if (level1ErrorCount > 0) {
                errorTitle += this.composeErrorTitle(level1ErrorCount, "syntax error");
            }
            if (nonEmitPreventingWarningCount > 0) {
                if (!level1ErrorCount && !level5ErrorCount && this.noEmitOnError) {
                    errorTitle += this.composeErrorTitle(nonEmitPreventingWarningCount, "non-emit-preventing type errors, but output is not generated as noEmitOnError option is true.");
                }
                else {
                    errorTitle += this.composeErrorTitle(nonEmitPreventingWarningCount, "non-emit-preventing type warning");
                }
            }
            if (hasPreventEmitErrors) {
                this.$errors.failWithoutHelp("" + os.EOL + errorTitle + os.EOL + errorMessage.red + os.EOL + ">>> ".red);
            }
            else {
                this.$logger.out(errorTitle);
                this.$logger.warn(errorMessage);
                this.$logger.out(">>>".green);
            }
        }
    };
    TypeScriptService.prototype.composeErrorTitle = function (count, title) {
        return count + " " + title + ((count === 1) ? '' : 's') + " " + os.EOL;
    };
    TypeScriptService.prototype.getTypeScriptCompilerOptionsAsArguments = function (options) {
        this.noEmitOnError = options.noEmitOnError;
        return _(options)
            .keys()
            .map(function (option) {
            var value = options[option];
            if (typeof (value) === "string") {
                return [("--" + option), value];
            }
            else if (value) {
                return [("--" + option)];
            }
            else {
                return null;
            }
        })
            .filter(function (argument) { return !!argument; })
            .flatten()
            .value();
    };
    TypeScriptService.prototype.getDefaultTypeScriptDefinitionsFiles = function (defaultTypeScriptDefinitionsFilesPath) {
        var _this = this;
        return (function () {
            if (!_this.$fs.exists(defaultTypeScriptDefinitionsFilesPath).wait()) {
                return [];
            }
            var defaultDefinitionsFiles = _this.$fs.readDirectory(defaultTypeScriptDefinitionsFilesPath).wait();
            var remainingDefaultDefinitionFiles = _.filter(defaultDefinitionsFiles, function (defFile) { return !_.some(_this.definitionFiles, function (f) { return path.basename(f) === defFile; }); });
            return _.map(remainingDefaultDefinitionFiles, function (definitionFilePath) {
                return path.join(defaultTypeScriptDefinitionsFilesPath, definitionFilePath);
            }).concat(_this.definitionFiles);
        }).future()();
    };
    TypeScriptService.prototype.createTempDirectoryForTsc = function () {
        var _this = this;
        return (function () {
            var tempDir = temp.mkdirSync("typescript-compiler-" + TypeScriptService.DEFAULT_TSC_VERSION);
            _this.$fs.writeJson(path.join(tempDir, _this.$projectConstants.PACKAGE_JSON_NAME), { name: "tsc-container", version: "1.0.0" }).wait();
            return tempDir;
        }).future()();
    };
    TypeScriptService.DEFAULT_TSC_VERSION = "1.8.10";
    TypeScriptService.TYPESCRIPT_MODULE_NAME = "typescript";
    __decorate([
        decorators_1.exportedPromise("typeScriptService"), 
        __metadata('design:type', Function), 
        __metadata('design:paramtypes', [String, Array, Array, Object]), 
        __metadata('design:returntype', Object)
    ], TypeScriptService.prototype, "transpile", null);
    return TypeScriptService;
}());
exports.TypeScriptService = TypeScriptService;
$injector.register("typeScriptService", TypeScriptService);
