#include "CommandObjectSession.h"
#include "lldb/Host/OptionParser.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Interpreter/OptionArgParser.h"
#include "lldb/Interpreter/OptionValue.h"
#include "lldb/Interpreter/OptionValueBoolean.h"
#include "lldb/Interpreter/OptionValueString.h"
#include "lldb/Interpreter/OptionValueUInt64.h"
#include "lldb/Interpreter/Options.h"

using namespace lldb;
using namespace lldb_private;

class CommandObjectSessionSave : public CommandObjectParsed {
public:
  CommandObjectSessionSave(CommandInterpreter &interpreter)
      : CommandObjectParsed(interpreter, "session save",
                            "Save the current session transcripts to a file.\n"
                            "If no file if specified, transcripts will be "
                            "saved to a temporary file.",
                            "session save [file]") {
    CommandArgumentEntry arg1;
    arg1.emplace_back(eArgTypePath, eArgRepeatOptional);
    m_arguments.push_back(arg1);
  }

  ~CommandObjectSessionSave() override = default;

  void
  HandleArgumentCompletion(CompletionRequest &request,
                           OptionElementVector &opt_element_vector) override {
    CommandCompletions::InvokeCommonCompletionCallbacks(
        GetCommandInterpreter(), CommandCompletions::eDiskFileCompletion,
        request, nullptr);
  }

protected:
  bool DoExecute(Args &args, CommandReturnObject &result) override {
    llvm::StringRef file_path;

    if (!args.empty())
      file_path = args[0].ref();

    if (m_interpreter.SaveTranscript(result, file_path.str()))
      result.SetStatus(eReturnStatusSuccessFinishNoResult);
    else
      result.SetStatus(eReturnStatusFailed);
    return result.Succeeded();
  }
};

#define LLDB_OPTIONS_history
#include "CommandOptions.inc"

class CommandObjectSessionHistory : public CommandObjectParsed {
public:
  CommandObjectSessionHistory(CommandInterpreter &interpreter)
      : CommandObjectParsed(interpreter, "session history",
                            "Dump the history of commands in this session.\n"
                            "Commands in the history list can be run again "
                            "using \"!<INDEX>\".   \"!-<OFFSET>\" will re-run "
                            "the command that is <OFFSET> commands from the end"
                            " of the list (counting the current command).",
                            nullptr) {}

  ~CommandObjectSessionHistory() override = default;

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

protected:
  class CommandOptions : public Options {
  public:
    CommandOptions()
        : m_start_idx(0), m_stop_idx(0), m_count(0), m_clear(false) {}

    ~CommandOptions() 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 'c':
        error = m_count.SetValueFromString(option_arg, eVarSetOperationAssign);
        break;
      case 's':
        if (option_arg == "end") {
          m_start_idx.SetCurrentValue(UINT64_MAX);
          m_start_idx.SetOptionWasSet();
        } else
          error = m_start_idx.SetValueFromString(option_arg,
                                                 eVarSetOperationAssign);
        break;
      case 'e':
        error =
            m_stop_idx.SetValueFromString(option_arg, eVarSetOperationAssign);
        break;
      case 'C':
        m_clear.SetCurrentValue(true);
        m_clear.SetOptionWasSet();
        break;
      default:
        llvm_unreachable("Unimplemented option");
      }

      return error;
    }

    void OptionParsingStarting(ExecutionContext *execution_context) override {
      m_start_idx.Clear();
      m_stop_idx.Clear();
      m_count.Clear();
      m_clear.Clear();
    }

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

    // Instance variables to hold the values for command options.

    OptionValueUInt64 m_start_idx;
    OptionValueUInt64 m_stop_idx;
    OptionValueUInt64 m_count;
    OptionValueBoolean m_clear;
  };

  bool DoExecute(Args &command, CommandReturnObject &result) override {
    if (m_options.m_clear.GetCurrentValue() &&
        m_options.m_clear.OptionWasSet()) {
      m_interpreter.GetCommandHistory().Clear();
      result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult);
    } else {
      if (m_options.m_start_idx.OptionWasSet() &&
          m_options.m_stop_idx.OptionWasSet() &&
          m_options.m_count.OptionWasSet()) {
        result.AppendError("--count, --start-index and --end-index cannot be "
                           "all specified in the same invocation");
        result.SetStatus(lldb::eReturnStatusFailed);
      } else {
        std::pair<bool, uint64_t> start_idx(
            m_options.m_start_idx.OptionWasSet(),
            m_options.m_start_idx.GetCurrentValue());
        std::pair<bool, uint64_t> stop_idx(
            m_options.m_stop_idx.OptionWasSet(),
            m_options.m_stop_idx.GetCurrentValue());
        std::pair<bool, uint64_t> count(m_options.m_count.OptionWasSet(),
                                        m_options.m_count.GetCurrentValue());

        const CommandHistory &history(m_interpreter.GetCommandHistory());

        if (start_idx.first && start_idx.second == UINT64_MAX) {
          if (count.first) {
            start_idx.second = history.GetSize() - count.second;
            stop_idx.second = history.GetSize() - 1;
          } else if (stop_idx.first) {
            start_idx.second = stop_idx.second;
            stop_idx.second = history.GetSize() - 1;
          } else {
            start_idx.second = 0;
            stop_idx.second = history.GetSize() - 1;
          }
        } else {
          if (!start_idx.first && !stop_idx.first && !count.first) {
            start_idx.second = 0;
            stop_idx.second = history.GetSize() - 1;
          } else if (start_idx.first) {
            if (count.first) {
              stop_idx.second = start_idx.second + count.second - 1;
            } else if (!stop_idx.first) {
              stop_idx.second = history.GetSize() - 1;
            }
          } else if (stop_idx.first) {
            if (count.first) {
              if (stop_idx.second >= count.second)
                start_idx.second = stop_idx.second - count.second + 1;
              else
                start_idx.second = 0;
            }
          } else /* if (count.first) */
          {
            start_idx.second = 0;
            stop_idx.second = count.second - 1;
          }
        }
        history.Dump(result.GetOutputStream(), start_idx.second,
                     stop_idx.second);
      }
    }
    return result.Succeeded();
  }

  CommandOptions m_options;
};

CommandObjectSession::CommandObjectSession(CommandInterpreter &interpreter)
    : CommandObjectMultiword(interpreter, "session",
                             "Commands controlling LLDB session.",
                             "session <subcommand> [<command-options>]") {
  LoadSubCommand("save",
                 CommandObjectSP(new CommandObjectSessionSave(interpreter)));
  LoadSubCommand("history",
                 CommandObjectSP(new CommandObjectSessionHistory(interpreter)));
}
