//===-- 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 "JSONUtils.h"
#include "RequestHandler.h"
#include "lldb/API/SBMemoryRegionInfo.h"
#include "llvm/ADT/StringExtras.h"

namespace lldb_dap {

// "DataBreakpointInfoRequest": {
//   "allOf": [ { "$ref": "#/definitions/Request" }, {
//     "type": "object",
//     "description": "Obtains information on a possible data breakpoint that
//     could be set on an expression or variable.\nClients should only call this
//     request if the corresponding capability `supportsDataBreakpoints` is
//     true.", "properties": {
//       "command": {
//         "type": "string",
//         "enum": [ "dataBreakpointInfo" ]
//       },
//       "arguments": {
//         "$ref": "#/definitions/DataBreakpointInfoArguments"
//       }
//     },
//     "required": [ "command", "arguments"  ]
//   }]
// },
// "DataBreakpointInfoArguments": {
//   "type": "object",
//   "description": "Arguments for `dataBreakpointInfo` request.",
//   "properties": {
//     "variablesReference": {
//       "type": "integer",
//       "description": "Reference to the variable container if the data
//       breakpoint is requested for a child of the container. The
//       `variablesReference` must have been obtained in the current suspended
//       state. See 'Lifetime of Object References' in the Overview section for
//       details."
//     },
//     "name": {
//       "type": "string",
//       "description": "The name of the variable's child to obtain data
//       breakpoint information for.\nIf `variablesReference` isn't specified,
//       this can be an expression."
//     },
//     "frameId": {
//       "type": "integer",
//       "description": "When `name` is an expression, evaluate it in the scope
//       of this stack frame. If not specified, the expression is evaluated in
//       the global scope. When `variablesReference` is specified, this property
//       has no effect."
//     }
//   },
//   "required": [ "name" ]
// },
// "DataBreakpointInfoResponse": {
//   "allOf": [ { "$ref": "#/definitions/Response" }, {
//     "type": "object",
//     "description": "Response to `dataBreakpointInfo` request.",
//     "properties": {
//       "body": {
//         "type": "object",
//         "properties": {
//           "dataId": {
//             "type": [ "string", "null" ],
//             "description": "An identifier for the data on which a data
//             breakpoint can be registered with the `setDataBreakpoints`
//             request or null if no data breakpoint is available. If a
//             `variablesReference` or `frameId` is passed, the `dataId` is
//             valid in the current suspended state, otherwise it's valid
//             indefinitely. See 'Lifetime of Object References' in the Overview
//             section for details. Breakpoints set using the `dataId` in the
//             `setDataBreakpoints` request may outlive the lifetime of the
//             associated `dataId`."
//           },
//           "description": {
//             "type": "string",
//             "description": "UI string that describes on what data the
//             breakpoint is set on or why a data breakpoint is not available."
//           },
//           "accessTypes": {
//             "type": "array",
//             "items": {
//               "$ref": "#/definitions/DataBreakpointAccessType"
//             },
//             "description": "Attribute lists the available access types for a
//             potential data breakpoint. A UI client could surface this
//             information."
//           },
//           "canPersist": {
//             "type": "boolean",
//             "description": "Attribute indicates that a potential data
//             breakpoint could be persisted across sessions."
//           }
//         },
//         "required": [ "dataId", "description" ]
//       }
//     },
//     "required": [ "body" ]
//   }]
// }
void DataBreakpointInfoRequestHandler::operator()(
    const llvm::json::Object &request) const {
  llvm::json::Object response;
  FillResponse(request, response);
  llvm::json::Object body;
  lldb::SBError error;
  llvm::json::Array accessTypes{"read", "write", "readWrite"};
  const auto *arguments = request.getObject("arguments");
  const auto variablesReference =
      GetInteger<uint64_t>(arguments, "variablesReference").value_or(0);
  llvm::StringRef name = GetString(arguments, "name").value_or("");
  lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
  lldb::SBValue variable = dap.variables.FindVariable(variablesReference, name);
  std::string addr, size;

  if (variable.IsValid()) {
    lldb::addr_t load_addr = variable.GetLoadAddress();
    size_t byte_size = variable.GetByteSize();
    if (load_addr == LLDB_INVALID_ADDRESS) {
      body.try_emplace("dataId", nullptr);
      body.try_emplace("description",
                       "does not exist in memory, its location is " +
                           std::string(variable.GetLocation()));
    } else if (byte_size == 0) {
      body.try_emplace("dataId", nullptr);
      body.try_emplace("description", "variable size is 0");
    } else {
      addr = llvm::utohexstr(load_addr);
      size = llvm::utostr(byte_size);
    }
  } else if (variablesReference == 0 && frame.IsValid()) {
    lldb::SBValue value = frame.EvaluateExpression(name.data());
    if (value.GetError().Fail()) {
      lldb::SBError error = value.GetError();
      const char *error_cstr = error.GetCString();
      body.try_emplace("dataId", nullptr);
      body.try_emplace("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())) {
            body.try_emplace("dataId", nullptr);
            body.try_emplace("description",
                             "memory region for address " + addr +
                                 " has no read or write permissions");
          }
        }
      } else {
        body.try_emplace("dataId", nullptr);
        body.try_emplace("description",
                         "unable to get byte size for expression: " +
                             name.str());
      }
    }
  } else {
    body.try_emplace("dataId", nullptr);
    body.try_emplace("description", "variable not found: " + name.str());
  }

  if (!body.getObject("dataId")) {
    body.try_emplace("dataId", addr + "/" + size);
    body.try_emplace("accessTypes", std::move(accessTypes));
    body.try_emplace("description",
                     size + " bytes at " + addr + " " + name.str());
  }
  response.try_emplace("body", std::move(body));
  dap.SendJSON(llvm::json::Value(std::move(response)));
}

} // namespace lldb_dap
