/* --------------------------------------------------------------------------------------------
 * Copyright (c) 2024 TypeFox and others.
 * Licensed under the MIT License. See LICENSE in the package root for license information.
 * ------------------------------------------------------------------------------------------ */
//The source file can be referred to: https://github.com/TypeFox/monaco-languageclient/blob/main/packages/examples/src/common/node/server-commons.ts
import {ServerOptions, WebSocketServer} from "ws";
import {IncomingMessage, Server} from "node:http";
import {fileURLToPath, URL} from "node:url";
import {Socket} from "node:net";
import {dirname} from "node:path";
import {IWebSocket, WebSocketMessageReader, WebSocketMessageWriter} from "vscode-ws-jsonrpc";
import {createConnection, createServerProcess, forward} from "vscode-ws-jsonrpc/server";
import {InitializeParams, InitializeRequest, Message} from "vscode-languageserver";
import * as cp from "child_process";

export enum LanguageName {
  /** https://nodejs.org/api/cli.html  */
  node = "node",
  /** https://docs.oracle.com/en/java/javase/21/docs/specs/man/java.html */
  java = "java"
}

export interface LanguageServerRunConfig {
  serverName: string;
  pathName: string;
  serverPort: number;
  runCommand: LanguageName | string;
  runCommandArgs: string[];
  wsServerOptions: ServerOptions,
  spawnOptions?: cp.SpawnOptions;
}

/**
 * start the language server inside the current process
 */
export const launchLanguageServer = (runconfig: LanguageServerRunConfig, socket: IWebSocket) => {
  const { serverName, runCommand, runCommandArgs, spawnOptions } = runconfig;
  // start the language server as an external process
  const reader = new WebSocketMessageReader(socket);
  const writer = new WebSocketMessageWriter(socket);
  const socketConnection = createConnection(reader, writer, () => socket.dispose());
  const serverConnection = createServerProcess(serverName, runCommand, runCommandArgs, spawnOptions);
  if (serverConnection) {
    forward(socketConnection, serverConnection, message => {
      if (Message.isRequest(message)) {
        console.log(`${serverName} Server received:`);
        console.log(message);
        if (message.method === InitializeRequest.type.method) {
          const initializeParams = message.params as InitializeParams;
          initializeParams.processId = process.pid;
        }
      }
      if (Message.isResponse(message)) {
        console.log(`${serverName} Server sent:`);
        console.log(message);
      }
      return message;
    });
  }
};

export const upgradeWsServer = (runconfig: LanguageServerRunConfig,
                                config: {
                                  server: Server,
                                  wss: WebSocketServer
                                }) => {
  config.server.on("upgrade", (request: IncomingMessage, socket: Socket, head: Buffer) => {
    const baseURL = `http://${request.headers.host}/`;
    const pathName = request.url !== undefined ? new URL(request.url, baseURL).pathname : undefined;
    if (pathName === runconfig.pathName) {
      config.wss.handleUpgrade(request, socket, head, webSocket => {
        const socket: IWebSocket = {
          send: content => webSocket.send(content, error => {
            if (error) {
              throw error;
            }
          }),
          onMessage: cb => webSocket.on("message", (data) => {
            console.log(data.toString());
            cb(data);
          }),
          onError: cb => webSocket.on("error", cb),
          onClose: cb => webSocket.on("close", cb),
          dispose: () => webSocket.close(),
        };
        // launch the server when the web socket is opened
        if (webSocket.readyState === webSocket.OPEN) {
          launchLanguageServer(runconfig, socket);
        } else {
          webSocket.on("open", () => {
            launchLanguageServer(runconfig, socket);
          });
        }
      });
    }
  });
};

/**
 * Solves: __dirname is not defined in ES module scope
 */
export const getLocalDirectory = (referenceUrl: string | URL) => {
  const __filename = fileURLToPath(referenceUrl);
  return dirname(__filename);
};