/**
 * @license
 * Copyright 2013 Palantir Technologies, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var fs = require("fs");
var glob = require("glob");
var path = require("path");
var ts = require("typescript");
var configuration_1 = require("./configuration");
var error_1 = require("./error");
var Linter = require("./linter");
var test_1 = require("./test");
var Runner = (function () {
    function Runner(options, outputStream) {
        this.options = options;
        this.outputStream = outputStream;
    }
    Runner.trimSingleQuotes = function (str) {
        return str.replace(/^'|'$/g, "");
    };
    Runner.prototype.run = function (onComplete) {
        if (this.options.version) {
            this.outputStream.write(Linter.VERSION + "\n");
            onComplete(0);
            return;
        }
        if (this.options.init) {
            if (fs.existsSync(configuration_1.CONFIG_FILENAME)) {
                console.error("Cannot generate " + configuration_1.CONFIG_FILENAME + ": file already exists");
                onComplete(1);
                return;
            }
            var tslintJSON = JSON.stringify(configuration_1.DEFAULT_CONFIG, undefined, "    ");
            fs.writeFileSync(configuration_1.CONFIG_FILENAME, tslintJSON);
            onComplete(0);
            return;
        }
        if (this.options.test) {
            var results = test_1.runTests(this.options.test, this.options.rulesDirectory);
            var didAllTestsPass = test_1.consoleTestResultsHandler(results);
            onComplete(didAllTestsPass ? 0 : 1);
            return;
        }
        // when provided, it should point to an existing location
        if (this.options.config && !fs.existsSync(this.options.config)) {
            console.error("Invalid option for configuration: " + this.options.config);
            onComplete(1);
            return;
        }
        // if both files and tsconfig are present, use files
        var files = this.options.files === undefined ? [] : this.options.files;
        var program;
        if (this.options.project != null) {
            if (!fs.existsSync(this.options.project)) {
                console.error("Invalid option for project: " + this.options.project);
                onComplete(1);
                return;
            }
            program = Linter.createProgram(this.options.project, path.dirname(this.options.project));
            if (files.length === 0) {
                files = Linter.getFileNames(program);
            }
            if (this.options.typeCheck) {
                // if type checking, run the type checker
                var diagnostics = ts.getPreEmitDiagnostics(program);
                if (diagnostics.length > 0) {
                    var messages = diagnostics.map(function (diag) {
                        // emit any error messages
                        var message = ts.DiagnosticCategory[diag.category];
                        if (diag.file) {
                            var _a = diag.file.getLineAndCharacterOfPosition(diag.start), line = _a.line, character = _a.character;
                            message += " at " + diag.file.fileName + ":" + (line + 1) + ":" + (character + 1) + ":";
                        }
                        message += " " + ts.flattenDiagnosticMessageText(diag.messageText, "\n");
                        return message;
                    });
                    throw new Error(messages.join("\n"));
                }
            }
            else {
                // if not type checking, we don't need to pass in a program object
                program = undefined;
            }
        }
        var ignorePatterns = [];
        if (this.options.exclude) {
            var excludeArguments = Array.isArray(this.options.exclude) ? this.options.exclude : [this.options.exclude];
            ignorePatterns = excludeArguments.map(Runner.trimSingleQuotes);
        }
        files = files
            .map(Runner.trimSingleQuotes)
            .map(function (file) { return glob.sync(file, { ignore: ignorePatterns, nodir: true }); })
            .reduce(function (a, b) { return a.concat(b); }, []);
        try {
            this.processFiles(onComplete, files, program);
        }
        catch (error) {
            if (error.name === error_1.FatalError.NAME) {
                console.error(error.message);
                onComplete(1);
            }
            // rethrow unhandled error
            throw error;
        }
    };
    Runner.prototype.processFiles = function (onComplete, files, program) {
        var _this = this;
        var possibleConfigAbsolutePath = this.options.config != null ? path.resolve(this.options.config) : null;
        var linter = new Linter({
            fix: !!this.options.fix,
            formatter: this.options.format,
            formattersDirectory: this.options.formattersDirectory || "",
            rulesDirectory: this.options.rulesDirectory || "",
        }, program);
        var lastFolder;
        var configFile;
        for (var _i = 0, files_1 = files; _i < files_1.length; _i++) {
            var file = files_1[_i];
            if (!fs.existsSync(file)) {
                console.error("Unable to open file: " + file);
                onComplete(1);
                return;
            }
            var buffer = new Buffer(256);
            buffer.fill(0);
            var fd = fs.openSync(file, "r");
            try {
                fs.readSync(fd, buffer, 0, 256, 0);
                if (buffer.readInt8(0) === 0x47 && buffer.readInt8(188) === 0x47) {
                    // MPEG transport streams use the '.ts' file extension. They use 0x47 as the frame
                    // separator, repeating every 188 bytes. It is unlikely to find that pattern in
                    // TypeScript source, so tslint ignores files with the specific pattern.
                    console.warn(file + ": ignoring MPEG transport stream");
                    return;
                }
            }
            finally {
                fs.closeSync(fd);
            }
            var contents = fs.readFileSync(file, "utf8");
            var folder = path.dirname(file);
            if (lastFolder !== folder) {
                configFile = configuration_1.findConfiguration(possibleConfigAbsolutePath, folder).results;
                lastFolder = folder;
            }
            linter.lint(file, contents, configFile);
        }
        var lintResult = linter.getResult();
        this.outputStream.write(lintResult.output, function () {
            if (_this.options.force || lintResult.errorCount === 0) {
                onComplete(0);
            }
            else {
                onComplete(2);
            }
        });
    };
    return Runner;
}());
exports.Runner = Runner;
