//===-- CommandObjectPlugin.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 "CommandObjectPlugin.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Host/OptionParser.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandReturnObject.h"

using namespace lldb;
using namespace lldb_private;

class CommandObjectPluginLoad : public CommandObjectParsed {
public:
  CommandObjectPluginLoad(CommandInterpreter &interpreter)
      : CommandObjectParsed(interpreter, "plugin load",
                            "Import a dylib that implements an LLDB plugin.",
                            nullptr) {
    AddSimpleArgumentList(eArgTypeFilename);
  }

  ~CommandObjectPluginLoad() override = default;

protected:
  void DoExecute(Args &command, CommandReturnObject &result) override {
    size_t argc = command.GetArgumentCount();

    if (argc != 1) {
      result.AppendError("'plugin load' requires one argument");
      return;
    }

    Status error;

    FileSpec dylib_fspec(command[0].ref());
    FileSystem::Instance().Resolve(dylib_fspec);

    if (GetDebugger().LoadPlugin(dylib_fspec, error))
      result.SetStatus(eReturnStatusSuccessFinishResult);
    else {
      result.AppendError(error.AsCString());
    }
  }
};

namespace {
// Helper function to perform an action on each matching plugin.
// The action callback is given the containing namespace along with plugin info
// for each matching plugin.
static int ActOnMatchingPlugins(
    const llvm::StringRef pattern,
    std::function<void(const PluginNamespace &plugin_namespace,
                       const std::vector<RegisteredPluginInfo> &plugin_info)>
        action) {
  int num_matching = 0;

  for (const PluginNamespace &plugin_namespace :
       PluginManager::GetPluginNamespaces()) {

    std::vector<RegisteredPluginInfo> matching_plugins;
    for (const RegisteredPluginInfo &plugin_info :
         plugin_namespace.get_info()) {
      if (PluginManager::MatchPluginName(pattern, plugin_namespace,
                                         plugin_info))
        matching_plugins.push_back(plugin_info);
    }

    if (!matching_plugins.empty()) {
      num_matching += matching_plugins.size();
      action(plugin_namespace, matching_plugins);
    }
  }

  return num_matching;
}

// Call the "SetEnable" function for each matching plugins.
// Used to share the majority of the code between the enable
// and disable commands.
int SetEnableOnMatchingPlugins(const llvm::StringRef &pattern,
                               CommandReturnObject &result, bool enabled) {
  return ActOnMatchingPlugins(
      pattern, [&](const PluginNamespace &plugin_namespace,
                   const std::vector<RegisteredPluginInfo> &plugins) {
        result.AppendMessage(plugin_namespace.name);
        for (const auto &plugin : plugins) {
          if (!plugin_namespace.set_enabled(plugin.name, enabled)) {
            result.AppendErrorWithFormat("failed to enable plugin %s.%s",
                                         plugin_namespace.name.data(),
                                         plugin.name.data());
            continue;
          }

          result.AppendMessageWithFormat(
              "  %s %-30s %s\n", enabled ? "[+]" : "[-]", plugin.name.data(),
              plugin.description.data());
        }
      });
}

static std::string ConvertJSONToPrettyString(const llvm::json::Value &json) {
  std::string str;
  llvm::raw_string_ostream os(str);
  os << llvm::formatv("{0:2}", json).str();
  os.flush();
  return str;
}

#define LLDB_OPTIONS_plugin_list
#include "CommandOptions.inc"

// These option definitions are used by the plugin list command.
class PluginListCommandOptions : public Options {
public:
  PluginListCommandOptions() = default;

  ~PluginListCommandOptions() override = default;

  Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
                        ExecutionContext *execution_context) override {
    Status error;
    const int short_option = m_getopt_table[option_idx].val;

    switch (short_option) {
    case 'j':
      m_json_format = true;
      break;
    default:
      llvm_unreachable("Unimplemented option");
    }

    return error;
  }

  void OptionParsingStarting(ExecutionContext *execution_context) override {
    m_json_format = false;
  }

  llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
    return llvm::ArrayRef(g_plugin_list_options);
  }

  // Instance variables to hold the values for command options.
  bool m_json_format = false;
};
} // namespace

class CommandObjectPluginList : public CommandObjectParsed {
public:
  CommandObjectPluginList(CommandInterpreter &interpreter)
      : CommandObjectParsed(interpreter, "plugin list",
                            "Report info about registered LLDB plugins.",
                            nullptr) {
    AddSimpleArgumentList(eArgTypeManagedPlugin);
    SetHelpLong(R"(
Display information about registered plugins.
The plugin information is formatted as shown below:

    <plugin-namespace>
      [+] <plugin-name>                  Plugin #1 description
      [-] <plugin-name>                  Plugin #2 description

An enabled plugin is marked with [+] and a disabled plugin is marked with [-].

Plugins can be listed by namespace and name with:

  plugin list <plugin-namespace>[.<plugin-name>]

Plugins can be listed by namespace alone or with a fully qualified name. When listed
with just a namespace all plugins in that namespace are listed.  When no arguments
are given all plugins are listed.

Examples:
List all plugins

  (lldb) plugin list

List all plugins in the system-runtime namespace

  (lldb) plugin list system-runtime

List only the plugin 'foo' matching a fully qualified name exactly

  (lldb) plugin list system-runtime.foo
)");
  }

  ~CommandObjectPluginList() override = default;

  Options *GetOptions() override { return &m_options; }

protected:
  void DoExecute(Args &command, CommandReturnObject &result) override {
    size_t argc = command.GetArgumentCount();
    result.SetStatus(eReturnStatusSuccessFinishResult);

    // Create a temporary vector to hold the patterns to simplify the logic
    // for the case when the user passes no patterns
    std::vector<llvm::StringRef> patterns;
    patterns.reserve(argc == 0 ? 1 : argc);
    if (argc == 0)
      patterns.push_back("");
    else
      for (size_t i = 0; i < argc; ++i)
        patterns.push_back(command[i].ref());

    if (m_options.m_json_format)
      OutputJsonFormat(patterns, result);
    else
      OutputTextFormat(patterns, result);
  }

private:
  void OutputJsonFormat(const std::vector<llvm::StringRef> &patterns,
                        CommandReturnObject &result) {
    llvm::json::Object obj;
    bool found_empty = false;
    for (const llvm::StringRef pattern : patterns) {
      llvm::json::Object pat_obj = PluginManager::GetJSON(pattern);
      if (pat_obj.empty()) {
        found_empty = true;
        result.AppendErrorWithFormat(
            "Found no matching plugins for pattern '%s'", pattern.data());
        break;
      }
      for (auto &entry : pat_obj) {
        obj[entry.first] = std::move(entry.second);
      }
    }
    if (!found_empty) {
      result.AppendMessage(ConvertJSONToPrettyString(std::move(obj)));
    }
  }

  void OutputTextFormat(const std::vector<llvm::StringRef> &patterns,
                        CommandReturnObject &result) {
    for (const llvm::StringRef pattern : patterns) {
      int num_matching = ActOnMatchingPlugins(
          pattern, [&](const PluginNamespace &plugin_namespace,
                       const std::vector<RegisteredPluginInfo> &plugins) {
            result.AppendMessage(plugin_namespace.name);
            for (auto &plugin : plugins) {
              result.AppendMessageWithFormat(
                  "  %s %-30s %s\n", plugin.enabled ? "[+]" : "[-]",
                  plugin.name.data(), plugin.description.data());
            }
          });
      if (num_matching == 0) {
        result.AppendErrorWithFormat(
            "Found no matching plugins for pattern '%s'", pattern.data());
        break;
      }
    }
  }

  PluginListCommandOptions m_options;
};

static void DoPluginEnableDisable(Args &command, CommandReturnObject &result,
                                  bool enable) {
  const char *name = enable ? "enable" : "disable";
  size_t argc = command.GetArgumentCount();
  if (argc == 0) {
    result.AppendErrorWithFormat("'plugin %s' requires one or more arguments",
                                 name);
    return;
  }
  result.SetStatus(eReturnStatusSuccessFinishResult);

  for (size_t i = 0; i < argc; ++i) {
    llvm::StringRef pattern = command[i].ref();
    int num_matching = SetEnableOnMatchingPlugins(pattern, result, enable);

    if (num_matching == 0) {
      result.AppendErrorWithFormat(
          "Found no matching plugins to %s for pattern '%s'", name,
          pattern.data());
      break;
    }
  }
}

class CommandObjectPluginEnable : public CommandObjectParsed {
public:
  CommandObjectPluginEnable(CommandInterpreter &interpreter)
      : CommandObjectParsed(interpreter, "plugin enable",
                            "Enable registered LLDB plugins.", nullptr) {
    AddSimpleArgumentList(eArgTypeManagedPlugin);
  }

  ~CommandObjectPluginEnable() override = default;

protected:
  void DoExecute(Args &command, CommandReturnObject &result) override {
    DoPluginEnableDisable(command, result, /*enable=*/true);
  }
};

class CommandObjectPluginDisable : public CommandObjectParsed {
public:
  CommandObjectPluginDisable(CommandInterpreter &interpreter)
      : CommandObjectParsed(interpreter, "plugin disable",
                            "Disable registered LLDB plugins.", nullptr) {
    AddSimpleArgumentList(eArgTypeManagedPlugin);
  }

  ~CommandObjectPluginDisable() override = default;

protected:
  void DoExecute(Args &command, CommandReturnObject &result) override {
    DoPluginEnableDisable(command, result, /*enable=*/false);
  }
};

CommandObjectPlugin::CommandObjectPlugin(CommandInterpreter &interpreter)
    : CommandObjectMultiword(interpreter, "plugin",
                             "Commands for managing LLDB plugins.",
                             "plugin <subcommand> [<subcommand-options>]") {
  LoadSubCommand("load",
                 CommandObjectSP(new CommandObjectPluginLoad(interpreter)));
  LoadSubCommand("list",
                 CommandObjectSP(new CommandObjectPluginList(interpreter)));
  LoadSubCommand("enable",
                 CommandObjectSP(new CommandObjectPluginEnable(interpreter)));
  LoadSubCommand("disable",
                 CommandObjectSP(new CommandObjectPluginDisable(interpreter)));
}

CommandObjectPlugin::~CommandObjectPlugin() = default;
