text: "use strict";
  /* --------------------------------------------------------------------------------------------
   * Copyright (c) Microsoft Corporation. All rights reserved.
   * Licensed under the MIT License. See License.txt in the project root for license information.
   * ------------------------------------------------------------------------------------------ */
  Object.defineProperty(exports, "__esModule", { value: true });
  exports.createMessageConnection = exports.ConnectionOptions = exports.CancellationStrategy = exports.CancellationSenderStrategy = exports.CancellationReceiverStrategy = exports.ConnectionStrategy = exports.ConnectionError = exports.ConnectionErrors = exports.LogTraceNotification = exports.SetTraceNotification = exports.TraceFormat = exports.Trace = exports.NullLogger = exports.ProgressType = void 0;
  const ral_1 = require("./ral");
  const Is = require("./is");
  const messages_1 = require("./messages");
  const linkedMap_1 = require("./linkedMap");
  const events_1 = require("./events");
  const cancellation_1 = require("./cancellation");
  var CancelNotification;
  (function (CancelNotification) {
      CancelNotification.type = new messages_1.NotificationType('$/cancelRequest');
  })(CancelNotification || (CancelNotification = {}));
  var ProgressNotification;
  (function (ProgressNotification) {
      ProgressNotification.type = new messages_1.NotificationType('$/progress');
  })(ProgressNotification || (ProgressNotification = {}));
  class ProgressType {
      constructor() {
      }
  }
  exports.ProgressType = ProgressType;
  var StarRequestHandler;
  (function (StarRequestHandler) {
      function is(value) {
          return Is.func(value);
      }
      StarRequestHandler.is = is;
  })(StarRequestHandler || (StarRequestHandler = {}));
  exports.NullLogger = Object.freeze({
      error: () => { },
      warn: () => { },
      info: () => { },
      log: () => { }
  });
  var Trace;
  (function (Trace) {
      Trace[Trace["Off"] = 0] = "Off";
      Trace[Trace["Messages"] = 1] = "Messages";
      Trace[Trace["Verbose"] = 2] = "Verbose";
  })(Trace = exports.Trace || (exports.Trace = {}));
  (function (Trace) {
      function fromString(value) {
          if (!Is.string(value)) {
              return Trace.Off;
          }
          value = value.toLowerCase();
          switch (value) {
              case 'off':
                  return Trace.Off;
              case 'messages':
                  return Trace.Messages;
              case 'verbose':
                  return Trace.Verbose;
              default:
                  return Trace.Off;
          }
      }
      Trace.fromString = fromString;
      function toString(value) {
          switch (value) {
              case Trace.Off:
                  return 'off';
              case Trace.Messages:
                  return 'messages';
              case Trace.Verbose:
                  return 'verbose';
              default:
                  return 'off';
          }
      }
      Trace.toString = toString;
  })(Trace = exports.Trace || (exports.Trace = {}));
  var TraceFormat;
  (function (TraceFormat) {
      TraceFormat["Text"] = "text";
      TraceFormat["JSON"] = "json";
  })(TraceFormat = exports.TraceFormat || (exports.TraceFormat = {}));
  (function (TraceFormat) {
      function fromString(value) {
          value = value.toLowerCase();
          if (value === 'json') {
              return TraceFormat.JSON;
          }
          else {
              return TraceFormat.Text;
          }
      }
      TraceFormat.fromString = fromString;
  })(TraceFormat = exports.TraceFormat || (exports.TraceFormat = {}));
  var SetTraceNotification;
  (function (SetTraceNotification) {
      SetTraceNotification.type = new messages_1.NotificationType('$/setTrace');
  })(SetTraceNotification = exports.SetTraceNotification || (exports.SetTraceNotification = {}));
  var LogTraceNotification;
  (function (LogTraceNotification) {
      LogTraceNotification.type = new messages_1.NotificationType('$/logTrace');
  })(LogTraceNotification = exports.LogTraceNotification || (exports.LogTraceNotification = {}));
  var ConnectionErrors;
  (function (ConnectionErrors) {
      /**
       * The connection is closed.
       */
      ConnectionErrors[ConnectionErrors["Closed"] = 1] = "Closed";
      /**
       * The connection got disposed.
       */
      ConnectionErrors[ConnectionErrors["Disposed"] = 2] = "Disposed";
      /**
       * The connection is already in listening mode.
       */
      ConnectionErrors[ConnectionErrors["AlreadyListening"] = 3] = "AlreadyListening";
  })(ConnectionErrors = exports.ConnectionErrors || (exports.ConnectionErrors = {}));
  class ConnectionError extends Error {
      constructor(code, message) {
          super(message);
          this.code = code;
          Object.setPrototypeOf(this, ConnectionError.prototype);
      }
  }
  exports.ConnectionError = ConnectionError;
  var ConnectionStrategy;
  (function (ConnectionStrategy) {
      function is(value) {
          const candidate = value;
          return candidate && Is.func(candidate.cancelUndispatched);
      }
      ConnectionStrategy.is = is;
  })(ConnectionStrategy = exports.ConnectionStrategy || (exports.ConnectionStrategy = {}));
  var CancellationReceiverStrategy;
  (function (CancellationReceiverStrategy) {
      CancellationReceiverStrategy.Message = Object.freeze({
          createCancellationTokenSource(_) {
              return new cancellation_1.CancellationTokenSource();
          }
      });
      function is(value) {
          const candidate = value;
          return candidate && Is.func(candidate.createCancellationTokenSource);
      }
      CancellationReceiverStrategy.is = is;
  })(CancellationReceiverStrategy = exports.CancellationReceiverStrategy || (exports.CancellationReceiverStrategy = {}));
  var CancellationSenderStrategy;
  (function (CancellationSenderStrategy) {
      CancellationSenderStrategy.Message = Object.freeze({
          sendCancellation(conn, id) {
              conn.sendNotification(CancelNotification.type, { id });
          },
          cleanup(_) { }
      });
      function is(value) {
          const candidate = value;
          return candidate && Is.func(candidate.sendCancellation) && Is.func(candidate.cleanup);
      }
      CancellationSenderStrategy.is = is;
  })(CancellationSenderStrategy = exports.CancellationSenderStrategy || (exports.CancellationSenderStrategy = {}));
  var CancellationStrategy;
  (function (CancellationStrategy) {
      CancellationStrategy.Message = Object.freeze({
          receiver: CancellationReceiverStrategy.Message,
          sender: CancellationSenderStrategy.Message
      });
      function is(value) {
          const candidate = value;
          return candidate && CancellationReceiverStrategy.is(candidate.receiver) && CancellationSenderStrategy.is(candidate.sender);
      }
      CancellationStrategy.is = is;
  })(CancellationStrategy = exports.CancellationStrategy || (exports.CancellationStrategy = {}));
  var ConnectionOptions;
  (function (ConnectionOptions) {
      function is(value) {
          const candidate = value;
          return candidate && (CancellationStrategy.is(candidate.cancellationStrategy) || ConnectionStrategy.is(candidate.connectionStrategy));
      }
      ConnectionOptions.is = is;
  })(ConnectionOptions = exports.ConnectionOptions || (exports.ConnectionOptions = {}));
  var ConnectionState;
  (function (ConnectionState) {
      ConnectionState[ConnectionState["New"] = 1] = "New";
      ConnectionState[ConnectionState["Listening"] = 2] = "Listening";
      ConnectionState[ConnectionState["Closed"] = 3] = "Closed";
      ConnectionState[ConnectionState["Disposed"] = 4] = "Disposed";
  })(ConnectionState || (ConnectionState = {}));
  function createMessageConnection(messageReader, messageWriter, _logger, options) {
      const logger = _logger !== undefined ? _logger : exports.NullLogger;
      let sequenceNumber = 0;
      let notificationSquenceNumber = 0;
      let unknownResponseSquenceNumber = 0;
      const version = '2.0';
      let starRequestHandler = undefined;
      const requestHandlers = Object.create(null);
      let starNotificationHandler = undefined;
      const notificationHandlers = Object.create(null);
      const progressHandlers = new Map();
      let timer;
      let messageQueue = new linkedMap_1.LinkedMap();
      let responsePromises = Object.create(null);
      let requestTokens = Object.create(null);
      let trace = Trace.Off;
      let traceFormat = TraceFormat.Text;
      let tracer;
      let state = ConnectionState.New;
      const errorEmitter = new events_1.Emitter();
      const closeEmitter = new events_1.Emitter();
      const unhandledNotificationEmitter = new events_1.Emitter();
      const unhandledProgressEmitter = new events_1.Emitter();
      const disposeEmitter = new events_1.Emitter();
      const cancellationStrategy = (options && options.cancellationStrategy) ? options.cancellationStrategy : CancellationStrategy.Message;
      function createRequestQueueKey(id) {
          if (id === null) {
              throw new Error(`Can't send requests with id null since the response can't be correlated.`);
          }
          return 'req-' + id.toString();
      }
      function createResponseQueueKey(id) {
          if (id === null) {
              return 'res-unknown-' + (++unknownResponseSquenceNumber).toString();
          }
          else {
              return 'res-' + id.toString();
          }
      }
      function createNotificationQueueKey() {
          return 'not-' + (++notificationSquenceNumber).toString();
      }
      function addMessageToQueue(queue, message) {
          if (messages_1.isRequestMessage(message)) {
              queue.set(createRequestQueueKey(message.id), message);
          }
          else if (messages_1.isResponseMessage(message)) {
              queue.set(createResponseQueueKey(message.id), message);
          }
          else {
              queue.set(createNotificationQueueKey(), message);
          }
      }
      function cancelUndispatched(_message) {
          return undefined;
      }
      function isListening() {
          return state === ConnectionState.Listening;
      }
      function isClosed() {
          return state === ConnectionState.Closed;
      }
      function isDisposed() {
          return state === ConnectionState.Disposed;
      }
      function closeHandler() {
          if (state === ConnectionState.New || state === ConnectionState.Listening) {
              state = ConnectionState.Closed;
              closeEmitter.fire(undefined);
          }
          // If the connection is disposed don't sent close events.
      }
      function readErrorHandler(error) {
          errorEmitter.fire([error, undefined, undefined]);
      }
      function writeErrorHandler(data) {
          errorEmitter.fire(data);
      }
      messageReader.onClose(closeHandler);
      messageReader.onError(readErrorHandler);
      messageWriter.onClose(closeHandler);
      messageWriter.onError(writeErrorHandler);
      function triggerMessageQueue() {
          if (timer || messageQueue.size === 0) {
              return;
          }
          timer = ral_1.default().timer.setImmediate(() => {
              timer = undefined;
              processMessageQueue();
          });
      }
      function processMessageQueue() {
          if (messageQueue.size === 0) {
              return;
          }
          const message = messageQueue.shift();
          try {
              if (messages_1.isRequestMessage(message)) {
                  handleRequest(message);
              }
              else if (messages_1.isNotificationMessage(message)) {
                  handleNotification(message);
              }
              else if (messages_1.isResponseMessage(message)) {
                  handleResponse(message);
              }
              else {
                  handleInvalidMessage(message);
              }
          }
          finally {
              triggerMessageQueue();
          }
      }
      const callback = (message) => {
          try {
              // We have received a cancellation message. Check if the message is still in the queue
              // and cancel it if allowed to do so.
              if (messages_1.isNotificationMessage(message) && message.method === CancelNotification.type.method) {
                  const key = createRequestQueueKey(message.params.id);
                  const toCancel = messageQueue.get(key);
                  if (messages_1.isRequestMessage(toCancel)) {
                      const strategy = options === null || options === void 0 ? void 0 : options.connectionStrategy;
                      const response = (strategy && strategy.cancelUndispatched) ? strategy.cancelUndispatched(toCancel, cancelUndispatched) : cancelUndispatched(toCancel);
                      if (response && (response.error !== undefined || response.result !== undefined)) {
                          messageQueue.delete(key);
                          response.id = toCancel.id;
                          traceSendingResponse(response, message.method, Date.now());
                          messageWriter.write(response);
                          return;
                      }
                  }
              }
              addMessageToQueue(messageQueue, message);
          }
          finally {
              triggerMessageQueue();
          }
      };
      function handleRequest(requestMessage) {
          if (isDisposed()) {
              // we return here silently since we fired an event when the
              // connection got disposed.
              return;
          }
          function reply(resultOrError, method, startTime) {
              const message = {
                  jsonrpc: version,
                  id: requestMessage.id
              };
              if (resultOrError instanceof messages_1.ResponseError) {
                  message.error = resultOrError.toJson();
              }
              else {
                  message.result = resultOrError === undefined ? null : resultOrError;
              }
              traceSendingResponse(message, method, startTime);
              messageWriter.write(message);
          }
          function replyError(error, method, startTime) {
              const message = {
                  jsonrpc: version,
                  id: requestMessage.id,
                  error: error.toJson()
              };
              traceSendingResponse(message, method, startTime);
              messageWriter.write(message);
          }
          function replySuccess(result, method, startTime) {
              // The JSON RPC defines that a response must either have a result or an error
              // So we can't treat undefined as a valid response result.
              if (result === undefined) {
                  result = null;
              }
              const message = {
                  jsonrpc: version,
                  id: requestMessage.id,
                  result: result
              };
              traceSendingResponse(message, method, startTime);
              messageWriter.write(message);
          }
          traceReceivedRequest(requestMessage);
          const element = requestHandlers[requestMessage.method];
          let type;
          let requestHandler;
          if (element) {
              type = element.type;
              requestHandler = element.handler;
          }
          const startTime = Date.now();
          if (requestHandler || starRequestHandler) {
              const tokenKey = String(requestMessage.id);
              const cancellationSource = cancellationStrategy.receiver.createCancellationTokenSource(tokenKey);
              requestTokens[tokenKey] = cancellationSource;
              try {
                  let handlerResult;
                  if (requestHandler) {
                      if (requestMessage.params === undefined) {
                          if (type !== undefined && type.numberOfParams !== 0) {
                              replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InvalidParams, `Request ${requestMessage.method} defines ${type.numberOfParams} params but recevied none.`), requestMessage.method, startTime);
                              return;
                          }
                          handlerResult = requestHandler(cancellationSource.token);
                      }
                      else if (Array.isArray(requestMessage.params)) {
                          if (type !== undefined && type.parameterStructures === messages_1.ParameterStructures.byName) {
                              replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InvalidParams, `Request ${requestMessage.method} defines parameters by name but received parameters by position`), requestMessage.method, startTime);
                              return;
                          }
                          handlerResult = requestHandler(...requestMessage.params, cancellationSource.token);
                      }
                      else {
                          if (type !== undefined && type.parameterStructures === messages_1.ParameterStructures.byPosition) {
                              replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InvalidParams, `Request ${requestMessage.method} defines parameters by position but received parameters by name`), requestMessage.method, startTime);
                              return;
                          }
                          handlerResult = requestHandler(requestMessage.params, cancellationSource.token);
                      }
                  }
                  else if (starRequestHandler) {
                      handlerResult = starRequestHandler(requestMessage.method, requestMessage.params, cancellationSource.token);
                  }
                  const promise = handlerResult;
                  if (!handlerResult) {
                      delete requestTokens[tokenKey];
                      replySuccess(handlerResult, requestMessage.method, startTime);
                  }
                  else if (promise.then) {
                      promise.then((resultOrError) => {
                          delete requestTokens[tokenKey];
                          reply(resultOrError, requestMessage.method, startTime);
                      }, error => {
                          delete requestTokens[tokenKey];
                          if (error instanceof messages_1.ResponseError) {
                              replyError(error, requestMessage.method, startTime);
                          }
                          else if (error && Is.string(error.message)) {
                              replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InternalError, `Request ${requestMessage.method} failed with message: ${error.message}`), requestMessage.method, startTime);
                          }
                          else {
                              replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InternalError, `Request ${requestMessage.method} failed unexpectedly without providing any details.`), requestMessage.method, startTime);
                          }
                      });
                  }
                  else {
                      delete requestTokens[tokenKey];
                      reply(handlerResult, requestMessage.method, startTime);
                  }
              }
              catch (error) {
                  delete requestTokens[tokenKey];
                  if (error instanceof messages_1.ResponseError) {
                      reply(error, requestMessage.method, startTime);
                  }
                  else if (error && Is.string(error.message)) {
                      replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InternalError, `Request ${requestMessage.method} failed with message: ${error.message}`), requestMessage.method, startTime);
                  }
                  else {
                      replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InternalError, `Request ${requestMessage.method} failed unexpectedly without providing any details.`), requestMessage.method, startTime);
                  }
              }
          }
          else {
              replyError(new messages_1.ResponseError(messages_1.ErrorCodes.MethodNotFound, `Unhandled method ${requestMessage.method}`), requestMessage.method, startTime);
          }
      }
      function handleResponse(responseMessage) {
          if (isDisposed()) {
              // See handle request.
              return;
          }
          if (responseMessage.id === null) {
              if (responseMessage.error) {
                  logger.error(`Received response message without id: Error is: \n${JSON.stringify(responseMessage.error, undefined, 4)}`);
              }
              else {
                  logger.error(`Received response message without id. No further error information provided.`);
              }
          }
          else {
              const key = String(responseMessage.id);
              const responsePromise = responsePromises[key];
              traceReceivedResponse(responseMessage, responsePromise);
              if (responsePromise) {
                  delete responsePromises[key];
                  try {
                      if (responseMessage.error) {
                          const error = responseMessage.error;
                          responsePromise.reject(new messages_1.ResponseError(error.code, error.message, error.data));
                      }
                      else if (responseMessage.result !== undefined) {
                          responsePromise.resolve(responseMessage.result);
                      }
                      else {
                          throw new Error('Should never happen.');
                      }
                  }
                  catch (error) {
                      if (error.message) {
                          logger.error(`Response handler '${responsePromise.method}' failed with message: ${error.message}`);
                      }
                      else {
                          logger.error(`Response handler '${responsePromise.method}' failed unexpectedly.`);
                      }
                  }
              }
          }
      }
      function handleNotification(message) {
          if (isDisposed()) {
              // See handle request.
              return;
          }
          let type = undefined;
          let notificationHandler;
          if (message.method === CancelNotification.type.method) {
              notificationHandler = (params) => {
                  const id = params.id;
                  const source = requestTokens[String(id)];
                  if (source) {
                      source.cancel();
                  }
              };
          }
          else {
              const element = notificationHandlers[message.method];
              if (element) {
                  notificationHandler = element.handler;
                  type = element.type;
              }
          }
          if (notificationHandler || starNotificationHandler) {
              try {
                  traceReceivedNotification(message);
                  if (notificationHandler) {
                      if (message.params === undefined) {
                          if (type !== undefined) {
                              if (type.numberOfParams !== 0 && type.parameterStructures !== messages_1.ParameterStructures.byName) {
                                  logger.error(`Notification ${message.method} defines ${type.numberOfParams} params but recevied none.`);
                              }
                          }
                          notificationHandler();
                      }
                      else if (Array.isArray(message.params)) {
                          if (type !== undefined) {
                              if (type.parameterStructures === messages_1.ParameterStructures.byName) {
                                  logger.error(`Notification ${message.method} defines parameters by name but received parameters by position`);
                              }
                              if (type.numberOfParams !== message.params.length) {
                                  logger.error(`Notification ${message.method} defines ${type.numberOfParams} params but received ${message.params.length} argumennts`);
                              }
                          }
                          notificationHandler(...message.params);
                      }
                      else {
                          if (type !== undefined && type.parameterStructures === messages_1.ParameterStructures.byPosition) {
                              logger.error(`Notification ${message.method} defines parameters by position but received parameters by name`);
                          }
                          notificationHandler(message.params);
                      }
                  }
                  else if (starNotificationHandler) {
                      starNotificationHandler(message.method, message.params);
                  }
              }
              catch (error) {
                  if (error.message) {
                      logger.error(`Notification handler '${message.method}' failed with message: ${error.message}`);
                  }
                  else {
                      logger.error(`Notification handler '${message.method}' failed unexpectedly.`);
                  }
              }
          }
          else {
              unhandledNotificationEmitter.fire(message);
          }
      }
      function handleInvalidMessage(message) {
          if (!message) {
              logger.error('Received empty message.');
              return;
          }
          logger.error(`Received message which is neither a response nor a notification message:\n${JSON.stringify(message, null, 4)}`);
          // Test whether we find an id to reject the promise
          const responseMessage = message;
          if (Is.string(responseMessage.id) || Is.number(responseMessage.id)) {
              const key = String(responseMessage.id);
              const responseHandler = responsePromises[key];
              if (responseHandler) {
                  responseHandler.reject(new Error('The received response has neither a result nor an error property.'));
              }
          }
      }
      function traceSendingRequest(message) {
          if (trace === Trace.Off || !tracer) {
              return;
          }
          if (traceFormat === TraceFormat.Text) {
              let data = undefined;
              if (trace === Trace.Verbose && message.params) {
                  data = `Params: ${JSON.stringify(message.params, null, 4)}\n\n`;
              }
              tracer.log(`Sending request '${message.method} - (${message.id})'.`, data);
          }
          else {
              logLSPMessage('send-request', message);
          }
      }
      function traceSendingNotification(message) {
          if (trace === Trace.Off || !tracer) {
              return;
          }
          if (traceFormat === TraceFormat.Text) {
              let data = undefined;
              if (trace === Trace.Verbose) {
                  if (message.params) {
                      data = `Params: ${JSON.stringify(message.params, null, 4)}\n\n`;
                  }
                  else {
                      data = 'No parameters provided.\n\n';
                  }
              }
              tracer.log(`Sending notification '${message.method}'.`, data);
          }
          else {
              logLSPMessage('send-notification', message);
          }
      }
      function traceSendingResponse(message, method, startTime) {
          if (trace === Trace.Off || !tracer) {
              return;
          }
          if (traceFormat === TraceFormat.Text) {
              let data = undefined;
              if (trace === Trace.Verbose) {
                  if (message.error && message.error.data) {
                      data = `Error data: ${JSON.stringify(message.error.data, null, 4)}\n\n`;
                  }
                  else {
                      if (message.result) {
                          data = `Result: ${JSON.stringify(message.result, null, 4)}\n\n`;
                      }
                      else if (message.error === undefined) {
                          data = 'No result returned.\n\n';
                      }
                  }
              }
              tracer.log(`Sending response '${method} - (${message.id})'. Processing request took ${Date.now() - startTime}ms`, data);
          }
          else {
              logLSPMessage('send-response', message);
          }
      }
      function traceReceivedRequest(message) {
          if (trace === Trace.Off || !tracer) {
              return;
          }
          if (traceFormat === TraceFormat.Text) {
              let data = undefined;
              if (trace === Trace.Verbose && message.params) {
                  data = `Params: ${JSON.stringify(message.params, null, 4)}\n\n`;
              }
              tracer.log(`Received request '${message.method} - (${message.id})'.`, data);
          }
          else {
              logLSPMessage('receive-request', message);
          }
      }
      function traceReceivedNotification(message) {
          if (trace === Trace.Off || !tracer || message.method === LogTraceNotification.type.method) {
              return;
          }
          if (traceFormat === TraceFormat.Text) {
              let data = undefined;
              if (trace === Trace.Verbose) {
                  if (message.params) {
                      data = `Params: ${JSON.stringify(message.params, null, 4)}\n\n`;
                  }
                  else {
                      data = 'No parameters provided.\n\n';
                  }
              }
              tracer.log(`Received notification '${message.method}'.`, data);
          }
          else {
              logLSPMessage('receive-notification', message);
          }
      }
      function traceReceivedResponse(message, responsePromise) {
          if (trace === Trace.Off || !tracer) {
              return;
          }
          if (traceFormat === TraceFormat.Text) {
              let data = undefined;
              if (trace === Trace.Verbose) {
                  if (message.error && message.error.data) {
                      data = `Error data: ${JSON.stringify(message.error.data, null, 4)}\n\n`;
                  }
                  else {
                      if (message.result) {
                          data = `Result: ${JSON.stringify(message.result, null, 4)}\n\n`;
                      }
                      else if (message.error === undefined) {
                          data = 'No result returned.\n\n';
                      }
                  }
              }
              if (responsePromise) {
                  const error = message.error ? ` Request failed: ${message.error.message} (${message.error.code}).` : '';
                  tracer.log(`Received response '${responsePromise.method} - (${message.id})' in ${Date.now() - responsePromise.timerStart}ms.${error}`, data);
              }
              else {
                  tracer.log(`Received response ${message.id} without active response promise.`, data);
              }
          }
          else {
              logLSPMessage('receive-response', message);
          }
      }
      function logLSPMessage(type, message) {
          if (!tracer || trace === Trace.Off) {
              return;
          }
          const lspMessage = {
              isLSPMessage: true,
              type,
              message,
              timestamp: Date.now()
          };
          tracer.log(lspMessage);
      }
      function throwIfClosedOrDisposed() {
          if (isClosed()) {
              throw new ConnectionError(ConnectionErrors.Closed, 'Connection is closed.');
          }
          if (isDisposed()) {
              throw new ConnectionError(ConnectionErrors.Disposed, 'Connection is disposed.');
          }
      }
      function throwIfListening() {
          if (isListening()) {
              throw new ConnectionError(ConnectionErrors.AlreadyListening, 'Connection is already listening');
          }
      }
      function throwIfNotListening() {
          if (!isListening()) {
              throw new Error('Call listen() first.');
          }
      }
      function undefinedToNull(param) {
          if (param === undefined) {
              return null;
          }
          else {
              return param;
          }
      }
      function nullToUndefined(param) {
          if (param === null) {
              return undefined;
          }
          else {
              return param;
          }
      }
      function isNamedParam(param) {
          return param !== undefined && param !== null && !Array.isArray(param) && typeof param === 'object';
      }
      function computeSingleParam(parameterStructures, param) {
          switch (parameterStructures) {
              case messages_1.ParameterStructures.auto:
                  if (isNamedParam(param)) {
                      return nullToUndefined(param);
                  }
                  else {
                      return [undefinedToNull(param)];
                  }
                  break;
              case messages_1.ParameterStructures.byName:
                  if (!isNamedParam(param)) {
                      throw new Error(`Recevied parameters by name but param is not an object literal.`);
                  }
                  return nullToUndefined(param);
              case messages_1.ParameterStructures.byPosition:
                  return [undefinedToNull(param)];
              default:
                  throw new Error(`Unknown parameter structure ${parameterStructures.toString()}`);
          }
      }
      function computeMessageParams(type, params) {
          let result;
          const numberOfParams = type.numberOfParams;
          switch (numberOfParams) {
              case 0:
                  result = undefined;
                  break;
              case 1:
                  result = computeSingleParam(type.parameterStructures, params[0]);
                  break;
              default:
                  result = [];
                  for (let i = 0; i < params.length && i < numberOfParams; i++) {
                      result.push(undefinedToNull(params[i]));
                  }
                  if (params.length < numberOfParams) {
                      for (let i = params.length; i < numberOfParams; i++) {
                          result.push(null);
                      }
                  }
                  break;
          }
          return result;
      }
      const connection = {
          sendNotification: (type, ...args) => {
              throwIfClosedOrDisposed();
              let method;
              let messageParams;
              if (Is.string(type)) {
                  method = type;
                  const first = args[0];
                  let paramStart = 0;
                  let parameterStructures = messages_1.ParameterStructures.auto;
                  if (messages_1.ParameterStructures.is(first)) {
                      paramStart = 1;
                      parameterStructures = first;
                  }
                  let paramEnd = args.length;
                  const numberOfParams = paramEnd - paramStart;
                  switch (numberOfParams) {
                      case 0:
                          messageParams = undefined;
                          break;
                      case 1:
                          messageParams = computeSingleParam(parameterStructures, args[paramStart]);
                          break;
                      default:
                          if (parameterStructures === messages_1.ParameterStructures.byName) {
                              throw new Error(`Recevied ${numberOfParams} parameters for 'by Name' notification parameter structure.`);
                          }
                          messageParams = args.slice(paramStart, paramEnd).map(value => undefinedToNull(value));
                          break;
                  }
              }
              else {
                  const params = args;
                  method = type.method;
                  messageParams = computeMessageParams(type, params);
              }
              const notificationMessage = {
                  jsonrpc: version,
                  method: method,
                  params: messageParams
              };
              traceSendingNotification(notificationMessage);
              messageWriter.write(notificationMessage);
          },
          onNotification: (type, handler) => {
              throwIfClosedOrDisposed();
              let method;
              if (Is.func(type)) {
                  starNotificationHandler = type;
              }
              else if (handler) {
                  if (Is.string(type)) {
                      method = type;
                      notificationHandlers[type] = { type: undefined, handler };
                  }
                  else {
                      method = type.method;
                      notificationHandlers[type.method] = { type, handler };
                  }
              }
              return {
                  dispose: () => {
                      if (method !== undefined) {
                          delete notificationHandlers[method];
                      }
                      else {
                          starNotificationHandler = undefined;
                      }
                  }
              };
          },
          onProgress: (_type, token, handler) => {
              if (progressHandlers.has(token)) {
                  throw new Error(`Progress handler for token ${token} already registered`);
              }
              progressHandlers.set(token, handler);
              return {
                  dispose: () => {
                      progressHandlers.delete(token);
                  }
              };
          },
          sendProgress: (_type, token, value) => {
              connection.sendNotification(ProgressNotification.type, { token, value });
          },
          onUnhandledProgress: unhandledProgressEmitter.event,
          sendRequest: (type, ...args) => {
              throwIfClosedOrDisposed();
              throwIfNotListening();
              let method;
              let messageParams;
              let token = undefined;
              if (Is.string(type)) {
                  method = type;
                  const first = args[0];
                  const last = args[args.length - 1];
                  let paramStart = 0;
                  let parameterStructures = messages_1.ParameterStructures.auto;
                  if (messages_1.ParameterStructures.is(first)) {
                      paramStart = 1;
                      parameterStructures = first;
                  }
                  let paramEnd = args.length;
                  if (cancellation_1.CancellationToken.is(last)) {
                      paramEnd = paramEnd - 1;
                      token = last;
                  }
                  const numberOfParams = paramEnd - paramStart;
                  switch (numberOfParams) {
                      case 0:
                          messageParams = undefined;
                          break;
                      case 1:
                          messageParams = computeSingleParam(parameterStructures, args[paramStart]);
                          break;
                      default:
                          if (parameterStructures === messages_1.ParameterStructures.byName) {
                              throw new Error(`Recevied ${numberOfParams} parameters for 'by Name' request parameter structure.`);
                          }
                          messageParams = args.slice(paramStart, paramEnd).map(value => undefinedToNull(value));
                          break;
                  }
              }
              else {
                  const params = args;
                  method = type.method;
                  messageParams = computeMessageParams(type, params);
                  const numberOfParams = type.numberOfParams;
                  token = cancellation_1.CancellationToken.is(params[numberOfParams]) ? params[numberOfParams] : undefined;
              }
              const id = sequenceNumber++;
              let disposable;
              if (token) {
                  disposable = token.onCancellationRequested(() => {
                      cancellationStrategy.sender.sendCancellation(connection, id);
                  });
              }
              const result = new Promise((resolve, reject) => {
                  const requestMessage = {
                      jsonrpc: version,
                      id: id,
                      method: method,
                      params: messageParams
                  };
                  const resolveWithCleanup = (r) => {
                      resolve(r);
                      cancellationStrategy.sender.cleanup(id);
                      disposable === null || disposable === void 0 ? void 0 : disposable.dispose();
                  };
                  const rejectWithCleanup = (r) => {
                      reject(r);
                      cancellationStrategy.sender.cleanup(id);
                      disposable === null || disposable === void 0 ? void 0 : disposable.dispose();
                  };
                  let responsePromise = { method: method, timerStart: Date.now(), resolve: resolveWithCleanup, reject: rejectWithCleanup };
                  traceSendingRequest(requestMessage);
                  try {
                      messageWriter.write(requestMessage);
                  }
                  catch (e) {
                      // Writing the message failed. So we need to reject the promise.
                      responsePromise.reject(new messages_1.ResponseError(messages_1.ErrorCodes.MessageWriteError, e.message ? e.message : 'Unknown reason'));
                      responsePromise = null;
                  }
                  if (responsePromise) {
                      responsePromises[String(id)] = responsePromise;
                  }
              });
              return result;
          },
          onRequest: (type, handler) => {
              throwIfClosedOrDisposed();
              let method = null;
              if (StarRequestHandler.is(type)) {
                  method = undefined;
                  starRequestHandler = type;
              }
              else if (Is.string(type)) {
                  method = null;
                  if (handler !== undefined) {
                      method = type;
                      requestHandlers[type] = { handler: handler, type: undefined };
                  }
              }
              else {
                  if (handler !== undefined) {
                      method = type.method;
                      requestHandlers[type.method] = { type, handler };
                  }
              }
              return {
                  dispose: () => {
                      if (method === null) {
                          return;
                      }
                      if (method !== undefined) {
                          delete requestHandlers[method];
                      }
                      else {
                          starRequestHandler = undefined;
                      }
                  }
              };
          },
          trace: (_value, _tracer, sendNotificationOrTraceOptions) => {
              let _sendNotification = false;
              let _traceFormat = TraceFormat.Text;
              if (sendNotificationOrTraceOptions !== undefined) {
                  if (Is.boolean(sendNotificationOrTraceOptions)) {
                      _sendNotification = sendNotificationOrTraceOptions;
                  }
                  else {
                      _sendNotification = sendNotificationOrTraceOptions.sendNotification || false;
                      _traceFormat = sendNotificationOrTraceOptions.traceFormat || TraceFormat.Text;
                  }
              }
              trace = _value;
              traceFormat = _traceFormat;
              if (trace === Trace.Off) {
                  tracer = undefined;
              }
              else {
                  tracer = _tracer;
              }
              if (_sendNotification && !isClosed() && !isDisposed()) {
                  connection.sendNotification(SetTraceNotification.type, { value: Trace.toString(_value) });
              }
          },
          onError: errorEmitter.event,
          onClose: closeEmitter.event,
          onUnhandledNotification: unhandledNotificationEmitter.event,
          onDispose: disposeEmitter.event,
          end: () => {
              messageWriter.end();
          },
          dispose: () => {
              if (isDisposed()) {
                  return;
              }
              state = ConnectionState.Disposed;
              disposeEmitter.fire(undefined);
              const error = new Error('Connection got disposed.');
              Object.keys(responsePromises).forEach((key) => {
                  responsePromises[key].reject(error);
              });
              responsePromises = Object.create(null);
              requestTokens = Object.create(null);
              messageQueue = new linkedMap_1.LinkedMap();
              // Test for backwards compatibility
              if (Is.func(messageWriter.dispose)) {
                  messageWriter.dispose();
              }
              if (Is.func(messageReader.dispose)) {
                  messageReader.dispose();
              }
          },
          listen: () => {
              throwIfClosedOrDisposed();
              throwIfListening();
              state = ConnectionState.Listening;
              messageReader.listen(callback);
          },
          inspect: () => {
              // eslint-disable-next-line no-console
              ral_1.default().console.log('inspect');
          }
      };
      connection.onNotification(LogTraceNotification.type, (params) => {
          if (trace === Trace.Off || !tracer) {
              return;
          }
          tracer.log(params.message, trace === Trace.Verbose ? params.verbose : undefined);
      });
      connection.onNotification(ProgressNotification.type, (params) => {
          const handler = progressHandlers.get(params.token);
          if (handler) {
              handler(params.value);
          }
          else {
              unhandledProgressEmitter.fire(params);
          }
      });
      return connection;
  }
  exports.createMessageConnection = createMessageConnection;
  //# sourceMappingURL=connection.js.map