/* --------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 * ------------------------------------------------------------------------------------------ */
'use strict';
var vscode_jsonrpc_1 = require('vscode-jsonrpc');
exports.ResponseError = vscode_jsonrpc_1.ResponseError;
exports.ErrorCodes = vscode_jsonrpc_1.ErrorCodes;
exports.StreamMessageReader = vscode_jsonrpc_1.StreamMessageReader;
exports.IPCMessageReader = vscode_jsonrpc_1.IPCMessageReader;
exports.StreamMessageWriter = vscode_jsonrpc_1.StreamMessageWriter;
exports.IPCMessageWriter = vscode_jsonrpc_1.IPCMessageWriter;
exports.Event = vscode_jsonrpc_1.Event;
var vscode_languageserver_types_1 = require('vscode-languageserver-types');
exports.TextDocument = vscode_languageserver_types_1.TextDocument;
exports.Range = vscode_languageserver_types_1.Range;
exports.Position = vscode_languageserver_types_1.Position;
exports.Location = vscode_languageserver_types_1.Location;
exports.Diagnostic = vscode_languageserver_types_1.Diagnostic;
exports.Command = vscode_languageserver_types_1.Command;
exports.TextEdit = vscode_languageserver_types_1.TextEdit;
exports.WorkspaceChange = vscode_languageserver_types_1.WorkspaceChange;
exports.TextDocumentIdentifier = vscode_languageserver_types_1.TextDocumentIdentifier;
exports.CompletionItem = vscode_languageserver_types_1.CompletionItem;
exports.CompletionList = vscode_languageserver_types_1.CompletionList;
exports.MarkedString = vscode_languageserver_types_1.MarkedString;
exports.SignatureInformation = vscode_languageserver_types_1.SignatureInformation;
exports.ParameterInformation = vscode_languageserver_types_1.ParameterInformation;
exports.CodeActionContext = vscode_languageserver_types_1.CodeActionContext;
exports.DocumentHighlight = vscode_languageserver_types_1.DocumentHighlight;
exports.SymbolInformation = vscode_languageserver_types_1.SymbolInformation;
exports.CodeLens = vscode_languageserver_types_1.CodeLens;
exports.FormattingOptions = vscode_languageserver_types_1.FormattingOptions;
exports.DocumentLink = vscode_languageserver_types_1.DocumentLink;
var protocol_1 = require('./protocol');
exports.FileChangeType = protocol_1.FileChangeType;
exports.TextDocumentSyncKind = protocol_1.TextDocumentSyncKind;
exports.CodeLensRequest = protocol_1.CodeLensRequest;
exports.CodeLensResolveRequest = protocol_1.CodeLensResolveRequest;
exports.DocumentFormattingRequest = protocol_1.DocumentFormattingRequest;
exports.DocumentRangeFormattingRequest = protocol_1.DocumentRangeFormattingRequest;
exports.DocumentOnTypeFormattingRequest = protocol_1.DocumentOnTypeFormattingRequest;
exports.RenameRequest = protocol_1.RenameRequest;
exports.DocumentLinkRequest = protocol_1.DocumentLinkRequest;
exports.DocumentLinkResolveRequest = protocol_1.DocumentLinkResolveRequest;
var Is = require('./utils/is');
// ------------- Reexport the API surface of the language worker API ----------------------
var fm = require('./files');
var net = require('net');
var stream = require('stream');
var Files;
(function (Files) {
    Files.uriToFilePath = fm.uriToFilePath;
    Files.resolveGlobalNodePath = fm.resolveGlobalNodePath;
    Files.resolve = fm.resolve;
    Files.resolveModule = fm.resolveModule;
    Files.resolveModule2 = fm.resolveModule2;
    Files.resolveModulePath = fm.resolveModulePath;
})(Files = exports.Files || (exports.Files = {}));
/**
 * A manager for simple text documents
 */
var TextDocuments = (function () {
    /**
     * Create a new text document manager.
     */
    function TextDocuments() {
        this._documents = Object.create(null);
        this._onDidChangeContent = new vscode_jsonrpc_1.Emitter();
        this._onDidOpen = new vscode_jsonrpc_1.Emitter();
        this._onDidClose = new vscode_jsonrpc_1.Emitter();
        this._onDidSave = new vscode_jsonrpc_1.Emitter();
    }
    Object.defineProperty(TextDocuments.prototype, "syncKind", {
        /**
         * Returns the [TextDocumentSyncKind](#TextDocumentSyncKind) used by
         * this text document manager.
         */
        get: function () {
            return protocol_1.TextDocumentSyncKind.Full;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(TextDocuments.prototype, "onDidChangeContent", {
        /**
         * An event that fires when a text document managed by this manager
         * has been opened or the content changes.
         */
        get: function () {
            return this._onDidChangeContent.event;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(TextDocuments.prototype, "onDidOpen", {
        /**
         * An event that fires when a text document managed by this manager
         * has been opened.
         */
        get: function () {
            return this._onDidOpen.event;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(TextDocuments.prototype, "onDidClose", {
        /**
         * An event that fires when a text document managed by this manager
         * has been closed.
         */
        get: function () {
            return this._onDidClose.event;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(TextDocuments.prototype, "onDidSave", {
        /**
         * An event that fires when a text document managed by this manager
         * has been closed.
         */
        get: function () {
            return this._onDidSave.event;
        },
        enumerable: true,
        configurable: true
    });
    /**
     * Returns the document for the given URI. Returns undefined if
     * the document is not mananged by this instance.
     *
     * @param uri The text document's URI to retrieve.
     * @return the text document or `undefined`.
     */
    TextDocuments.prototype.get = function (uri) {
        return this._documents[uri];
    };
    /**
     * Returns all text documents managed by this instance.
     *
     * @return all text documents.
     */
    TextDocuments.prototype.all = function () {
        var _this = this;
        return Object.keys(this._documents).map(function (key) { return _this._documents[key]; });
    };
    /**
     * Returns the URIs of all text documents managed by this instance.
     *
     * @return the URI's of all text documents.
     */
    TextDocuments.prototype.keys = function () {
        return Object.keys(this._documents);
    };
    /**
     * Listens for `low level` notification on the given connection to
     * update the text documents managed by this instance.
     *
     * @param connection The connection to listen on.
     */
    TextDocuments.prototype.listen = function (connection) {
        var _this = this;
        connection.__textDocumentSync = protocol_1.TextDocumentSyncKind.Full;
        connection.onDidOpenTextDocument(function (event) {
            var td = event.textDocument;
            var document = vscode_languageserver_types_1.TextDocument.create(td.uri, td.languageId, td.version, td.text);
            _this._documents[td.uri] = document;
            _this._onDidOpen.fire({ document: document });
            _this._onDidChangeContent.fire({ document: document });
        });
        connection.onDidChangeTextDocument(function (event) {
            var td = event.textDocument;
            var changes = event.contentChanges;
            var last = changes.length > 0 ? changes[changes.length - 1] : null;
            if (last) {
                var document_1 = _this._documents[td.uri];
                if (document_1 && Is.func(document_1['update'])) {
                    document_1.update(last, td.version);
                    _this._onDidChangeContent.fire({ document: document_1 });
                }
            }
        });
        connection.onDidCloseTextDocument(function (event) {
            var document = _this._documents[event.textDocument.uri];
            if (document) {
                delete _this._documents[event.textDocument.uri];
                _this._onDidClose.fire({ document: document });
            }
        });
        connection.onDidSaveTextDocument(function (event) {
            var document = _this._documents[event.textDocument.uri];
            if (document) {
                _this._onDidSave.fire({ document: document });
            }
        });
    };
    return TextDocuments;
}());
exports.TextDocuments = TextDocuments;
// ------------------------- implementation of the language server protocol ---------------------------------------------
/**
 * Helps tracking error message. Equal occurences of the same
 * message are only stored once. This class is for example
 * usefull if text documents are validated in a loop and equal
 * error message should be folded into one.
 */
var ErrorMessageTracker = (function () {
    function ErrorMessageTracker() {
        this.messages = Object.create(null);
    }
    /**
     * Add a message to the tracker.
     *
     * @param message The message to add.
     */
    ErrorMessageTracker.prototype.add = function (message) {
        var count = this.messages[message];
        if (!count) {
            count = 0;
        }
        count++;
        this.messages[message] = count;
    };
    /**
     * Send all tracked messages to the conenction's window.
     *
     * @param connection The connection establised between client and server.
     */
    ErrorMessageTracker.prototype.sendErrors = function (connection) {
        Object.keys(this.messages).forEach(function (message) {
            connection.window.showErrorMessage(message);
        });
    };
    return ErrorMessageTracker;
}());
exports.ErrorMessageTracker = ErrorMessageTracker;
var ConnectionLogger = (function () {
    function ConnectionLogger() {
    }
    ConnectionLogger.prototype.attach = function (connection) {
        this.connection = connection;
    };
    ConnectionLogger.prototype.error = function (message) {
        this.send(protocol_1.MessageType.Error, message);
    };
    ConnectionLogger.prototype.warn = function (message) {
        this.send(protocol_1.MessageType.Warning, message);
    };
    ConnectionLogger.prototype.info = function (message) {
        this.send(protocol_1.MessageType.Info, message);
    };
    ConnectionLogger.prototype.log = function (message) {
        this.send(protocol_1.MessageType.Log, message);
    };
    ConnectionLogger.prototype.send = function (type, message) {
        if (this.connection) {
            this.connection.sendNotification(protocol_1.LogMessageNotification.type, { type: type, message: message });
        }
    };
    return ConnectionLogger;
}());
var RemoteWindowImpl = (function () {
    function RemoteWindowImpl(connection) {
        this.connection = connection;
    }
    RemoteWindowImpl.prototype.showErrorMessage = function (message) {
        var actions = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            actions[_i - 1] = arguments[_i];
        }
        return this.connection.sendRequest(protocol_1.ShowMessageRequest.type, { type: protocol_1.MessageType.Error, message: message, actions: actions });
    };
    RemoteWindowImpl.prototype.showWarningMessage = function (message) {
        var actions = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            actions[_i - 1] = arguments[_i];
        }
        return this.connection.sendRequest(protocol_1.ShowMessageRequest.type, { type: protocol_1.MessageType.Warning, message: message, actions: actions });
    };
    RemoteWindowImpl.prototype.showInformationMessage = function (message) {
        var actions = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            actions[_i - 1] = arguments[_i];
        }
        return this.connection.sendRequest(protocol_1.ShowMessageRequest.type, { type: protocol_1.MessageType.Info, message: message, actions: actions });
    };
    return RemoteWindowImpl;
}());
var TracerImpl = (function () {
    function TracerImpl(connection) {
        this.connection = connection;
        this._trace = vscode_jsonrpc_1.Trace.Off;
    }
    Object.defineProperty(TracerImpl.prototype, "trace", {
        set: function (value) {
            this._trace = value;
        },
        enumerable: true,
        configurable: true
    });
    TracerImpl.prototype.log = function (message, verbose) {
        if (this._trace === vscode_jsonrpc_1.Trace.Off) {
            return;
        }
        this.connection.sendNotification(vscode_jsonrpc_1.LogTraceNotification.type, {
            message: message,
            verbose: this._trace === vscode_jsonrpc_1.Trace.Verbose ? verbose : null
        });
    };
    return TracerImpl;
}());
var TelemetryImpl = (function () {
    function TelemetryImpl(connection) {
        this.connection = connection;
    }
    TelemetryImpl.prototype.logEvent = function (data) {
        this.connection.sendNotification(protocol_1.TelemetryEventNotification.type, data);
    };
    return TelemetryImpl;
}());
function createConnection(input, output) {
    if (!input && !output && process.argv.length > 2) {
        var port = void 0;
        var argv = process.argv.slice(2);
        for (var i = 0; i < argv.length; i++) {
            var arg = argv[i];
            if (arg === '--node-ipc') {
                input = new vscode_jsonrpc_1.IPCMessageReader(process);
                output = new vscode_jsonrpc_1.IPCMessageWriter(process);
            }
            else if (arg === '--stdio') {
                input = process.stdin;
                output = process.stdout;
            }
            else if (arg === '--socket') {
                port = parseInt(argv[i + 1]);
                i++;
            }
            else {
                var args = arg.split('=');
                if (args[0] === '--socket') {
                    port = parseInt(args[1]);
                }
            }
        }
        if (port) {
            output = new stream.PassThrough();
            input = new stream.PassThrough();
            var server_1 = net.createServer(function (socket) {
                server_1.close();
                socket.pipe(output);
                input.pipe(socket);
            }).listen(port);
        }
    }
    var commandLineMessage = "Use arguments of createConnection or set command line parameters: '--node-ipc', '--stdio' or '--socket={number}'";
    if (!input) {
        throw new Error("Connection input stream is not set. " + commandLineMessage);
    }
    if (!output) {
        throw new Error("Connection output stream is not set. " + commandLineMessage);
    }
    var shutdownReceived;
    // Backwards compatibility
    if (Is.func(input.read) && Is.func(input.on)) {
        var inputStream = input;
        inputStream.on('end', function () {
            process.exit(shutdownReceived ? 0 : 1);
        });
        inputStream.on('close', function () {
            process.exit(shutdownReceived ? 0 : 1);
        });
    }
    var logger = new ConnectionLogger();
    var connection = vscode_jsonrpc_1.createServerMessageConnection(input, output, logger);
    logger.attach(connection);
    var remoteWindow = new RemoteWindowImpl(connection);
    var telemetry = new TelemetryImpl(connection);
    var tracer = new TracerImpl(connection);
    function asThenable(value) {
        if (Is.thenable(value)) {
            return value;
        }
        else {
            return Promise.resolve(value);
        }
    }
    var shutdownHandler = null;
    var initializeHandler = null;
    var exitHandler = null;
    var protocolConnection = {
        listen: function () { return connection.listen(); },
        sendRequest: function (type, params) { return connection.sendRequest(type, params); },
        onRequest: function (type, handler) { return connection.onRequest(type, handler); },
        sendNotification: function (type, params) { return connection.sendNotification(type, params); },
        onNotification: function (type, handler) { return connection.onNotification(type, handler); },
        onInitialize: function (handler) { return initializeHandler = handler; },
        onShutdown: function (handler) { return shutdownHandler = handler; },
        onExit: function (handler) { return exitHandler = handler; },
        get console() { return logger; },
        get window() { return remoteWindow; },
        get telemetry() { return telemetry; },
        get tracer() { return tracer; },
        onDidChangeConfiguration: function (handler) { return connection.onNotification(protocol_1.DidChangeConfigurationNotification.type, handler); },
        onDidChangeWatchedFiles: function (handler) { return connection.onNotification(protocol_1.DidChangeWatchedFilesNotification.type, handler); },
        __textDocumentSync: undefined,
        onDidOpenTextDocument: function (handler) { return connection.onNotification(protocol_1.DidOpenTextDocumentNotification.type, handler); },
        onDidChangeTextDocument: function (handler) { return connection.onNotification(protocol_1.DidChangeTextDocumentNotification.type, handler); },
        onDidCloseTextDocument: function (handler) { return connection.onNotification(protocol_1.DidCloseTextDocumentNotification.type, handler); },
        onDidSaveTextDocument: function (handler) { return connection.onNotification(protocol_1.DidSaveTextDocumentNotification.type, handler); },
        sendDiagnostics: function (params) { return connection.sendNotification(protocol_1.PublishDiagnosticsNotification.type, params); },
        onHover: function (handler) { return connection.onRequest(protocol_1.HoverRequest.type, handler); },
        onCompletion: function (handler) { return connection.onRequest(protocol_1.CompletionRequest.type, handler); },
        onCompletionResolve: function (handler) { return connection.onRequest(protocol_1.CompletionResolveRequest.type, handler); },
        onSignatureHelp: function (handler) { return connection.onRequest(protocol_1.SignatureHelpRequest.type, handler); },
        onDefinition: function (handler) { return connection.onRequest(protocol_1.DefinitionRequest.type, handler); },
        onReferences: function (handler) { return connection.onRequest(protocol_1.ReferencesRequest.type, handler); },
        onDocumentHighlight: function (handler) { return connection.onRequest(protocol_1.DocumentHighlightRequest.type, handler); },
        onDocumentSymbol: function (handler) { return connection.onRequest(protocol_1.DocumentSymbolRequest.type, handler); },
        onWorkspaceSymbol: function (handler) { return connection.onRequest(protocol_1.WorkspaceSymbolRequest.type, handler); },
        onCodeAction: function (handler) { return connection.onRequest(protocol_1.CodeActionRequest.type, handler); },
        onCodeLens: function (handler) { return connection.onRequest(protocol_1.CodeLensRequest.type, handler); },
        onCodeLensResolve: function (handler) { return connection.onRequest(protocol_1.CodeLensResolveRequest.type, handler); },
        onDocumentFormatting: function (handler) { return connection.onRequest(protocol_1.DocumentFormattingRequest.type, handler); },
        onDocumentRangeFormatting: function (handler) { return connection.onRequest(protocol_1.DocumentRangeFormattingRequest.type, handler); },
        onDocumentOnTypeFormatting: function (handler) { return connection.onRequest(protocol_1.DocumentOnTypeFormattingRequest.type, handler); },
        onRenameRequest: function (handler) { return connection.onRequest(protocol_1.RenameRequest.type, handler); },
        onDocumentLinks: function (handler) { return connection.onRequest(protocol_1.DocumentLinkRequest.type, handler); },
        onDocumentLinkResolve: function (handler) { return connection.onRequest(protocol_1.DocumentLinkResolveRequest.type, handler); },
        dispose: function () { return connection.dispose(); }
    };
    connection.onRequest(protocol_1.InitializeRequest.type, function (params) {
        if (Is.number(params.processId)) {
            // We received a parent process id. Set up a timer to periodically check
            // if the parent is still alive.
            setInterval(function () {
                try {
                    process.kill(params.processId, 0);
                }
                catch (ex) {
                    // Parent process doesn't exist anymore. Exit the server.
                    process.exit(shutdownReceived ? 0 : 1);
                }
            }, 3000);
        }
        if (Is.string(params.trace)) {
            tracer.trace = vscode_jsonrpc_1.Trace.fromString(params.trace);
        }
        if (initializeHandler) {
            var result = initializeHandler(params, new vscode_jsonrpc_1.CancellationTokenSource().token);
            return asThenable(result).then(function (value) {
                if (value instanceof vscode_jsonrpc_1.ResponseError) {
                    return value;
                }
                var result = value;
                if (!result) {
                    result = { capabilities: {} };
                }
                var capabilities = result.capabilities;
                if (!capabilities) {
                    capabilities = {};
                    result.capabilities = {};
                }
                if (!Is.number(capabilities.textDocumentSync)) {
                    capabilities.textDocumentSync = Is.number(protocolConnection.__textDocumentSync) ? protocolConnection.__textDocumentSync : protocol_1.TextDocumentSyncKind.None;
                }
                return result;
            });
        }
        else {
            var result = { capabilities: { textDocumentSync: protocol_1.TextDocumentSyncKind.None } };
            return result;
        }
    });
    connection.onRequest(protocol_1.ShutdownRequest.type, function (params) {
        shutdownReceived = true;
        if (shutdownHandler) {
            return shutdownHandler(params, new vscode_jsonrpc_1.CancellationTokenSource().token);
        }
        else {
            return undefined;
        }
    });
    connection.onNotification(protocol_1.ExitNotification.type, function (params) {
        try {
            if (exitHandler) {
                exitHandler(params);
            }
        }
        finally {
            if (shutdownReceived) {
                process.exit(0);
            }
            else {
                process.exit(1);
            }
        }
    });
    connection.onNotification(vscode_jsonrpc_1.SetTraceNotification.type, function (params) {
        tracer.trace = vscode_jsonrpc_1.Trace.fromString(params.value);
        ;
    });
    return protocolConnection;
}
exports.createConnection = createConnection;
