//===-- CommandAlias.cpp ------------------------------------------*- C++
//-*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "lldb/Interpreter/CommandAlias.h"

#include "llvm/Support/ErrorHandling.h"

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

using namespace lldb;
using namespace lldb_private;

static bool ProcessAliasOptionsArgs(lldb::CommandObjectSP &cmd_obj_sp,
                                    const char *options_args,
                                    OptionArgVectorSP &option_arg_vector_sp) {
  bool success = true;
  OptionArgVector *option_arg_vector = option_arg_vector_sp.get();

  if (!options_args || (strlen(options_args) < 1))
    return true;

  std::string options_string(options_args);
  Args args(options_args);
  CommandReturnObject result;
  // 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);
    args.Unshift(llvm::StringRef("dummy_arg"));
    args.ParseAliasOptions(*options, result, option_arg_vector, options_string);
    args.Shift();
    if (result.Succeeded())
      options->VerifyPartialOptions(result);
    if (!result.Succeeded() &&
        result.GetStatus() != lldb::eReturnStatusStarted) {
      result.AppendError("Unable to create requested alias.\n");
      return false;
    }
  }

  if (!options_string.empty()) {
    if (cmd_obj_sp->WantsRawCommandString())
      option_arg_vector->push_back(
          OptionArgPair("<argument>", OptionArgValue(-1, options_string)));
    else {
      const size_t argc = args.GetArgumentCount();
      for (size_t i = 0; i < argc; ++i)
        if (strcmp(args.GetArgumentAtIndex(i), "") != 0)
          option_arg_vector->push_back(OptionArgPair(
              "<argument>",
              OptionArgValue(-1, std::string(args.GetArgumentAtIndex(i)))));
    }
  }

  return success;
}

CommandAlias::CommandAlias(CommandInterpreter &interpreter,
                           lldb::CommandObjectSP cmd_sp,
                           const char *options_args, const char *name,
                           const char *help, const char *syntax, uint32_t flags)
    : CommandObject(interpreter, name, help, syntax, flags),
      m_underlying_command_sp(),
      m_option_string(options_args ? 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 (ProcessAliasOptionsArgs(cmd_sp, options_args, m_option_args_sp)) {
    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 || !help[0]) {
      StreamString sstr;
      StreamString translation_and_help;
      GetAliasExpansion(sstr);

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

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;
}

int CommandAlias::HandleCompletion(Args &input, int &cursor_index,
                                   int &cursor_char_position,
                                   int match_start_point,
                                   int max_return_elements, bool &word_complete,
                                   StringList &matches) {
  if (IsValid())
    return m_underlying_command_sp->HandleCompletion(
        input, cursor_index, cursor_char_position, match_start_point,
        max_return_elements, word_complete, matches);
  return -1;
}

int CommandAlias::HandleArgumentCompletion(
    Args &input, int &cursor_index, int &cursor_char_position,
    OptionElementVector &opt_element_vector, int match_start_point,
    int max_return_elements, bool &word_complete, StringList &matches) {
  if (IsValid())
    return m_underlying_command_sp->HandleArgumentCompletion(
        input, cursor_index, cursor_char_position, opt_element_vector,
        match_start_point, max_return_elements, word_complete, matches);
  return -1;
}

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

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

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

  if (m_option_args_sp) {
    OptionArgVector *options = m_option_args_sp.get();
    for (size_t i = 0; i < options->size(); ++i) {
      OptionArgPair cur_option = (*options)[i];
      std::string opt = cur_option.first;
      OptionArgValue value_pair = cur_option.second;
      std::string value = value_pair.second;
      if (opt.compare("<argument>") == 0) {
        help_string.Printf(" %s", value.c_str());
      } else {
        help_string.Printf(" %s", opt.c_str());
        if ((value.compare("<no-argument>") != 0) &&
            (value.compare("<need-argument") != 0)) {
          help_string.Printf(" %s", value.c_str());
        }
      }
    }
  }

  help_string.Printf("'");
}

bool CommandAlias::IsDashDashCommand() {
  if (m_is_dashdash_alias == eLazyBoolCalculate) {
    m_is_dashdash_alias = eLazyBoolNo;
    if (IsValid()) {
      for (const OptionArgPair &opt_arg : *GetOptionArguments()) {
        if (opt_arg.first == "<argument>" && !opt_arg.second.second.empty() &&
            llvm::StringRef(opt_arg.second.second).endswith("--")) {
          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()) {
    auto desugared = ((CommandAlias *)underlying.get())->Desugar();
    auto options = GetOptionArguments();
    options->insert(options->begin(), desugared.second->begin(),
                    desugared.second->end());
    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(const char *str) {
  this->CommandObject::SetHelp(str);
  m_did_set_help = true;
}

void CommandAlias::SetHelpLong(const char *str) {
  this->CommandObject::SetHelpLong(str);
  m_did_set_help_long = true;
}

const char *CommandAlias::GetHelp() {
  if (!m_cmd_help_short.empty() || m_did_set_help)
    return m_cmd_help_short.c_str();
  if (IsValid())
    return m_underlying_command_sp->GetHelp();
  return nullptr;
}

const char *CommandAlias::GetHelpLong() {
  if (!m_cmd_help_long.empty() || m_did_set_help_long)
    return m_cmd_help_long.c_str();
  if (IsValid())
    return m_underlying_command_sp->GetHelpLong();
  return nullptr;
}
