//===-- CommandAlias.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 "lldb/Interpreter/CommandAlias.h"

#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormatAdapters.h"

#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandObject.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Interpreter/Options.h"
#include "lldb/Utility/StreamString.h"

using namespace lldb;
using namespace lldb_private;

static llvm::Error
ProcessAliasOptionsArgs(lldb::CommandObjectSP &cmd_obj_sp,
                        llvm::StringRef options_args,
                        OptionArgVectorSP &option_arg_vector_sp) {
  OptionArgVector *option_arg_vector = option_arg_vector_sp.get();

  if (options_args.size() < 1)
    return llvm::Error::success();

  Args args(options_args);
  std::string options_string(options_args);
  // Check to see if the command being aliased can take any command options.
  Options *options = cmd_obj_sp->GetOptions();
  if (options) {
    // See if any options were specified as part of the alias;  if so, handle
    // them appropriately.
    ExecutionContext exe_ctx =
        cmd_obj_sp->GetCommandInterpreter().GetExecutionContext();
    options->NotifyOptionParsingStarting(&exe_ctx);

    llvm::Expected<Args> args_or =
        options->ParseAlias(args, option_arg_vector, options_string);
    if (!args_or)
      return llvm::createStringError(
          llvm::formatv("unable to create alias: {0}",
                        llvm::fmt_consume(args_or.takeError())));
    args = std::move(*args_or);
    if (llvm::Error error = options->VerifyPartialOptions())
      return error;
  }

  if (!options_string.empty()) {
    if (cmd_obj_sp->WantsRawCommandString()) {
      option_arg_vector->emplace_back(CommandInterpreter::g_argument, -1,
                                      options_string);
    } else {
      for (auto &entry : args.entries()) {
        if (!entry.ref().empty())
          option_arg_vector->emplace_back(
              std::string(CommandInterpreter::g_argument), -1,
              std::string(entry.ref()));
      }
    }
  }

  return llvm::Error::success();
}

CommandAlias::CommandAlias(CommandInterpreter &interpreter,
                           lldb::CommandObjectSP cmd_sp,
                           llvm::StringRef options_args, llvm::StringRef name,
                           llvm::StringRef help, llvm::StringRef syntax,
                           uint32_t flags)
    : CommandObject(interpreter, name, help, syntax, flags),
      m_option_string(std::string(options_args)),
      m_option_args_sp(new OptionArgVector),
      m_is_dashdash_alias(eLazyBoolCalculate), m_did_set_help(false),
      m_did_set_help_long(false) {
  if (llvm::Error error =
          ProcessAliasOptionsArgs(cmd_sp, options_args, m_option_args_sp)) {
    // FIXME: Find a way to percolate this error up.
    LLDB_LOG_ERROR(GetLog(LLDBLog::Host), std::move(error),
                   "ProcessAliasOptionsArgs failed: {0}");
  } else {
    m_underlying_command_sp = cmd_sp;
    for (int i = 0;
         auto *cmd_entry = m_underlying_command_sp->GetArgumentEntryAtIndex(i);
         i++) {
      m_arguments.push_back(*cmd_entry);
    }
    if (!help.empty()) {
      StreamString sstr;
      StreamString translation_and_help;
      GetAliasExpansion(sstr);

      translation_and_help.Printf(
          "(%s)  %s", sstr.GetData(),
          GetUnderlyingCommand()->GetHelp().str().c_str());
      SetHelp(translation_and_help.GetString());
    }
  }
}

bool CommandAlias::WantsRawCommandString() {
  if (IsValid())
    return m_underlying_command_sp->WantsRawCommandString();
  return false;
}

bool CommandAlias::WantsCompletion() {
  if (IsValid())
    return m_underlying_command_sp->WantsCompletion();
  return false;
}

void CommandAlias::HandleCompletion(CompletionRequest &request) {
  if (IsValid())
    m_underlying_command_sp->HandleCompletion(request);
}

void CommandAlias::HandleArgumentCompletion(
    CompletionRequest &request, OptionElementVector &opt_element_vector) {
  if (IsValid())
    m_underlying_command_sp->HandleArgumentCompletion(request,
                                                      opt_element_vector);
}

Options *CommandAlias::GetOptions() {
  if (IsValid())
    return m_underlying_command_sp->GetOptions();
  return nullptr;
}

void CommandAlias::Execute(const char *args_string,
                           CommandReturnObject &result) {
  llvm_unreachable("CommandAlias::Execute is not to be called");
}

void CommandAlias::GetAliasExpansion(StreamString &help_string) const {
  llvm::StringRef command_name = m_underlying_command_sp->GetCommandName();
  help_string.Printf("'%*s", (int)command_name.size(), command_name.data());

  if (!m_option_args_sp) {
    help_string.Printf("'");
    return;
  }

  OptionArgVector *options = m_option_args_sp.get();
  std::string opt;
  std::string value;

  for (const auto &opt_entry : *options) {
    std::tie(opt, std::ignore, value) = opt_entry;
    if (opt == CommandInterpreter::g_argument) {
      help_string.Printf(" %s", value.c_str());
    } else {
      help_string.Printf(" %s", opt.c_str());
      if ((value != CommandInterpreter::g_no_argument) 
           && (value != CommandInterpreter::g_need_argument)) {
        help_string.Printf(" %s", value.c_str());
      }
    }
  }

  help_string.Printf("'");
}

bool CommandAlias::IsDashDashCommand() {
  if (m_is_dashdash_alias != eLazyBoolCalculate)
    return (m_is_dashdash_alias == eLazyBoolYes);
  m_is_dashdash_alias = eLazyBoolNo;
  if (!IsValid())
    return false;

  std::string opt;
  std::string value;

  for (const auto &opt_entry : *GetOptionArguments()) {
    std::tie(opt, std::ignore, value) = opt_entry;
    if (opt == CommandInterpreter::g_argument && !value.empty() &&
        llvm::StringRef(value).ends_with("--")) {
      m_is_dashdash_alias = eLazyBoolYes;
      break;
    }
  }

  // if this is a nested alias, it may be adding arguments on top of an already
  // dash-dash alias
  if ((m_is_dashdash_alias == eLazyBoolNo) && IsNestedAlias())
    m_is_dashdash_alias =
        (GetUnderlyingCommand()->IsDashDashCommand() ? eLazyBoolYes
                                                     : eLazyBoolNo);
  return (m_is_dashdash_alias == eLazyBoolYes);
}

bool CommandAlias::IsNestedAlias() {
  if (GetUnderlyingCommand())
    return GetUnderlyingCommand()->IsAlias();
  return false;
}

std::pair<lldb::CommandObjectSP, OptionArgVectorSP> CommandAlias::Desugar() {
  auto underlying = GetUnderlyingCommand();
  if (!underlying)
    return {nullptr, nullptr};

  if (underlying->IsAlias()) {
    // FIXME: This doesn't work if the original alias fills a slot in the
    // underlying alias, since this just appends the two lists.
    auto desugared = ((CommandAlias *)underlying.get())->Desugar();
    OptionArgVectorSP options = std::make_shared<OptionArgVector>();
    llvm::append_range(*options, *desugared.second);
    llvm::append_range(*options, *GetOptionArguments());
    return {desugared.first, options};
  }

  return {underlying, GetOptionArguments()};
}

// allow CommandAlias objects to provide their own help, but fallback to the
// info for the underlying command if no customization has been provided
void CommandAlias::SetHelp(llvm::StringRef str) {
  this->CommandObject::SetHelp(str);
  m_did_set_help = true;
}

void CommandAlias::SetHelpLong(llvm::StringRef str) {
  this->CommandObject::SetHelpLong(str);
  m_did_set_help_long = true;
}

llvm::StringRef CommandAlias::GetHelp() {
  if (!m_cmd_help_short.empty() || m_did_set_help)
    return m_cmd_help_short;
  if (IsValid())
    return m_underlying_command_sp->GetHelp();
  return llvm::StringRef();
}

llvm::StringRef CommandAlias::GetHelpLong() {
  if (!m_cmd_help_long.empty() || m_did_set_help_long)
    return m_cmd_help_long;
  if (IsValid())
    return m_underlying_command_sp->GetHelpLong();
  return llvm::StringRef();
}
