//===-- SBBreakpoint.cpp ----------------------------------------*- C++ -*-===//
//
// 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 "lldb/API/SBBreakpoint.h"
#include "SBReproducerPrivate.h"
#include "lldb/API/SBBreakpointLocation.h"
#include "lldb/API/SBDebugger.h"
#include "lldb/API/SBEvent.h"
#include "lldb/API/SBProcess.h"
#include "lldb/API/SBStream.h"
#include "lldb/API/SBStringList.h"
#include "lldb/API/SBThread.h"

#include "lldb/Breakpoint/Breakpoint.h"
#include "lldb/Breakpoint/BreakpointIDList.h"
#include "lldb/Breakpoint/BreakpointLocation.h"
#include "lldb/Breakpoint/BreakpointResolver.h"
#include "lldb/Breakpoint/BreakpointResolverScripted.h"
#include "lldb/Breakpoint/StoppointCallbackContext.h"
#include "lldb/Core/Address.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Core/StreamFile.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/ScriptInterpreter.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/SectionLoadList.h"
#include "lldb/Target/Target.h"
#include "lldb/Target/Thread.h"
#include "lldb/Target/ThreadSpec.h"
#include "lldb/Utility/Stream.h"

#include "SBBreakpointOptionCommon.h"

#include "lldb/lldb-enumerations.h"

#include "llvm/ADT/STLExtras.h"

using namespace lldb;
using namespace lldb_private;

SBBreakpoint::SBBreakpoint() { LLDB_RECORD_CONSTRUCTOR_NO_ARGS(SBBreakpoint); }

SBBreakpoint::SBBreakpoint(const SBBreakpoint &rhs)
    : m_opaque_wp(rhs.m_opaque_wp) {
  LLDB_RECORD_CONSTRUCTOR(SBBreakpoint, (const lldb::SBBreakpoint &), rhs);
}

SBBreakpoint::SBBreakpoint(const lldb::BreakpointSP &bp_sp)
    : m_opaque_wp(bp_sp) {
  LLDB_RECORD_CONSTRUCTOR(SBBreakpoint, (const lldb::BreakpointSP &), bp_sp);
}

SBBreakpoint::~SBBreakpoint() = default;

const SBBreakpoint &SBBreakpoint::operator=(const SBBreakpoint &rhs) {
  LLDB_RECORD_METHOD(const lldb::SBBreakpoint &,
                     SBBreakpoint, operator=,(const lldb::SBBreakpoint &), rhs);

  m_opaque_wp = rhs.m_opaque_wp;
  return LLDB_RECORD_RESULT(*this);
}

bool SBBreakpoint::operator==(const lldb::SBBreakpoint &rhs) {
  LLDB_RECORD_METHOD(
      bool, SBBreakpoint, operator==,(const lldb::SBBreakpoint &), rhs);

  return m_opaque_wp.lock() == rhs.m_opaque_wp.lock();
}

bool SBBreakpoint::operator!=(const lldb::SBBreakpoint &rhs) {
  LLDB_RECORD_METHOD(
      bool, SBBreakpoint, operator!=,(const lldb::SBBreakpoint &), rhs);

  return m_opaque_wp.lock() != rhs.m_opaque_wp.lock();
}

break_id_t SBBreakpoint::GetID() const {
  LLDB_RECORD_METHOD_CONST_NO_ARGS(lldb::break_id_t, SBBreakpoint, GetID);

  break_id_t break_id = LLDB_INVALID_BREAK_ID;
  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp)
    break_id = bkpt_sp->GetID();

  return break_id;
}

bool SBBreakpoint::IsValid() const {
  LLDB_RECORD_METHOD_CONST_NO_ARGS(bool, SBBreakpoint, IsValid);
  return this->operator bool();
}
SBBreakpoint::operator bool() const {
  LLDB_RECORD_METHOD_CONST_NO_ARGS(bool, SBBreakpoint, operator bool);

  BreakpointSP bkpt_sp = GetSP();
  if (!bkpt_sp)
    return false;
  else if (bkpt_sp->GetTarget().GetBreakpointByID(bkpt_sp->GetID()))
    return true;
  else
    return false;
}

void SBBreakpoint::ClearAllBreakpointSites() {
  LLDB_RECORD_METHOD_NO_ARGS(void, SBBreakpoint, ClearAllBreakpointSites);

  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    bkpt_sp->ClearAllBreakpointSites();
  }
}

SBBreakpointLocation SBBreakpoint::FindLocationByAddress(addr_t vm_addr) {
  LLDB_RECORD_METHOD(lldb::SBBreakpointLocation, SBBreakpoint,
                     FindLocationByAddress, (lldb::addr_t), vm_addr);

  SBBreakpointLocation sb_bp_location;

  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    if (vm_addr != LLDB_INVALID_ADDRESS) {
      std::lock_guard<std::recursive_mutex> guard(
          bkpt_sp->GetTarget().GetAPIMutex());
      Address address;
      Target &target = bkpt_sp->GetTarget();
      if (!target.GetSectionLoadList().ResolveLoadAddress(vm_addr, address)) {
        address.SetRawAddress(vm_addr);
      }
      sb_bp_location.SetLocation(bkpt_sp->FindLocationByAddress(address));
    }
  }
  return LLDB_RECORD_RESULT(sb_bp_location);
}

break_id_t SBBreakpoint::FindLocationIDByAddress(addr_t vm_addr) {
  LLDB_RECORD_METHOD(lldb::break_id_t, SBBreakpoint, FindLocationIDByAddress,
                     (lldb::addr_t), vm_addr);

  break_id_t break_id = LLDB_INVALID_BREAK_ID;
  BreakpointSP bkpt_sp = GetSP();

  if (bkpt_sp && vm_addr != LLDB_INVALID_ADDRESS) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    Address address;
    Target &target = bkpt_sp->GetTarget();
    if (!target.GetSectionLoadList().ResolveLoadAddress(vm_addr, address)) {
      address.SetRawAddress(vm_addr);
    }
    break_id = bkpt_sp->FindLocationIDByAddress(address);
  }

  return break_id;
}

SBBreakpointLocation SBBreakpoint::FindLocationByID(break_id_t bp_loc_id) {
  LLDB_RECORD_METHOD(lldb::SBBreakpointLocation, SBBreakpoint, FindLocationByID,
                     (lldb::break_id_t), bp_loc_id);

  SBBreakpointLocation sb_bp_location;
  BreakpointSP bkpt_sp = GetSP();

  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    sb_bp_location.SetLocation(bkpt_sp->FindLocationByID(bp_loc_id));
  }

  return LLDB_RECORD_RESULT(sb_bp_location);
}

SBBreakpointLocation SBBreakpoint::GetLocationAtIndex(uint32_t index) {
  LLDB_RECORD_METHOD(lldb::SBBreakpointLocation, SBBreakpoint,
                     GetLocationAtIndex, (uint32_t), index);

  SBBreakpointLocation sb_bp_location;
  BreakpointSP bkpt_sp = GetSP();

  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    sb_bp_location.SetLocation(bkpt_sp->GetLocationAtIndex(index));
  }

  return LLDB_RECORD_RESULT(sb_bp_location);
}

void SBBreakpoint::SetEnabled(bool enable) {
  LLDB_RECORD_METHOD(void, SBBreakpoint, SetEnabled, (bool), enable);

  BreakpointSP bkpt_sp = GetSP();

  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    bkpt_sp->SetEnabled(enable);
  }
}

bool SBBreakpoint::IsEnabled() {
  LLDB_RECORD_METHOD_NO_ARGS(bool, SBBreakpoint, IsEnabled);

  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    return bkpt_sp->IsEnabled();
  } else
    return false;
}

void SBBreakpoint::SetOneShot(bool one_shot) {
  LLDB_RECORD_METHOD(void, SBBreakpoint, SetOneShot, (bool), one_shot);

  BreakpointSP bkpt_sp = GetSP();

  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    bkpt_sp->SetOneShot(one_shot);
  }
}

bool SBBreakpoint::IsOneShot() const {
  LLDB_RECORD_METHOD_CONST_NO_ARGS(bool, SBBreakpoint, IsOneShot);

  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    return bkpt_sp->IsOneShot();
  } else
    return false;
}

bool SBBreakpoint::IsInternal() {
  LLDB_RECORD_METHOD_NO_ARGS(bool, SBBreakpoint, IsInternal);

  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    return bkpt_sp->IsInternal();
  } else
    return false;
}

void SBBreakpoint::SetIgnoreCount(uint32_t count) {
  LLDB_RECORD_METHOD(void, SBBreakpoint, SetIgnoreCount, (uint32_t), count);

  BreakpointSP bkpt_sp = GetSP();

  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    bkpt_sp->SetIgnoreCount(count);
  }
}

void SBBreakpoint::SetCondition(const char *condition) {
  LLDB_RECORD_METHOD(void, SBBreakpoint, SetCondition, (const char *),
                     condition);

  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    bkpt_sp->SetCondition(condition);
  }
}

const char *SBBreakpoint::GetCondition() {
  LLDB_RECORD_METHOD_NO_ARGS(const char *, SBBreakpoint, GetCondition);

  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    return bkpt_sp->GetConditionText();
  }
  return nullptr;
}

void SBBreakpoint::SetAutoContinue(bool auto_continue) {
  LLDB_RECORD_METHOD(void, SBBreakpoint, SetAutoContinue, (bool),
                     auto_continue);

  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    bkpt_sp->SetAutoContinue(auto_continue);
  }
}

bool SBBreakpoint::GetAutoContinue() {
  LLDB_RECORD_METHOD_NO_ARGS(bool, SBBreakpoint, GetAutoContinue);

  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    return bkpt_sp->IsAutoContinue();
  }
  return false;
}

uint32_t SBBreakpoint::GetHitCount() const {
  LLDB_RECORD_METHOD_CONST_NO_ARGS(uint32_t, SBBreakpoint, GetHitCount);

  uint32_t count = 0;
  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    count = bkpt_sp->GetHitCount();
  }

  return count;
}

uint32_t SBBreakpoint::GetIgnoreCount() const {
  LLDB_RECORD_METHOD_CONST_NO_ARGS(uint32_t, SBBreakpoint, GetIgnoreCount);

  uint32_t count = 0;
  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    count = bkpt_sp->GetIgnoreCount();
  }

  return count;
}

void SBBreakpoint::SetThreadID(tid_t tid) {
  LLDB_RECORD_METHOD(void, SBBreakpoint, SetThreadID, (lldb::tid_t), tid);

  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    bkpt_sp->SetThreadID(tid);
  }
}

tid_t SBBreakpoint::GetThreadID() {
  LLDB_RECORD_METHOD_NO_ARGS(lldb::tid_t, SBBreakpoint, GetThreadID);

  tid_t tid = LLDB_INVALID_THREAD_ID;
  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    tid = bkpt_sp->GetThreadID();
  }

  return tid;
}

void SBBreakpoint::SetThreadIndex(uint32_t index) {
  LLDB_RECORD_METHOD(void, SBBreakpoint, SetThreadIndex, (uint32_t), index);

  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    bkpt_sp->GetOptions()->GetThreadSpec()->SetIndex(index);
  }
}

uint32_t SBBreakpoint::GetThreadIndex() const {
  LLDB_RECORD_METHOD_CONST_NO_ARGS(uint32_t, SBBreakpoint, GetThreadIndex);

  uint32_t thread_idx = UINT32_MAX;
  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    const ThreadSpec *thread_spec =
        bkpt_sp->GetOptions()->GetThreadSpecNoCreate();
    if (thread_spec != nullptr)
      thread_idx = thread_spec->GetIndex();
  }

  return thread_idx;
}

void SBBreakpoint::SetThreadName(const char *thread_name) {
  LLDB_RECORD_METHOD(void, SBBreakpoint, SetThreadName, (const char *),
                     thread_name);

  BreakpointSP bkpt_sp = GetSP();

  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    bkpt_sp->GetOptions()->GetThreadSpec()->SetName(thread_name);
  }
}

const char *SBBreakpoint::GetThreadName() const {
  LLDB_RECORD_METHOD_CONST_NO_ARGS(const char *, SBBreakpoint, GetThreadName);

  const char *name = nullptr;
  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    const ThreadSpec *thread_spec =
        bkpt_sp->GetOptions()->GetThreadSpecNoCreate();
    if (thread_spec != nullptr)
      name = thread_spec->GetName();
  }

  return name;
}

void SBBreakpoint::SetQueueName(const char *queue_name) {
  LLDB_RECORD_METHOD(void, SBBreakpoint, SetQueueName, (const char *),
                     queue_name);

  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    bkpt_sp->GetOptions()->GetThreadSpec()->SetQueueName(queue_name);
  }
}

const char *SBBreakpoint::GetQueueName() const {
  LLDB_RECORD_METHOD_CONST_NO_ARGS(const char *, SBBreakpoint, GetQueueName);

  const char *name = nullptr;
  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    const ThreadSpec *thread_spec =
        bkpt_sp->GetOptions()->GetThreadSpecNoCreate();
    if (thread_spec)
      name = thread_spec->GetQueueName();
  }

  return name;
}

size_t SBBreakpoint::GetNumResolvedLocations() const {
  LLDB_RECORD_METHOD_CONST_NO_ARGS(size_t, SBBreakpoint,
                                   GetNumResolvedLocations);

  size_t num_resolved = 0;
  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    num_resolved = bkpt_sp->GetNumResolvedLocations();
  }
  return num_resolved;
}

size_t SBBreakpoint::GetNumLocations() const {
  LLDB_RECORD_METHOD_CONST_NO_ARGS(size_t, SBBreakpoint, GetNumLocations);

  BreakpointSP bkpt_sp = GetSP();
  size_t num_locs = 0;
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    num_locs = bkpt_sp->GetNumLocations();
  }
  return num_locs;
}

void SBBreakpoint::SetCommandLineCommands(SBStringList &commands) {
  LLDB_RECORD_METHOD(void, SBBreakpoint, SetCommandLineCommands,
                     (lldb::SBStringList &), commands);

  BreakpointSP bkpt_sp = GetSP();
  if (!bkpt_sp)
    return;
  if (commands.GetSize() == 0)
    return;

  std::lock_guard<std::recursive_mutex> guard(
      bkpt_sp->GetTarget().GetAPIMutex());
  std::unique_ptr<BreakpointOptions::CommandData> cmd_data_up(
      new BreakpointOptions::CommandData(*commands, eScriptLanguageNone));

  bkpt_sp->GetOptions()->SetCommandDataCallback(cmd_data_up);
}

bool SBBreakpoint::GetCommandLineCommands(SBStringList &commands) {
  LLDB_RECORD_METHOD(bool, SBBreakpoint, GetCommandLineCommands,
                     (lldb::SBStringList &), commands);

  BreakpointSP bkpt_sp = GetSP();
  if (!bkpt_sp)
    return false;
  StringList command_list;
  bool has_commands =
      bkpt_sp->GetOptions()->GetCommandLineCallbacks(command_list);
  if (has_commands)
    commands.AppendList(command_list);
  return has_commands;
}

bool SBBreakpoint::GetDescription(SBStream &s) {
  LLDB_RECORD_METHOD(bool, SBBreakpoint, GetDescription, (lldb::SBStream &), s);

  return GetDescription(s, true);
}

bool SBBreakpoint::GetDescription(SBStream &s, bool include_locations) {
  LLDB_RECORD_METHOD(bool, SBBreakpoint, GetDescription,
                     (lldb::SBStream &, bool), s, include_locations);

  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    s.Printf("SBBreakpoint: id = %i, ", bkpt_sp->GetID());
    bkpt_sp->GetResolverDescription(s.get());
    bkpt_sp->GetFilterDescription(s.get());
    if (include_locations) {
      const size_t num_locations = bkpt_sp->GetNumLocations();
      s.Printf(", locations = %" PRIu64, (uint64_t)num_locations);
    }
    return true;
  }
  s.Printf("No value");
  return false;
}

SBError SBBreakpoint::AddLocation(SBAddress &address) {
  LLDB_RECORD_METHOD(lldb::SBError, SBBreakpoint, AddLocation,
                     (lldb::SBAddress &), address);

  BreakpointSP bkpt_sp = GetSP();
  SBError error;

  if (!address.IsValid()) {
    error.SetErrorString("Can't add an invalid address.");
    return LLDB_RECORD_RESULT(error);
  }

  if (!bkpt_sp) {
    error.SetErrorString("No breakpoint to add a location to.");
    return LLDB_RECORD_RESULT(error);
  }

  if (!llvm::isa<BreakpointResolverScripted>(bkpt_sp->GetResolver().get())) {
    error.SetErrorString("Only a scripted resolver can add locations.");
    return LLDB_RECORD_RESULT(error);
  }

  if (bkpt_sp->GetSearchFilter()->AddressPasses(address.ref()))
    bkpt_sp->AddLocation(address.ref());
  else {
    StreamString s;
    address.get()->Dump(&s, &bkpt_sp->GetTarget(),
                        Address::DumpStyleModuleWithFileAddress);
    error.SetErrorStringWithFormat("Address: %s didn't pass the filter.",
                                   s.GetData());
  }
  return LLDB_RECORD_RESULT(error);
}

void SBBreakpoint ::SetCallback(SBBreakpointHitCallback callback, void *baton) {
  LLDB_RECORD_DUMMY(void, SBBreakpoint, SetCallback,
                    (lldb::SBBreakpointHitCallback, void *), callback, baton);

  BreakpointSP bkpt_sp = GetSP();

  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    BatonSP baton_sp(new SBBreakpointCallbackBaton(callback, baton));
    bkpt_sp->SetCallback(SBBreakpointCallbackBaton
      ::PrivateBreakpointHitCallback, baton_sp,
                         false);
  }
}

void SBBreakpoint::SetScriptCallbackFunction(
    const char *callback_function_name) {
  LLDB_RECORD_METHOD(void, SBBreakpoint, SetScriptCallbackFunction,
                     (const char *), callback_function_name);

  BreakpointSP bkpt_sp = GetSP();

  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    BreakpointOptions *bp_options = bkpt_sp->GetOptions();
    bkpt_sp->GetTarget()
        .GetDebugger()
        .GetScriptInterpreter()
        ->SetBreakpointCommandCallbackFunction(bp_options,
                                               callback_function_name);
  }
}

SBError SBBreakpoint::SetScriptCallbackBody(const char *callback_body_text) {
  LLDB_RECORD_METHOD(lldb::SBError, SBBreakpoint, SetScriptCallbackBody,
                     (const char *), callback_body_text);

  BreakpointSP bkpt_sp = GetSP();

  SBError sb_error;
  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    BreakpointOptions *bp_options = bkpt_sp->GetOptions();
    Status error =
        bkpt_sp->GetTarget()
            .GetDebugger()
            .GetScriptInterpreter()
            ->SetBreakpointCommandCallback(bp_options, callback_body_text);
    sb_error.SetError(error);
  } else
    sb_error.SetErrorString("invalid breakpoint");

  return LLDB_RECORD_RESULT(sb_error);
}

bool SBBreakpoint::AddName(const char *new_name) {
  LLDB_RECORD_METHOD(bool, SBBreakpoint, AddName, (const char *), new_name);

  BreakpointSP bkpt_sp = GetSP();

  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    Status error; // Think I'm just going to swallow the error here, it's
                  // probably more annoying to have to provide it.
    bkpt_sp->GetTarget().AddNameToBreakpoint(bkpt_sp, new_name, error);
    if (error.Fail())
      return false;
  }

  return true;
}

void SBBreakpoint::RemoveName(const char *name_to_remove) {
  LLDB_RECORD_METHOD(void, SBBreakpoint, RemoveName, (const char *),
                     name_to_remove);

  BreakpointSP bkpt_sp = GetSP();

  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    bkpt_sp->GetTarget().RemoveNameFromBreakpoint(bkpt_sp,
                                                  ConstString(name_to_remove));
  }
}

bool SBBreakpoint::MatchesName(const char *name) {
  LLDB_RECORD_METHOD(bool, SBBreakpoint, MatchesName, (const char *), name);

  BreakpointSP bkpt_sp = GetSP();

  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    return bkpt_sp->MatchesName(name);
  }

  return false;
}

void SBBreakpoint::GetNames(SBStringList &names) {
  LLDB_RECORD_METHOD(void, SBBreakpoint, GetNames, (lldb::SBStringList &),
                     names);

  BreakpointSP bkpt_sp = GetSP();

  if (bkpt_sp) {
    std::lock_guard<std::recursive_mutex> guard(
        bkpt_sp->GetTarget().GetAPIMutex());
    std::vector<std::string> names_vec;
    bkpt_sp->GetNames(names_vec);
    for (std::string name : names_vec) {
      names.AppendString(name.c_str());
    }
  }
}

bool SBBreakpoint::EventIsBreakpointEvent(const lldb::SBEvent &event) {
  LLDB_RECORD_STATIC_METHOD(bool, SBBreakpoint, EventIsBreakpointEvent,
                            (const lldb::SBEvent &), event);

  return Breakpoint::BreakpointEventData::GetEventDataFromEvent(event.get()) !=
         nullptr;
}

BreakpointEventType
SBBreakpoint::GetBreakpointEventTypeFromEvent(const SBEvent &event) {
  LLDB_RECORD_STATIC_METHOD(lldb::BreakpointEventType, SBBreakpoint,
                            GetBreakpointEventTypeFromEvent,
                            (const lldb::SBEvent &), event);

  if (event.IsValid())
    return Breakpoint::BreakpointEventData::GetBreakpointEventTypeFromEvent(
        event.GetSP());
  return eBreakpointEventTypeInvalidType;
}

SBBreakpoint SBBreakpoint::GetBreakpointFromEvent(const lldb::SBEvent &event) {
  LLDB_RECORD_STATIC_METHOD(lldb::SBBreakpoint, SBBreakpoint,
                            GetBreakpointFromEvent, (const lldb::SBEvent &),
                            event);

  if (event.IsValid())
    return LLDB_RECORD_RESULT(
        SBBreakpoint(Breakpoint::BreakpointEventData::GetBreakpointFromEvent(
            event.GetSP())));
  return LLDB_RECORD_RESULT(SBBreakpoint());
}

SBBreakpointLocation
SBBreakpoint::GetBreakpointLocationAtIndexFromEvent(const lldb::SBEvent &event,
                                                    uint32_t loc_idx) {
  LLDB_RECORD_STATIC_METHOD(lldb::SBBreakpointLocation, SBBreakpoint,
                            GetBreakpointLocationAtIndexFromEvent,
                            (const lldb::SBEvent &, uint32_t), event, loc_idx);

  SBBreakpointLocation sb_breakpoint_loc;
  if (event.IsValid())
    sb_breakpoint_loc.SetLocation(
        Breakpoint::BreakpointEventData::GetBreakpointLocationAtIndexFromEvent(
            event.GetSP(), loc_idx));
  return LLDB_RECORD_RESULT(sb_breakpoint_loc);
}

uint32_t
SBBreakpoint::GetNumBreakpointLocationsFromEvent(const lldb::SBEvent &event) {
  LLDB_RECORD_STATIC_METHOD(uint32_t, SBBreakpoint,
                            GetNumBreakpointLocationsFromEvent,
                            (const lldb::SBEvent &), event);

  uint32_t num_locations = 0;
  if (event.IsValid())
    num_locations =
        (Breakpoint::BreakpointEventData::GetNumBreakpointLocationsFromEvent(
            event.GetSP()));
  return num_locations;
}

bool SBBreakpoint::IsHardware() const {
  LLDB_RECORD_METHOD_CONST_NO_ARGS(bool, SBBreakpoint, IsHardware);

  BreakpointSP bkpt_sp = GetSP();
  if (bkpt_sp)
    return bkpt_sp->IsHardware();
  return false;
}

BreakpointSP SBBreakpoint::GetSP() const { return m_opaque_wp.lock(); }

// This is simple collection of breakpoint id's and their target.
class SBBreakpointListImpl {
public:
  SBBreakpointListImpl(lldb::TargetSP target_sp) : m_target_wp() {
    if (target_sp && target_sp->IsValid())
      m_target_wp = target_sp;
  }

  ~SBBreakpointListImpl() = default;

  size_t GetSize() { return m_break_ids.size(); }

  BreakpointSP GetBreakpointAtIndex(size_t idx) {
    if (idx >= m_break_ids.size())
      return BreakpointSP();
    TargetSP target_sp = m_target_wp.lock();
    if (!target_sp)
      return BreakpointSP();
    lldb::break_id_t bp_id = m_break_ids[idx];
    return target_sp->GetBreakpointList().FindBreakpointByID(bp_id);
  }

  BreakpointSP FindBreakpointByID(lldb::break_id_t desired_id) {
    TargetSP target_sp = m_target_wp.lock();
    if (!target_sp)
      return BreakpointSP();

    for (lldb::break_id_t &break_id : m_break_ids) {
      if (break_id == desired_id)
        return target_sp->GetBreakpointList().FindBreakpointByID(break_id);
    }
    return BreakpointSP();
  }

  bool Append(BreakpointSP bkpt) {
    TargetSP target_sp = m_target_wp.lock();
    if (!target_sp || !bkpt)
      return false;
    if (bkpt->GetTargetSP() != target_sp)
      return false;
    m_break_ids.push_back(bkpt->GetID());
    return true;
  }

  bool AppendIfUnique(BreakpointSP bkpt) {
    TargetSP target_sp = m_target_wp.lock();
    if (!target_sp || !bkpt)
      return false;
    if (bkpt->GetTargetSP() != target_sp)
      return false;
    lldb::break_id_t bp_id = bkpt->GetID();
    if (find(m_break_ids.begin(), m_break_ids.end(), bp_id) ==
        m_break_ids.end())
      return false;

    m_break_ids.push_back(bkpt->GetID());
    return true;
  }

  bool AppendByID(lldb::break_id_t id) {
    TargetSP target_sp = m_target_wp.lock();
    if (!target_sp)
      return false;
    if (id == LLDB_INVALID_BREAK_ID)
      return false;
    m_break_ids.push_back(id);
    return true;
  }

  void Clear() { m_break_ids.clear(); }

  void CopyToBreakpointIDList(lldb_private::BreakpointIDList &bp_list) {
    for (lldb::break_id_t id : m_break_ids) {
      bp_list.AddBreakpointID(BreakpointID(id));
    }
  }

  TargetSP GetTarget() { return m_target_wp.lock(); }

private:
  std::vector<lldb::break_id_t> m_break_ids;
  TargetWP m_target_wp;
};

SBBreakpointList::SBBreakpointList(SBTarget &target)
    : m_opaque_sp(new SBBreakpointListImpl(target.GetSP())) {
  LLDB_RECORD_CONSTRUCTOR(SBBreakpointList, (lldb::SBTarget &), target);
}

SBBreakpointList::~SBBreakpointList() {}

size_t SBBreakpointList::GetSize() const {
  LLDB_RECORD_METHOD_CONST_NO_ARGS(size_t, SBBreakpointList, GetSize);

  if (!m_opaque_sp)
    return 0;
  else
    return m_opaque_sp->GetSize();
}

SBBreakpoint SBBreakpointList::GetBreakpointAtIndex(size_t idx) {
  LLDB_RECORD_METHOD(lldb::SBBreakpoint, SBBreakpointList, GetBreakpointAtIndex,
                     (size_t), idx);

  if (!m_opaque_sp)
    return LLDB_RECORD_RESULT(SBBreakpoint());

  BreakpointSP bkpt_sp = m_opaque_sp->GetBreakpointAtIndex(idx);
  return LLDB_RECORD_RESULT(SBBreakpoint(bkpt_sp));
}

SBBreakpoint SBBreakpointList::FindBreakpointByID(lldb::break_id_t id) {
  LLDB_RECORD_METHOD(lldb::SBBreakpoint, SBBreakpointList, FindBreakpointByID,
                     (lldb::break_id_t), id);

  if (!m_opaque_sp)
    return LLDB_RECORD_RESULT(SBBreakpoint());
  BreakpointSP bkpt_sp = m_opaque_sp->FindBreakpointByID(id);
  return LLDB_RECORD_RESULT(SBBreakpoint(bkpt_sp));
}

void SBBreakpointList::Append(const SBBreakpoint &sb_bkpt) {
  LLDB_RECORD_METHOD(void, SBBreakpointList, Append,
                     (const lldb::SBBreakpoint &), sb_bkpt);

  if (!sb_bkpt.IsValid())
    return;
  if (!m_opaque_sp)
    return;
  m_opaque_sp->Append(sb_bkpt.m_opaque_wp.lock());
}

void SBBreakpointList::AppendByID(lldb::break_id_t id) {
  LLDB_RECORD_METHOD(void, SBBreakpointList, AppendByID, (lldb::break_id_t),
                     id);

  if (!m_opaque_sp)
    return;
  m_opaque_sp->AppendByID(id);
}

bool SBBreakpointList::AppendIfUnique(const SBBreakpoint &sb_bkpt) {
  LLDB_RECORD_METHOD(bool, SBBreakpointList, AppendIfUnique,
                     (const lldb::SBBreakpoint &), sb_bkpt);

  if (!sb_bkpt.IsValid())
    return false;
  if (!m_opaque_sp)
    return false;
  return m_opaque_sp->AppendIfUnique(sb_bkpt.GetSP());
}

void SBBreakpointList::Clear() {
  LLDB_RECORD_METHOD_NO_ARGS(void, SBBreakpointList, Clear);

  if (m_opaque_sp)
    m_opaque_sp->Clear();
}

void SBBreakpointList::CopyToBreakpointIDList(
    lldb_private::BreakpointIDList &bp_id_list) {
  if (m_opaque_sp)
    m_opaque_sp->CopyToBreakpointIDList(bp_id_list);
}

namespace lldb_private {
namespace repro {

template <>
void RegisterMethods<SBBreakpoint>(Registry &R) {
  LLDB_REGISTER_CONSTRUCTOR(SBBreakpoint, ());
  LLDB_REGISTER_CONSTRUCTOR(SBBreakpoint, (const lldb::SBBreakpoint &));
  LLDB_REGISTER_CONSTRUCTOR(SBBreakpoint, (const lldb::BreakpointSP &));
  LLDB_REGISTER_METHOD(const lldb::SBBreakpoint &,
                       SBBreakpoint, operator=,(const lldb::SBBreakpoint &));
  LLDB_REGISTER_METHOD(bool,
                       SBBreakpoint, operator==,(const lldb::SBBreakpoint &));
  LLDB_REGISTER_METHOD(bool,
                       SBBreakpoint, operator!=,(const lldb::SBBreakpoint &));
  LLDB_REGISTER_METHOD_CONST(lldb::break_id_t, SBBreakpoint, GetID, ());
  LLDB_REGISTER_METHOD_CONST(bool, SBBreakpoint, IsValid, ());
  LLDB_REGISTER_METHOD_CONST(bool, SBBreakpoint, operator bool, ());
  LLDB_REGISTER_METHOD(void, SBBreakpoint, ClearAllBreakpointSites, ());
  LLDB_REGISTER_METHOD(lldb::SBBreakpointLocation, SBBreakpoint,
                       FindLocationByAddress, (lldb::addr_t));
  LLDB_REGISTER_METHOD(lldb::break_id_t, SBBreakpoint,
                       FindLocationIDByAddress, (lldb::addr_t));
  LLDB_REGISTER_METHOD(lldb::SBBreakpointLocation, SBBreakpoint,
                       FindLocationByID, (lldb::break_id_t));
  LLDB_REGISTER_METHOD(lldb::SBBreakpointLocation, SBBreakpoint,
                       GetLocationAtIndex, (uint32_t));
  LLDB_REGISTER_METHOD(void, SBBreakpoint, SetEnabled, (bool));
  LLDB_REGISTER_METHOD(bool, SBBreakpoint, IsEnabled, ());
  LLDB_REGISTER_METHOD(void, SBBreakpoint, SetOneShot, (bool));
  LLDB_REGISTER_METHOD_CONST(bool, SBBreakpoint, IsOneShot, ());
  LLDB_REGISTER_METHOD(bool, SBBreakpoint, IsInternal, ());
  LLDB_REGISTER_METHOD(void, SBBreakpoint, SetIgnoreCount, (uint32_t));
  LLDB_REGISTER_METHOD(void, SBBreakpoint, SetCondition, (const char *));
  LLDB_REGISTER_METHOD(const char *, SBBreakpoint, GetCondition, ());
  LLDB_REGISTER_METHOD(void, SBBreakpoint, SetAutoContinue, (bool));
  LLDB_REGISTER_METHOD(bool, SBBreakpoint, GetAutoContinue, ());
  LLDB_REGISTER_METHOD_CONST(uint32_t, SBBreakpoint, GetHitCount, ());
  LLDB_REGISTER_METHOD_CONST(uint32_t, SBBreakpoint, GetIgnoreCount, ());
  LLDB_REGISTER_METHOD(void, SBBreakpoint, SetThreadID, (lldb::tid_t));
  LLDB_REGISTER_METHOD(lldb::tid_t, SBBreakpoint, GetThreadID, ());
  LLDB_REGISTER_METHOD(void, SBBreakpoint, SetThreadIndex, (uint32_t));
  LLDB_REGISTER_METHOD_CONST(uint32_t, SBBreakpoint, GetThreadIndex, ());
  LLDB_REGISTER_METHOD(void, SBBreakpoint, SetThreadName, (const char *));
  LLDB_REGISTER_METHOD_CONST(const char *, SBBreakpoint, GetThreadName, ());
  LLDB_REGISTER_METHOD(void, SBBreakpoint, SetQueueName, (const char *));
  LLDB_REGISTER_METHOD_CONST(const char *, SBBreakpoint, GetQueueName, ());
  LLDB_REGISTER_METHOD_CONST(size_t, SBBreakpoint, GetNumResolvedLocations,
                             ());
  LLDB_REGISTER_METHOD_CONST(size_t, SBBreakpoint, GetNumLocations, ());
  LLDB_REGISTER_METHOD(void, SBBreakpoint, SetCommandLineCommands,
                       (lldb::SBStringList &));
  LLDB_REGISTER_METHOD(bool, SBBreakpoint, GetCommandLineCommands,
                       (lldb::SBStringList &));
  LLDB_REGISTER_METHOD(bool, SBBreakpoint, GetDescription,
                       (lldb::SBStream &));
  LLDB_REGISTER_METHOD(bool, SBBreakpoint, GetDescription,
                       (lldb::SBStream &, bool));
  LLDB_REGISTER_METHOD(lldb::SBError, SBBreakpoint, AddLocation,
                       (lldb::SBAddress &));
  LLDB_REGISTER_METHOD(void, SBBreakpoint, SetScriptCallbackFunction,
                       (const char *));
  LLDB_REGISTER_METHOD(lldb::SBError, SBBreakpoint, SetScriptCallbackBody,
                       (const char *));
  LLDB_REGISTER_METHOD(bool, SBBreakpoint, AddName, (const char *));
  LLDB_REGISTER_METHOD(void, SBBreakpoint, RemoveName, (const char *));
  LLDB_REGISTER_METHOD(bool, SBBreakpoint, MatchesName, (const char *));
  LLDB_REGISTER_METHOD(void, SBBreakpoint, GetNames, (lldb::SBStringList &));
  LLDB_REGISTER_STATIC_METHOD(bool, SBBreakpoint, EventIsBreakpointEvent,
                              (const lldb::SBEvent &));
  LLDB_REGISTER_STATIC_METHOD(lldb::BreakpointEventType, SBBreakpoint,
                              GetBreakpointEventTypeFromEvent,
                              (const lldb::SBEvent &));
  LLDB_REGISTER_STATIC_METHOD(lldb::SBBreakpoint, SBBreakpoint,
                              GetBreakpointFromEvent,
                              (const lldb::SBEvent &));
  LLDB_REGISTER_STATIC_METHOD(lldb::SBBreakpointLocation, SBBreakpoint,
                              GetBreakpointLocationAtIndexFromEvent,
                              (const lldb::SBEvent &, uint32_t));
  LLDB_REGISTER_STATIC_METHOD(uint32_t, SBBreakpoint,
                              GetNumBreakpointLocationsFromEvent,
                              (const lldb::SBEvent &));
  LLDB_REGISTER_METHOD_CONST(bool, SBBreakpoint, IsHardware, ());
}

template <>
void RegisterMethods<SBBreakpointList>(Registry &R) {
  LLDB_REGISTER_CONSTRUCTOR(SBBreakpointList, (lldb::SBTarget &));
  LLDB_REGISTER_METHOD_CONST(size_t, SBBreakpointList, GetSize, ());
  LLDB_REGISTER_METHOD(lldb::SBBreakpoint, SBBreakpointList,
                       GetBreakpointAtIndex, (size_t));
  LLDB_REGISTER_METHOD(lldb::SBBreakpoint, SBBreakpointList,
                       FindBreakpointByID, (lldb::break_id_t));
  LLDB_REGISTER_METHOD(void, SBBreakpointList, Append,
                       (const lldb::SBBreakpoint &));
  LLDB_REGISTER_METHOD(void, SBBreakpointList, AppendByID,
                       (lldb::break_id_t));
  LLDB_REGISTER_METHOD(bool, SBBreakpointList, AppendIfUnique,
                       (const lldb::SBBreakpoint &));
  LLDB_REGISTER_METHOD(void, SBBreakpointList, Clear, ());
}

}
}
