//===-- DataBreakpointInfoRequestHandler.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 "EventHelper.h"
#include "Protocol/ProtocolTypes.h"
#include "RequestHandler.h"
#include "lldb/API/SBMemoryRegionInfo.h"
#include "llvm/ADT/StringExtras.h"
#include <optional>

namespace lldb_dap {

/// Obtains information on a possible data breakpoint that could be set on an
/// expression or variable. Clients should only call this request if the
/// corresponding capability supportsDataBreakpoints is true.
llvm::Expected<protocol::DataBreakpointInfoResponseBody>
DataBreakpointInfoRequestHandler::Run(
    const protocol::DataBreakpointInfoArguments &args) const {
  protocol::DataBreakpointInfoResponseBody response;
  lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId.value_or(UINT64_MAX));
  lldb::SBValue variable = dap.variables.FindVariable(
      args.variablesReference.value_or(0), args.name);
  std::string addr, size;

  bool is_data_ok = true;
  if (variable.IsValid()) {
    lldb::addr_t load_addr = variable.GetLoadAddress();
    size_t byte_size = variable.GetByteSize();
    if (load_addr == LLDB_INVALID_ADDRESS) {
      is_data_ok = false;
      response.description = "does not exist in memory, its location is " +
                             std::string(variable.GetLocation());
    } else if (byte_size == 0) {
      is_data_ok = false;
      response.description = "variable size is 0";
    } else {
      addr = llvm::utohexstr(load_addr);
      size = llvm::utostr(byte_size);
    }
  } else if (args.variablesReference.value_or(0) == 0 && frame.IsValid()) {
    lldb::SBValue value = frame.EvaluateExpression(args.name.c_str());
    if (value.GetError().Fail()) {
      lldb::SBError error = value.GetError();
      const char *error_cstr = error.GetCString();
      is_data_ok = false;
      response.description = error_cstr && error_cstr[0]
                                 ? std::string(error_cstr)
                                 : "evaluation failed";
    } else {
      uint64_t load_addr = value.GetValueAsUnsigned();
      lldb::SBData data = value.GetPointeeData();
      if (data.IsValid()) {
        size = llvm::utostr(data.GetByteSize());
        addr = llvm::utohexstr(load_addr);
        lldb::SBMemoryRegionInfo region;
        lldb::SBError err =
            dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region);
        // Only lldb-server supports "qMemoryRegionInfo". So, don't fail this
        // request if SBProcess::GetMemoryRegionInfo returns error.
        if (err.Success()) {
          if (!(region.IsReadable() || region.IsWritable())) {
            is_data_ok = false;
            response.description = "memory region for address " + addr +
                                   " has no read or write permissions";
          }
        }
      } else {
        is_data_ok = false;
        response.description =
            "unable to get byte size for expression: " + args.name;
      }
    }
  } else {
    is_data_ok = false;
    response.description = "variable not found: " + args.name;
  }

  if (is_data_ok) {
    response.dataId = addr + "/" + size;
    response.accessTypes = {protocol::eDataBreakpointAccessTypeRead,
                            protocol::eDataBreakpointAccessTypeWrite,
                            protocol::eDataBreakpointAccessTypeReadWrite};
    response.description = size + " bytes at " + addr + " " + args.name;
  }

  return response;
}

} // namespace lldb_dap
