//===-- ExceptionInfoRequestHandler.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/SBStream.h"

namespace lldb_dap {

// "ExceptionInfoRequest": {
//   "allOf": [ { "$ref": "#/definitions/Request" }, {
//     "type": "object",
//     "description": "Retrieves the details of the exception that
//     caused this event to be raised. Clients should only call this request if
//     the corresponding capability `supportsExceptionInfoRequest` is true.",
//     "properties": {
//       "command": {
//         "type": "string",
//         "enum": [ "exceptionInfo" ]
//       },
//       "arguments": {
//         "$ref": "#/definitions/ExceptionInfoArguments"
//       }
//     },
//     "required": [ "command", "arguments"  ]
//   }]
// },
// "ExceptionInfoArguments": {
//   "type": "object",
//   "description": "Arguments for `exceptionInfo` request.",
//   "properties": {
//     "threadId": {
//       "type": "integer",
//       "description": "Thread for which exception information should be
//       retrieved."
//     }
//   },
//   "required": [ "threadId" ]
// },
// "ExceptionInfoResponse": {
//   "allOf": [ { "$ref": "#/definitions/Response" }, {
//     "type": "object",
//     "description": "Response to `exceptionInfo` request.",
//     "properties": {
//       "body": {
//         "type": "object",
//         "properties": {
//           "exceptionId": {
//             "type": "string",
//             "description": "ID of the exception that was thrown."
//           },
//           "description": {
//             "type": "string",
//             "description": "Descriptive text for the exception."
//           },
//           "breakMode": {
//          "$ref": "#/definitions/ExceptionBreakMode",
//            "description": "Mode that caused the exception notification to
//            be raised."
//           },
//           "details": {
//             "$ref": "#/definitions/ExceptionDetails",
//            "description": "Detailed information about the exception."
//           }
//         },
//         "required": [ "exceptionId", "breakMode" ]
//       }
//     },
//     "required": [ "body" ]
//   }]
// }
// "ExceptionDetails": {
//   "type": "object",
//   "description": "Detailed information about an exception that has
//   occurred.", "properties": {
//     "message": {
//       "type": "string",
//       "description": "Message contained in the exception."
//     },
//     "typeName": {
//       "type": "string",
//       "description": "Short type name of the exception object."
//     },
//     "fullTypeName": {
//       "type": "string",
//       "description": "Fully-qualified type name of the exception object."
//     },
//     "evaluateName": {
//       "type": "string",
//       "description": "An expression that can be evaluated in the current
//       scope to obtain the exception object."
//     },
//     "stackTrace": {
//       "type": "string",
//       "description": "Stack trace at the time the exception was thrown."
//     },
//     "innerException": {
//       "type": "array",
//       "items": {
//         "$ref": "#/definitions/ExceptionDetails"
//       },
//       "description": "Details of the exception contained by this exception,
//       if any."
//     }
//   }
// },
void ExceptionInfoRequestHandler::operator()(
    const llvm::json::Object &request) const {
  llvm::json::Object response;
  FillResponse(request, response);
  const auto *arguments = request.getObject("arguments");
  llvm::json::Object body;
  lldb::SBThread thread = dap.GetLLDBThread(*arguments);
  if (thread.IsValid()) {
    auto stopReason = thread.GetStopReason();
    if (stopReason == lldb::eStopReasonSignal)
      body.try_emplace("exceptionId", "signal");
    else if (stopReason == lldb::eStopReasonBreakpoint) {
      ExceptionBreakpoint *exc_bp = dap.GetExceptionBPFromStopReason(thread);
      if (exc_bp) {
        EmplaceSafeString(body, "exceptionId", exc_bp->GetFilter());
        EmplaceSafeString(body, "description", exc_bp->GetLabel());
      } else {
        body.try_emplace("exceptionId", "exception");
      }
    } else {
      body.try_emplace("exceptionId", "exception");
    }
    if (!ObjectContainsKey(body, "description")) {
      char description[1024];
      if (thread.GetStopDescription(description, sizeof(description))) {
        EmplaceSafeString(body, "description", description);
      }
    }
    body.try_emplace("breakMode", "always");
    auto exception = thread.GetCurrentException();
    if (exception.IsValid()) {
      llvm::json::Object details;
      lldb::SBStream stream;
      if (exception.GetDescription(stream)) {
        EmplaceSafeString(details, "message", stream.GetData());
      }

      auto exceptionBacktrace = thread.GetCurrentExceptionBacktrace();
      if (exceptionBacktrace.IsValid()) {
        lldb::SBStream stream;
        exceptionBacktrace.GetDescription(stream);
        for (uint32_t i = 0; i < exceptionBacktrace.GetNumFrames(); i++) {
          lldb::SBFrame frame = exceptionBacktrace.GetFrameAtIndex(i);
          frame.GetDescription(stream);
        }
        EmplaceSafeString(details, "stackTrace", stream.GetData());
      }

      body.try_emplace("details", std::move(details));
    }
    // auto excInfoCount = thread.GetStopReasonDataCount();
    // for (auto i=0; i<excInfoCount; ++i) {
    //   uint64_t exc_data = thread.GetStopReasonDataAtIndex(i);
    // }
  } else {
    response["success"] = llvm::json::Value(false);
  }
  response.try_emplace("body", std::move(body));
  dap.SendJSON(llvm::json::Value(std::move(response)));
}
} // namespace lldb_dap
