//===-- TypeCategoryMap.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/DataFormatters/TypeCategoryMap.h"

#include "lldb/DataFormatters/FormatClasses.h"
#include "lldb/Utility/Log.h"


using namespace lldb;
using namespace lldb_private;

TypeCategoryMap::TypeCategoryMap(IFormatChangeListener *lst)
    : m_map_mutex(), listener(lst), m_map(), m_active_categories() {
  ConstString default_cs("default");
  lldb::TypeCategoryImplSP default_sp =
      lldb::TypeCategoryImplSP(new TypeCategoryImpl(listener, default_cs));
  Add(default_cs, default_sp);
  Enable(default_cs, First);
}

void TypeCategoryMap::Add(KeyType name, const ValueSP &entry) {
  std::lock_guard<std::recursive_mutex> guard(m_map_mutex);
  m_map[name] = entry;
  if (listener)
    listener->Changed();
}

bool TypeCategoryMap::Delete(KeyType name) {
  std::lock_guard<std::recursive_mutex> guard(m_map_mutex);
  MapIterator iter = m_map.find(name);
  if (iter == m_map.end())
    return false;
  m_map.erase(name);
  Disable(name);
  if (listener)
    listener->Changed();
  return true;
}

bool TypeCategoryMap::Enable(KeyType category_name, Position pos) {
  std::lock_guard<std::recursive_mutex> guard(m_map_mutex);
  ValueSP category;
  if (!Get(category_name, category))
    return false;
  return Enable(category, pos);
}

bool TypeCategoryMap::Disable(KeyType category_name) {
  std::lock_guard<std::recursive_mutex> guard(m_map_mutex);
  ValueSP category;
  if (!Get(category_name, category))
    return false;
  return Disable(category);
}

bool TypeCategoryMap::Enable(ValueSP category, Position pos) {
  std::lock_guard<std::recursive_mutex> guard(m_map_mutex);
  if (category.get()) {
    Position pos_w = pos;
    if (pos == First || m_active_categories.size() == 0)
      m_active_categories.push_front(category);
    else if (pos == Last || pos == m_active_categories.size())
      m_active_categories.push_back(category);
    else if (pos < m_active_categories.size()) {
      ActiveCategoriesList::iterator iter = m_active_categories.begin();
      while (pos_w) {
        pos_w--, iter++;
      }
      m_active_categories.insert(iter, category);
    } else
      return false;
    category->Enable(true, pos);
    return true;
  }
  return false;
}

bool TypeCategoryMap::Disable(ValueSP category) {
  std::lock_guard<std::recursive_mutex> guard(m_map_mutex);
  if (category.get()) {
    m_active_categories.remove_if(delete_matching_categories(category));
    category->Disable();
    return true;
  }
  return false;
}

void TypeCategoryMap::EnableAllCategories() {
  std::lock_guard<std::recursive_mutex> guard(m_map_mutex);
  std::vector<ValueSP> sorted_categories(m_map.size(), ValueSP());
  MapType::iterator iter = m_map.begin(), end = m_map.end();
  for (; iter != end; ++iter) {
    if (iter->second->IsEnabled())
      continue;
    auto pos = iter->second->GetLastEnabledPosition();
    if (pos >= sorted_categories.size()) {
      auto iter = std::find_if(
          sorted_categories.begin(), sorted_categories.end(),
          [](const ValueSP &sp) -> bool { return sp.get() == nullptr; });
      pos = std::distance(sorted_categories.begin(), iter);
    }
    sorted_categories.at(pos) = iter->second;
  }
  decltype(sorted_categories)::iterator viter = sorted_categories.begin(),
                                        vend = sorted_categories.end();
  for (; viter != vend; viter++)
    if (*viter)
      Enable(*viter, Last);
}

void TypeCategoryMap::DisableAllCategories() {
  std::lock_guard<std::recursive_mutex> guard(m_map_mutex);
  for (Position p = First; !m_active_categories.empty(); p++) {
    m_active_categories.front()->SetEnabledPosition(p);
    Disable(m_active_categories.front());
  }
}

void TypeCategoryMap::Clear() {
  std::lock_guard<std::recursive_mutex> guard(m_map_mutex);
  m_map.clear();
  m_active_categories.clear();
  if (listener)
    listener->Changed();
}

bool TypeCategoryMap::Get(KeyType name, ValueSP &entry) {
  std::lock_guard<std::recursive_mutex> guard(m_map_mutex);
  MapIterator iter = m_map.find(name);
  if (iter == m_map.end())
    return false;
  entry = iter->second;
  return true;
}

bool TypeCategoryMap::Get(uint32_t pos, ValueSP &entry) {
  std::lock_guard<std::recursive_mutex> guard(m_map_mutex);
  MapIterator iter = m_map.begin();
  MapIterator end = m_map.end();
  while (pos > 0) {
    iter++;
    pos--;
    if (iter == end)
      return false;
  }
  entry = iter->second;
  return false;
}

bool TypeCategoryMap::AnyMatches(
    ConstString type_name, TypeCategoryImpl::FormatCategoryItems items,
    bool only_enabled, const char **matching_category,
    TypeCategoryImpl::FormatCategoryItems *matching_type) {
  std::lock_guard<std::recursive_mutex> guard(m_map_mutex);

  MapIterator pos, end = m_map.end();
  for (pos = m_map.begin(); pos != end; pos++) {
    if (pos->second->AnyMatches(type_name, items, only_enabled,
                                matching_category, matching_type))
      return true;
  }
  return false;
}

lldb::TypeFormatImplSP
TypeCategoryMap::GetFormat(FormattersMatchData &match_data) {
  std::lock_guard<std::recursive_mutex> guard(m_map_mutex);

  uint32_t reason_why;
  ActiveCategoriesIterator begin, end = m_active_categories.end();

  Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_DATAFORMATTERS));

  if (log) {
    for (auto match : match_data.GetMatchesVector()) {
      log->Printf(
          "[CategoryMap::GetFormat] candidate match = %s %s %s %s reason = "
          "%" PRIu32,
          match.GetTypeName().GetCString(),
          match.DidStripPointer() ? "strip-pointers" : "no-strip-pointers",
          match.DidStripReference() ? "strip-reference" : "no-strip-reference",
          match.DidStripTypedef() ? "strip-typedef" : "no-strip-typedef",
          match.GetReason());
    }
  }

  for (begin = m_active_categories.begin(); begin != end; begin++) {
    lldb::TypeCategoryImplSP category_sp = *begin;
    lldb::TypeFormatImplSP current_format;
    if (log)
      log->Printf("[TypeCategoryMap::GetFormat] Trying to use category %s",
                  category_sp->GetName());
    if (!category_sp->Get(match_data.GetValueObject(),
                          match_data.GetMatchesVector(), current_format,
                          &reason_why))
      continue;
    return current_format;
  }
  if (log)
    log->Printf(
        "[TypeCategoryMap::GetFormat] nothing found - returning empty SP");
  return lldb::TypeFormatImplSP();
}

lldb::TypeSummaryImplSP
TypeCategoryMap::GetSummaryFormat(FormattersMatchData &match_data) {
  std::lock_guard<std::recursive_mutex> guard(m_map_mutex);

  uint32_t reason_why;
  ActiveCategoriesIterator begin, end = m_active_categories.end();

  Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_DATAFORMATTERS));

  if (log) {
    for (auto match : match_data.GetMatchesVector()) {
      log->Printf(
          "[CategoryMap::GetSummaryFormat] candidate match = %s %s %s %s "
          "reason = %" PRIu32,
          match.GetTypeName().GetCString(),
          match.DidStripPointer() ? "strip-pointers" : "no-strip-pointers",
          match.DidStripReference() ? "strip-reference" : "no-strip-reference",
          match.DidStripTypedef() ? "strip-typedef" : "no-strip-typedef",
          match.GetReason());
    }
  }

  for (begin = m_active_categories.begin(); begin != end; begin++) {
    lldb::TypeCategoryImplSP category_sp = *begin;
    lldb::TypeSummaryImplSP current_format;
    if (log)
      log->Printf("[CategoryMap::GetSummaryFormat] Trying to use category %s",
                  category_sp->GetName());
    if (!category_sp->Get(match_data.GetValueObject(),
                          match_data.GetMatchesVector(), current_format,
                          &reason_why))
      continue;
    return current_format;
  }
  if (log)
    log->Printf(
        "[CategoryMap::GetSummaryFormat] nothing found - returning empty SP");
  return lldb::TypeSummaryImplSP();
}

lldb::SyntheticChildrenSP
TypeCategoryMap::GetSyntheticChildren(FormattersMatchData &match_data) {
  std::lock_guard<std::recursive_mutex> guard(m_map_mutex);

  uint32_t reason_why;

  ActiveCategoriesIterator begin, end = m_active_categories.end();

  Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_DATAFORMATTERS));

  if (log) {
    for (auto match : match_data.GetMatchesVector()) {
      log->Printf(
          "[CategoryMap::GetSyntheticChildren] candidate match = %s %s %s %s "
          "reason = %" PRIu32,
          match.GetTypeName().GetCString(),
          match.DidStripPointer() ? "strip-pointers" : "no-strip-pointers",
          match.DidStripReference() ? "strip-reference" : "no-strip-reference",
          match.DidStripTypedef() ? "strip-typedef" : "no-strip-typedef",
          match.GetReason());
    }
  }

  for (begin = m_active_categories.begin(); begin != end; begin++) {
    lldb::TypeCategoryImplSP category_sp = *begin;
    lldb::SyntheticChildrenSP current_format;
    if (log)
      log->Printf(
          "[CategoryMap::GetSyntheticChildren] Trying to use category %s",
          category_sp->GetName());
    if (!category_sp->Get(match_data.GetValueObject(),
                          match_data.GetMatchesVector(), current_format,
                          &reason_why))
      continue;
    return current_format;
  }
  if (log)
    log->Printf("[CategoryMap::GetSyntheticChildren] nothing found - returning "
                "empty SP");
  return lldb::SyntheticChildrenSP();
}

lldb::TypeValidatorImplSP
TypeCategoryMap::GetValidator(FormattersMatchData &match_data) {
  std::lock_guard<std::recursive_mutex> guard(m_map_mutex);

  uint32_t reason_why;
  ActiveCategoriesIterator begin, end = m_active_categories.end();

  Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_DATAFORMATTERS));

  if (log) {
    for (auto match : match_data.GetMatchesVector()) {
      log->Printf(
          "[CategoryMap::GetValidator] candidate match = %s %s %s %s reason = "
          "%" PRIu32,
          match.GetTypeName().GetCString(),
          match.DidStripPointer() ? "strip-pointers" : "no-strip-pointers",
          match.DidStripReference() ? "strip-reference" : "no-strip-reference",
          match.DidStripTypedef() ? "strip-typedef" : "no-strip-typedef",
          match.GetReason());
    }
  }

  for (begin = m_active_categories.begin(); begin != end; begin++) {
    lldb::TypeCategoryImplSP category_sp = *begin;
    lldb::TypeValidatorImplSP current_format;
    if (log)
      log->Printf("[CategoryMap::GetValidator] Trying to use category %s",
                  category_sp->GetName());
    if (!category_sp->Get(match_data.GetValueObject(),
                          match_data.GetMatchesVector(), current_format,
                          &reason_why))
      continue;
    return current_format;
  }
  if (log)
    log->Printf(
        "[CategoryMap::GetValidator] nothing found - returning empty SP");
  return lldb::TypeValidatorImplSP();
}

void TypeCategoryMap::ForEach(ForEachCallback callback) {
  if (callback) {
    std::lock_guard<std::recursive_mutex> guard(m_map_mutex);

    // loop through enabled categories in respective order
    {
      ActiveCategoriesIterator begin, end = m_active_categories.end();
      for (begin = m_active_categories.begin(); begin != end; begin++) {
        lldb::TypeCategoryImplSP category = *begin;
        if (!callback(category))
          break;
      }
    }

    // loop through disabled categories in just any order
    {
      MapIterator pos, end = m_map.end();
      for (pos = m_map.begin(); pos != end; pos++) {
        if (pos->second->IsEnabled())
          continue;
        if (!callback(pos->second))
          break;
      }
    }
  }
}

TypeCategoryImplSP TypeCategoryMap::GetAtIndex(uint32_t index) {
  std::lock_guard<std::recursive_mutex> guard(m_map_mutex);

  if (index < m_map.size()) {
    MapIterator pos, end = m_map.end();
    for (pos = m_map.begin(); pos != end; pos++) {
      if (index == 0)
        return pos->second;
      index--;
    }
  }

  return TypeCategoryImplSP();
}
