//===-- CommandObjectProtocolServer.cpp -----------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "CommandObjectProtocolServer.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/ProtocolServer.h"
#include "lldb/Host/Socket.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Utility/UriParser.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/FormatAdapters.h"
#include <string>

using namespace llvm;
using namespace lldb;
using namespace lldb_private;

#define LLDB_OPTIONS_mcp
#include "CommandOptions.inc"

class CommandObjectProtocolServerStart : public CommandObjectParsed {
public:
  CommandObjectProtocolServerStart(CommandInterpreter &interpreter)
      : CommandObjectParsed(interpreter, "protocol-server start",
                            "start protocol server",
                            "protocol-server start <protocol> [<connection>]") {
    AddSimpleArgumentList(lldb::eArgTypeProtocol, eArgRepeatPlain);
    AddSimpleArgumentList(lldb::eArgTypeConnectURL, eArgRepeatPlain);
  }

  ~CommandObjectProtocolServerStart() override = default;

protected:
  void DoExecute(Args &args, CommandReturnObject &result) override {
    if (args.GetArgumentCount() < 1) {
      result.AppendError("no protocol specified");
      return;
    }

    llvm::StringRef protocol = args.GetArgumentAtIndex(0);
    ProtocolServer *server = ProtocolServer::GetOrCreate(protocol);
    if (!server) {
      result.AppendErrorWithFormatv(
          "unsupported protocol: {0}. Supported protocols are: {1}", protocol,
          llvm::join(ProtocolServer::GetSupportedProtocols(), ", "));
      return;
    }

    std::string connection_uri = "listen://[localhost]:0";
    if (args.GetArgumentCount() >= 2)
      connection_uri = args.GetArgumentAtIndex(1);

    const char *connection_error =
        "unsupported connection specifier, expected 'accept:///path' "
        "or 'listen://[host]:port', got '{0}'.";
    auto uri = lldb_private::URI::Parse(connection_uri);
    if (!uri) {
      result.AppendErrorWithFormatv(connection_error, connection_uri);
      return;
    }

    std::optional<Socket::ProtocolModePair> protocol_and_mode =
        Socket::GetProtocolAndMode(uri->scheme);
    if (!protocol_and_mode || protocol_and_mode->second != Socket::ModeAccept) {
      result.AppendErrorWithFormatv(connection_error, connection_uri);
      return;
    }

    ProtocolServer::Connection connection;
    connection.protocol = protocol_and_mode->first;
    if (connection.protocol == Socket::SocketProtocol::ProtocolUnixDomain)
      connection.name = uri->path;
    else
      connection.name = formatv(
          "[{0}]:{1}", uri->hostname.empty() ? "0.0.0.0" : uri->hostname,
          uri->port.value_or(0));

    if (llvm::Error error = server->Start(connection)) {
      result.AppendErrorWithFormatv("{0}", llvm::fmt_consume(std::move(error)));
      return;
    }

    if (Socket *socket = server->GetSocket()) {
      std::string address =
          llvm::join(socket->GetListeningConnectionURI(), ", ");
      result.AppendMessageWithFormatv(
          "{0} server started with connection listeners: {1}", protocol,
          address);
      result.SetStatus(eReturnStatusSuccessFinishNoResult);
    }
  }
};

class CommandObjectProtocolServerStop : public CommandObjectParsed {
public:
  CommandObjectProtocolServerStop(CommandInterpreter &interpreter)
      : CommandObjectParsed(interpreter, "protocol-server stop",
                            "stop protocol server",
                            "protocol-server stop <protocol>") {
    AddSimpleArgumentList(lldb::eArgTypeProtocol, eArgRepeatPlain);
  }

  ~CommandObjectProtocolServerStop() override = default;

protected:
  void DoExecute(Args &args, CommandReturnObject &result) override {
    if (args.GetArgumentCount() < 1) {
      result.AppendError("no protocol specified");
      return;
    }

    llvm::StringRef protocol = args.GetArgumentAtIndex(0);
    ProtocolServer *server = ProtocolServer::GetOrCreate(protocol);
    if (!server) {
      result.AppendErrorWithFormatv(
          "unsupported protocol: {0}. Supported protocols are: {1}", protocol,
          llvm::join(ProtocolServer::GetSupportedProtocols(), ", "));
      return;
    }

    if (llvm::Error error = server->Stop()) {
      result.AppendErrorWithFormatv("{0}", llvm::fmt_consume(std::move(error)));
      return;
    }
  }
};

class CommandObjectProtocolServerGet : public CommandObjectParsed {
public:
  CommandObjectProtocolServerGet(CommandInterpreter &interpreter)
      : CommandObjectParsed(interpreter, "protocol-server get",
                            "get protocol server connection information",
                            "protocol-server get <protocol>") {
    AddSimpleArgumentList(lldb::eArgTypeProtocol, eArgRepeatPlain);
  }

  ~CommandObjectProtocolServerGet() override = default;

protected:
  void DoExecute(Args &args, CommandReturnObject &result) override {
    if (args.GetArgumentCount() < 1) {
      result.AppendError("no protocol specified");
      return;
    }

    llvm::StringRef protocol = args.GetArgumentAtIndex(0);
    ProtocolServer *server = ProtocolServer::GetOrCreate(protocol);
    if (!server) {
      result.AppendErrorWithFormatv(
          "unsupported protocol: {0}. Supported protocols are: {1}", protocol,
          llvm::join(ProtocolServer::GetSupportedProtocols(), ", "));
      return;
    }

    Socket *socket = server->GetSocket();
    if (!socket) {
      result.AppendErrorWithFormatv("{0} server is not running", protocol);
      return;
    }

    std::string address = llvm::join(socket->GetListeningConnectionURI(), ", ");
    result.AppendMessageWithFormatv("{0} server connection listeners: {1}",
                                    protocol, address);
    result.SetStatus(eReturnStatusSuccessFinishNoResult);
  }
};

CommandObjectProtocolServer::CommandObjectProtocolServer(
    CommandInterpreter &interpreter)
    : CommandObjectMultiword(interpreter, "protocol-server",
                             "Start, stop, and query protocol servers.",
                             "protocol-server") {
  LoadSubCommand("start", CommandObjectSP(new CommandObjectProtocolServerStart(
                              interpreter)));
  LoadSubCommand("stop", CommandObjectSP(
                             new CommandObjectProtocolServerStop(interpreter)));
  LoadSubCommand(
      "get", CommandObjectSP(new CommandObjectProtocolServerGet(interpreter)));
}

CommandObjectProtocolServer::~CommandObjectProtocolServer() = default;
