/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file LICENSE.rst or https://cmake.org/licensing for details.  */
#include "cmDebuggerExceptionManager.h"

#include <utility>
#include <vector>

#include <cm3p/cppdap/optional.h>
#include <cm3p/cppdap/session.h>
#include <cm3p/cppdap/types.h>

#include "cmDebuggerProtocol.h"
#include "cmMessageType.h"

namespace cmDebugger {

cmDebuggerExceptionManager::cmDebuggerExceptionManager(
  dap::Session* dapSession)
  : DapSession(dapSession)
{
  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetExceptionBreakpoints
  DapSession->registerHandler(
    [&](dap::SetExceptionBreakpointsRequest const& request) {
      return HandleSetExceptionBreakpointsRequest(request);
    });

  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_ExceptionInfo
  DapSession->registerHandler([&](dap::ExceptionInfoRequest const& request) {
    (void)request;
    return HandleExceptionInfoRequest();
  });

  ExceptionMap[MessageType::AUTHOR_WARNING] =
    cmDebuggerExceptionFilter{ "AUTHOR_WARNING", "Warning (dev)" };
  ExceptionMap[MessageType::AUTHOR_ERROR] =
    cmDebuggerExceptionFilter{ "AUTHOR_ERROR", "Error (dev)" };
  ExceptionMap[MessageType::FATAL_ERROR] =
    cmDebuggerExceptionFilter{ "FATAL_ERROR", "Fatal error" };
  ExceptionMap[MessageType::INTERNAL_ERROR] =
    cmDebuggerExceptionFilter{ "INTERNAL_ERROR", "Internal error" };
  ExceptionMap[MessageType::MESSAGE] =
    cmDebuggerExceptionFilter{ "MESSAGE", "Other messages" };
  ExceptionMap[MessageType::WARNING] =
    cmDebuggerExceptionFilter{ "WARNING", "Warning" };
  ExceptionMap[MessageType::LOG] =
    cmDebuggerExceptionFilter{ "LOG", "Debug log" };
  ExceptionMap[MessageType::DEPRECATION_ERROR] =
    cmDebuggerExceptionFilter{ "DEPRECATION_ERROR", "Deprecation error" };
  ExceptionMap[MessageType::DEPRECATION_WARNING] =
    cmDebuggerExceptionFilter{ "DEPRECATION_WARNING", "Deprecation warning" };
  RaiseExceptions["AUTHOR_ERROR"] = true;
  RaiseExceptions["FATAL_ERROR"] = true;
  RaiseExceptions["INTERNAL_ERROR"] = true;
  RaiseExceptions["DEPRECATION_ERROR"] = true;
}

dap::SetExceptionBreakpointsResponse
cmDebuggerExceptionManager::HandleSetExceptionBreakpointsRequest(
  dap::SetExceptionBreakpointsRequest const& request)
{
  std::unique_lock<std::mutex> lock(Mutex);
  dap::SetExceptionBreakpointsResponse response;
  RaiseExceptions.clear();
  for (auto const& filter : request.filters) {
    RaiseExceptions[filter] = true;
  }

  return response;
}

dap::ExceptionInfoResponse
cmDebuggerExceptionManager::HandleExceptionInfoRequest()
{
  std::unique_lock<std::mutex> lock(Mutex);

  dap::ExceptionInfoResponse response;
  if (TheException.has_value()) {
    response.exceptionId = TheException->Id;
    response.breakMode = "always";
    response.description = TheException->Description;
    TheException = cm::nullopt;
  }
  return response;
}

void cmDebuggerExceptionManager::HandleInitializeRequest(
  dap::CMakeInitializeResponse& response)
{
  std::unique_lock<std::mutex> lock(Mutex);
  response.supportsExceptionInfoRequest = true;

  dap::array<dap::ExceptionBreakpointsFilter> exceptionBreakpointFilters;
  for (auto& pair : ExceptionMap) {
    dap::ExceptionBreakpointsFilter filter;
    filter.filter = pair.second.Filter;
    filter.label = pair.second.Label;
    filter.def = RaiseExceptions[filter.filter];
    exceptionBreakpointFilters.emplace_back(filter);
  }

  response.exceptionBreakpointFilters = exceptionBreakpointFilters;
}

cm::optional<dap::StoppedEvent>
cmDebuggerExceptionManager::RaiseExceptionIfAny(MessageType t,
                                                std::string const& text)
{
  cm::optional<dap::StoppedEvent> maybeStoppedEvent;
  std::unique_lock<std::mutex> lock(Mutex);
  if (RaiseExceptions[ExceptionMap[t].Filter]) {
    dap::StoppedEvent stoppedEvent;
    stoppedEvent.allThreadsStopped = true;
    stoppedEvent.reason = "exception";
    stoppedEvent.description = "Pause on exception";
    stoppedEvent.text = text;
    TheException = cmDebuggerException{ ExceptionMap[t].Filter, text };
    maybeStoppedEvent = std::move(stoppedEvent);
  }

  return maybeStoppedEvent;
}

void cmDebuggerExceptionManager::ClearAll()
{
  std::unique_lock<std::mutex> lock(Mutex);
  RaiseExceptions.clear();
}

} // namespace cmDebugger
