//===-- BreakpointLocationCollection.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/Breakpoint/BreakpointLocationCollection.h"
#include "lldb/Breakpoint/Breakpoint.h"
#include "lldb/Breakpoint/BreakpointLocation.h"
#include "lldb/Core/ModuleList.h"
#include "lldb/Target/Thread.h"
#include "lldb/Target/ThreadSpec.h"

using namespace lldb;
using namespace lldb_private;

// BreakpointLocationCollection constructor
BreakpointLocationCollection::BreakpointLocationCollection()
    : m_break_loc_collection(), m_collection_mutex() {}

// Destructor
BreakpointLocationCollection::~BreakpointLocationCollection() {}

void BreakpointLocationCollection::Add(const BreakpointLocationSP &bp_loc) {
  std::lock_guard<std::mutex> guard(m_collection_mutex);
  BreakpointLocationSP old_bp_loc =
      FindByIDPair(bp_loc->GetBreakpoint().GetID(), bp_loc->GetID());
  if (!old_bp_loc.get())
    m_break_loc_collection.push_back(bp_loc);
}

bool BreakpointLocationCollection::Remove(lldb::break_id_t bp_id,
                                          lldb::break_id_t bp_loc_id) {
  std::lock_guard<std::mutex> guard(m_collection_mutex);
  collection::iterator pos = GetIDPairIterator(bp_id, bp_loc_id); // Predicate
  if (pos != m_break_loc_collection.end()) {
    m_break_loc_collection.erase(pos);
    return true;
  }
  return false;
}

class BreakpointIDPairMatches {
public:
  BreakpointIDPairMatches(lldb::break_id_t break_id,
                          lldb::break_id_t break_loc_id)
      : m_break_id(break_id), m_break_loc_id(break_loc_id) {}

  bool operator()(const BreakpointLocationSP &bp_loc) const {
    return m_break_id == bp_loc->GetBreakpoint().GetID() &&
           m_break_loc_id == bp_loc->GetID();
  }

private:
  const lldb::break_id_t m_break_id;
  const lldb::break_id_t m_break_loc_id;
};

BreakpointLocationCollection::collection::iterator
BreakpointLocationCollection::GetIDPairIterator(lldb::break_id_t break_id,
                                                lldb::break_id_t break_loc_id) {
  return std::find_if(
      m_break_loc_collection.begin(),
      m_break_loc_collection.end(),                     // Search full range
      BreakpointIDPairMatches(break_id, break_loc_id)); // Predicate
}

BreakpointLocationCollection::collection::const_iterator
BreakpointLocationCollection::GetIDPairConstIterator(
    lldb::break_id_t break_id, lldb::break_id_t break_loc_id) const {
  return std::find_if(
      m_break_loc_collection.begin(),
      m_break_loc_collection.end(),                     // Search full range
      BreakpointIDPairMatches(break_id, break_loc_id)); // Predicate
}

BreakpointLocationSP
BreakpointLocationCollection::FindByIDPair(lldb::break_id_t break_id,
                                           lldb::break_id_t break_loc_id) {
  BreakpointLocationSP stop_sp;
  collection::iterator pos = GetIDPairIterator(break_id, break_loc_id);
  if (pos != m_break_loc_collection.end())
    stop_sp = *pos;

  return stop_sp;
}

const BreakpointLocationSP BreakpointLocationCollection::FindByIDPair(
    lldb::break_id_t break_id, lldb::break_id_t break_loc_id) const {
  BreakpointLocationSP stop_sp;
  collection::const_iterator pos =
      GetIDPairConstIterator(break_id, break_loc_id);
  if (pos != m_break_loc_collection.end())
    stop_sp = *pos;

  return stop_sp;
}

BreakpointLocationSP BreakpointLocationCollection::GetByIndex(size_t i) {
  std::lock_guard<std::mutex> guard(m_collection_mutex);
  BreakpointLocationSP stop_sp;
  if (i < m_break_loc_collection.size())
    stop_sp = m_break_loc_collection[i];

  return stop_sp;
}

const BreakpointLocationSP
BreakpointLocationCollection::GetByIndex(size_t i) const {
  std::lock_guard<std::mutex> guard(m_collection_mutex);
  BreakpointLocationSP stop_sp;
  if (i < m_break_loc_collection.size())
    stop_sp = m_break_loc_collection[i];

  return stop_sp;
}

bool BreakpointLocationCollection::ShouldStop(
    StoppointCallbackContext *context) {
  bool shouldStop = false;
  size_t i = 0;
  size_t prev_size = GetSize();
  while (i < prev_size) {
    // ShouldStop can remove the breakpoint from the list
    if (GetByIndex(i)->ShouldStop(context))
      shouldStop = true;

    if (prev_size == GetSize())
      i++;
    prev_size = GetSize();
  }
  return shouldStop;
}

bool BreakpointLocationCollection::ValidForThisThread(Thread *thread) {
  std::lock_guard<std::mutex> guard(m_collection_mutex);
  collection::iterator pos, begin = m_break_loc_collection.begin(),
                            end = m_break_loc_collection.end();

  for (pos = begin; pos != end; ++pos) {
    if ((*pos)->ValidForThisThread(thread))
      return true;
  }
  return false;
}

bool BreakpointLocationCollection::IsInternal() const {
  std::lock_guard<std::mutex> guard(m_collection_mutex);
  collection::const_iterator pos, begin = m_break_loc_collection.begin(),
                                  end = m_break_loc_collection.end();

  bool is_internal = true;

  for (pos = begin; pos != end; ++pos) {
    if (!(*pos)->GetBreakpoint().IsInternal()) {
      is_internal = false;
      break;
    }
  }
  return is_internal;
}

void BreakpointLocationCollection::GetDescription(
    Stream *s, lldb::DescriptionLevel level) {
  std::lock_guard<std::mutex> guard(m_collection_mutex);
  collection::iterator pos, begin = m_break_loc_collection.begin(),
                            end = m_break_loc_collection.end();

  for (pos = begin; pos != end; ++pos) {
    if (pos != begin)
      s->PutChar(' ');
    (*pos)->GetDescription(s, level);
  }
}

BreakpointLocationCollection &BreakpointLocationCollection::operator=(
    const BreakpointLocationCollection &rhs) {
  if (this != &rhs) {
      std::lock(m_collection_mutex, rhs.m_collection_mutex);
      std::lock_guard<std::mutex> lhs_guard(m_collection_mutex, std::adopt_lock);
      std::lock_guard<std::mutex> rhs_guard(rhs.m_collection_mutex, std::adopt_lock);
      m_break_loc_collection = rhs.m_break_loc_collection;
  }
  return *this;
}
