/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#include "watchman/CommandRegistry.h"
#include "watchman/Logging.h"
#include "watchman/root/Root.h"
#include "watchman/root/resolve.h"
#include "watchman/sockname.h"
#include "watchman/thirdparty/jansson/jansson.h"
#include "watchman/watchman_cmd.h"
#include "watchman/watchman_stream.h"

using namespace watchman;

static bool query_caps(
    json_ref& response,
    json_ref& result,
    const json_ref& arr,
    bool required) {
  size_t i;
  bool have_all = true;

  for (i = 0; i < json_array_size(arr); i++) {
    const auto& ele = arr.at(i);
    const char* capname = json_string_value(ele);
    bool have = capability_supported(json_to_w_string(ele).view());
    if (!have) {
      have_all = false;
    }
    if (!capname) {
      break;
    }
    result.set(capname, json_boolean(have));
    if (required && !have) {
      auto buf = w_string::build(
          "client required capability `",
          capname,
          "` is not supported by this server");
      response.set("error", w_string_to_json(buf));
      watchman::log(watchman::ERR, "version: ", buf, "\n");

      // Only trigger the error on the first one we hit.  Ideally
      // we'd tell the user about all of them, but it is a PITA to
      // join and print them here in C :-/
      required = false;
    }
  }
  return have_all;
}

/* version */
static void cmd_version(struct watchman_client* client, const json_ref& args) {
  auto resp = make_response();

#ifdef WATCHMAN_BUILD_INFO
  resp.set(
      "buildinfo", typed_string_to_json(WATCHMAN_BUILD_INFO, W_STRING_UNICODE));
#endif

  /* ["version"]
   *    -> just returns the basic version information.
   * ["version", {"required": ["foo"], "optional": ["bar"]}]
   *    -> includes capability matching information
   */

  if (json_array_size(args) == 2) {
    const auto& arg_obj = args.at(1);

    auto req_cap = arg_obj.get_default("required");
    auto opt_cap = arg_obj.get_default("optional");

    auto cap_res = json_object_of_size(
        (opt_cap ? json_array_size(opt_cap) : 0) +
        (req_cap ? json_array_size(req_cap) : 0));

    if (opt_cap && opt_cap.isArray()) {
      query_caps(resp, cap_res, opt_cap, false);
    }
    if (req_cap && req_cap.isArray()) {
      query_caps(resp, cap_res, req_cap, true);
    }

    resp.set("capabilities", std::move(cap_res));
  }

  send_and_dispose_response(client, std::move(resp));
}
W_CMD_REG(
    "version",
    cmd_version,
    CMD_DAEMON | CMD_CLIENT | CMD_ALLOW_ANY_USER,
    NULL)

/* list-capabilities */
static void cmd_list_capabilities(
    struct watchman_client* client,
    const json_ref&) {
  auto resp = make_response();

  resp.set("capabilities", capability_get_list());
  send_and_dispose_response(client, std::move(resp));
}
W_CMD_REG(
    "list-capabilities",
    cmd_list_capabilities,
    CMD_DAEMON | CMD_CLIENT | CMD_ALLOW_ANY_USER,
    NULL)

/* get-sockname */
static void cmd_get_sockname(struct watchman_client* client, const json_ref&) {
  auto resp = make_response();

  // For legacy reasons we report the unix domain socket as sockname on
  // unix but the named pipe path on windows
  resp.set(
      "sockname",
      w_string_to_json(w_string(get_sock_name_legacy(), W_STRING_BYTE)));
  if (!disable_unix_socket) {
    resp.set(
        "unix_domain", w_string_to_json(w_string::build(get_unix_sock_name())));
  }

#ifdef WIN32
  if (!disable_named_pipe) {
    resp.set(
        "named_pipe",
        w_string_to_json(w_string::build(get_named_pipe_sock_path())));
  }
#endif

  send_and_dispose_response(client, std::move(resp));
}
W_CMD_REG(
    "get-sockname",
    cmd_get_sockname,
    CMD_DAEMON | CMD_CLIENT | CMD_ALLOW_ANY_USER,
    NULL)

static void cmd_get_config(
    struct watchman_client* client,
    const json_ref& args) {
  json_ref config;

  if (json_array_size(args) != 2) {
    send_error_response(client, "wrong number of arguments for 'get-config'");
    return;
  }

  auto root = resolveRoot(client, args);

  auto resp = make_response();

  config = root->config_file;

  if (!config) {
    config = json_object();
  }

  resp.set("config", std::move(config));
  send_and_dispose_response(client, std::move(resp));
}
W_CMD_REG("get-config", cmd_get_config, CMD_DAEMON, w_cmd_realpath_root)

/* vim:ts=2:sw=2:et:
 */
