//===-- EvaluateRequestHandler.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 "DAP.h"
#include "DAPError.h"
#include "EventHelper.h"
#include "JSONUtils.h"
#include "LLDBUtils.h"
#include "Protocol/DAPTypes.h"
#include "Protocol/ProtocolRequests.h"
#include "Protocol/ProtocolTypes.h"
#include "RequestHandler.h"
#include "lldb/lldb-enumerations.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"

using namespace llvm;
using namespace lldb_dap;
using namespace lldb_dap::protocol;

namespace lldb_dap {

static bool RunExpressionAsLLDBCommand(DAP &dap, lldb::SBFrame &frame,
                                       std::string &expression,
                                       EvaluateContext context) {
  if (context != eEvaluateContextRepl && context != eEvaluateContextUnknown)
    return false;

  // Since we don't know this context do not try to repeat the last command;
  if (context == eEvaluateContextUnknown && expression.empty())
    return false;

  const bool repeat_last_command =
      expression.empty() && dap.last_valid_variable_expression.empty();
  if (repeat_last_command)
    return true;

  const ReplMode repl_mode = dap.DetectReplMode(frame, expression, false);
  return repl_mode == ReplMode::Command;
}

static lldb::SBValue EvaluateVariableExpression(lldb::SBTarget &target,
                                                lldb::SBFrame &frame,
                                                const std::string &expression,
                                                bool run_as_expression) {
  const char *expression_cstr = expression.c_str();

  lldb::SBValue value;
  if (frame) {
    // Check if it is a variable or an expression path for a variable. i.e.
    // 'foo->bar' finds the 'bar' variable. It is more reliable than the
    // expression parser in many cases and it is faster.
    value = frame.GetValueForVariablePath(
        expression_cstr, lldb::eDynamicDontRunTarget, lldb::eDILModeLegacy);
    if (value || !run_as_expression)
      return value;

    return frame.EvaluateExpression(expression_cstr);
  }

  if (run_as_expression)
    value = target.EvaluateExpression(expression_cstr);

  return value;
}

/// Evaluates the given expression in the context of a stack frame.
///
/// The expression has access to any variables and arguments that are in scope.
Expected<EvaluateResponseBody>
EvaluateRequestHandler::Run(const EvaluateArguments &arguments) const {

  EvaluateResponseBody body;
  lldb::SBFrame frame = dap.GetLLDBFrame(arguments.frameId);
  std::string expression = llvm::StringRef(arguments.expression).trim().str();
  const EvaluateContext evaluate_context = arguments.context;
  const bool is_repl_context = evaluate_context == eEvaluateContextRepl;

  if (RunExpressionAsLLDBCommand(dap, frame, expression, evaluate_context)) {
    // Since the current expression is not for a variable, clear the
    // last_valid_variable_expression field.
    dap.last_valid_variable_expression.clear();
    // If we're evaluating a command relative to the current frame, set the
    // focus_tid to the current frame for any thread related events.
    if (frame.IsValid()) {
      dap.focus_tid = frame.GetThread().GetThreadID();
    }

    bool required_command_failed = false;
    body.result = RunLLDBCommands(
        dap.debugger, dap.GetAPIMutex(), llvm::StringRef(), {expression},
        required_command_failed,
        /*parse_command_directives=*/false, /*echo_commands=*/false);
    return body;
  }

  if (dap.ProcessIsNotStopped())
    return llvm::make_error<DAPError>(
        "Cannot evaluate expressions while the process is running. Pause "
        "the process and try again.",
        /**error_code=*/llvm::inconvertibleErrorCode(),
        /**show_user=*/false);

  // If the user expression is empty, evaluate the last valid variable
  // expression.
  if (expression.empty() && is_repl_context)
    expression = dap.last_valid_variable_expression;

  const bool run_as_expression = evaluate_context != eEvaluateContextHover;
  lldb::SBValue value = EvaluateVariableExpression(
      dap.target, frame, expression, run_as_expression);

  if (value.GetError().Fail())
    return ToError(value.GetError(), /*show_user=*/false);

  if (is_repl_context) {
    // save the new variable expression
    dap.last_valid_variable_expression = std::move(expression);

    // Freeze dry the value in case users expand it later in the debug console
    value = value.Persist();
  }

  const bool hex = arguments.format ? arguments.format->hex : false;
  VariableDescription desc(value, dap.configuration.enableAutoVariableSummaries,
                           hex);

  body.result = desc.GetResult(evaluate_context);
  body.type = desc.display_type_name;

  if (value.MightHaveChildren() || ValuePointsToCode(value))
    body.variablesReference = dap.reference_storage.Insert(
        value, /*is_permanent=*/is_repl_context, /*is_internal=*/false);

  if (lldb::addr_t addr = value.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS)
    body.memoryReference = EncodeMemoryReference(addr);

  if (ValuePointsToCode(value) &&
      body.variablesReference.Kind() != eReferenceKindInvalid)
    body.valueLocationReference =
        PackLocation(body.variablesReference.AsUInt32(), true);

  return body;
}

} // namespace lldb_dap
