//===-- LibCxxList.cpp ------------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

// C Includes
// C++ Includes
// Other libraries and framework includes
// Project includes
#include "LibCxx.h"

#include "lldb/Core/ValueObject.h"
#include "lldb/Core/ValueObjectConstResult.h"
#include "lldb/DataFormatters/FormattersHelpers.h"
#include "lldb/Symbol/ClangASTContext.h"
#include "lldb/Target/Target.h"
#include "lldb/Utility/DataBufferHeap.h"
#include "lldb/Utility/Endian.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/Stream.h"

using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::formatters;

namespace {

class ListEntry {
public:
  ListEntry() = default;
  ListEntry(ValueObjectSP entry_sp) : m_entry_sp(entry_sp) {}
  ListEntry(const ListEntry &rhs) = default;
  ListEntry(ValueObject *entry)
      : m_entry_sp(entry ? entry->GetSP() : ValueObjectSP()) {}

  ListEntry next() {
    static ConstString g_next("__next_");

    if (!m_entry_sp)
      return ListEntry();
    return ListEntry(m_entry_sp->GetChildMemberWithName(g_next, true));
  }

  ListEntry prev() {
    static ConstString g_prev("__prev_");

    if (!m_entry_sp)
      return ListEntry();
    return ListEntry(m_entry_sp->GetChildMemberWithName(g_prev, true));
  }

  uint64_t value() const {
    if (!m_entry_sp)
      return 0;
    return m_entry_sp->GetValueAsUnsigned(0);
  }

  bool null() { return (value() == 0); }

  explicit operator bool() { return GetEntry() && !null(); }

  ValueObjectSP GetEntry() { return m_entry_sp; }

  void SetEntry(ValueObjectSP entry) { m_entry_sp = entry; }

  bool operator==(const ListEntry &rhs) const { return value() == rhs.value(); }

  bool operator!=(const ListEntry &rhs) const { return !(*this == rhs); }

private:
  ValueObjectSP m_entry_sp;
};

class ListIterator {
public:
  ListIterator() = default;
  ListIterator(ListEntry entry) : m_entry(entry) {}
  ListIterator(ValueObjectSP entry) : m_entry(entry) {}
  ListIterator(const ListIterator &rhs) = default;
  ListIterator(ValueObject *entry) : m_entry(entry) {}

  ValueObjectSP value() { return m_entry.GetEntry(); }

  ValueObjectSP advance(size_t count) {
    if (count == 0)
      return m_entry.GetEntry();
    if (count == 1) {
      next();
      return m_entry.GetEntry();
    }
    while (count > 0) {
      next();
      count--;
      if (m_entry.null())
        return lldb::ValueObjectSP();
    }
    return m_entry.GetEntry();
  }

  bool operator==(const ListIterator &rhs) const {
    return (rhs.m_entry == m_entry);
  }

protected:
  void next() { m_entry = m_entry.next(); }

  void prev() { m_entry = m_entry.prev(); }

private:
  ListEntry m_entry;
};

} // end anonymous namespace

namespace lldb_private {
namespace formatters {
class LibcxxStdListSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
public:
  LibcxxStdListSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);

  ~LibcxxStdListSyntheticFrontEnd() override = default;

  size_t CalculateNumChildren() override;

  lldb::ValueObjectSP GetChildAtIndex(size_t idx) override;

  bool Update() override;

  bool MightHaveChildren() override;

  size_t GetIndexOfChildWithName(const ConstString &name) override;

private:
  bool HasLoop(size_t count);

  size_t m_list_capping_size;
  static const bool g_use_loop_detect = true;

  size_t m_loop_detected; // The number of elements that have had loop detection
                          // run over them.
  ListEntry m_slow_runner; // Used for loop detection
  ListEntry m_fast_runner; // Used for loop detection

  lldb::addr_t m_node_address;
  ValueObject *m_head;
  ValueObject *m_tail;
  CompilerType m_element_type;
  size_t m_count;
  std::map<size_t, ListIterator> m_iterators;
};
} // namespace formatters
} // namespace lldb_private

lldb_private::formatters::LibcxxStdListSyntheticFrontEnd::
    LibcxxStdListSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
    : SyntheticChildrenFrontEnd(*valobj_sp), m_list_capping_size(0),
      m_loop_detected(0), m_node_address(), m_head(nullptr), m_tail(nullptr),
      m_element_type(), m_count(UINT32_MAX), m_iterators() {
  if (valobj_sp)
    Update();
}

bool lldb_private::formatters::LibcxxStdListSyntheticFrontEnd::HasLoop(
    size_t count) {
  if (!g_use_loop_detect)
    return false;
  // don't bother checking for a loop if we won't actually need to jump nodes
  if (m_count < 2)
    return false;

  if (m_loop_detected == 0) {
    // This is the first time we are being run (after the last update). Set up
    // the loop
    // invariant for the first element.
    m_slow_runner = ListEntry(m_head).next();
    m_fast_runner = m_slow_runner.next();
    m_loop_detected = 1;
  }

  // Loop invariant:
  // Loop detection has been run over the first m_loop_detected elements. If
  // m_slow_runner ==
  // m_fast_runner then the loop has been detected after m_loop_detected
  // elements.
  const size_t steps_to_run = std::min(count, m_count);
  while (m_loop_detected < steps_to_run && m_slow_runner && m_fast_runner &&
         m_slow_runner != m_fast_runner) {

    m_slow_runner = m_slow_runner.next();
    m_fast_runner = m_fast_runner.next().next();
    m_loop_detected++;
  }
  if (count <= m_loop_detected)
    return false; // No loop in the first m_loop_detected elements.
  if (!m_slow_runner || !m_fast_runner)
    return false; // Reached the end of the list. Definitely no loops.
  return m_slow_runner == m_fast_runner;
}

size_t lldb_private::formatters::LibcxxStdListSyntheticFrontEnd::
    CalculateNumChildren() {
  if (m_count != UINT32_MAX)
    return m_count;
  if (!m_head || !m_tail || m_node_address == 0)
    return 0;
  ValueObjectSP size_alloc(
      m_backend.GetChildMemberWithName(ConstString("__size_alloc_"), true));
  if (size_alloc) {
    ValueObjectSP first(
        size_alloc->GetChildMemberWithName(ConstString("__first_"), true));
    if (first) {
      m_count = first->GetValueAsUnsigned(UINT32_MAX);
    }
  }
  if (m_count != UINT32_MAX) {
    return m_count;
  } else {
    uint64_t next_val = m_head->GetValueAsUnsigned(0);
    uint64_t prev_val = m_tail->GetValueAsUnsigned(0);
    if (next_val == 0 || prev_val == 0)
      return 0;
    if (next_val == m_node_address)
      return 0;
    if (next_val == prev_val)
      return 1;
    uint64_t size = 2;
    ListEntry current(m_head);
    while (current.next() && current.next().value() != m_node_address) {
      size++;
      current = current.next();
      if (size > m_list_capping_size)
        break;
    }
    return m_count = (size - 1);
  }
}

lldb::ValueObjectSP
lldb_private::formatters::LibcxxStdListSyntheticFrontEnd::GetChildAtIndex(
    size_t idx) {
  static ConstString g_value("__value_");
  static ConstString g_next("__next_");

  if (idx >= CalculateNumChildren())
    return lldb::ValueObjectSP();

  if (!m_head || !m_tail || m_node_address == 0)
    return lldb::ValueObjectSP();

  if (HasLoop(idx + 1))
    return lldb::ValueObjectSP();

  size_t actual_advance = idx;

  ListIterator current(m_head);
  if (idx > 0) {
    auto cached_iterator = m_iterators.find(idx - 1);
    if (cached_iterator != m_iterators.end()) {
      current = cached_iterator->second;
      actual_advance = 1;
    }
  }

  ValueObjectSP current_sp(current.advance(actual_advance));
  if (!current_sp)
    return lldb::ValueObjectSP();

  m_iterators[idx] = current;

  current_sp = current_sp->GetChildAtIndex(1, true); // get the __value_ child
  if (!current_sp)
    return lldb::ValueObjectSP();

  if (current_sp->GetName() == g_next) {
    ProcessSP process_sp(current_sp->GetProcessSP());
    if (!process_sp)
      return nullptr;

    // if we grabbed the __next_ pointer, then the child is one pointer deep-er
    lldb::addr_t addr = current_sp->GetParent()->GetPointerValue();
    addr = addr + 2 * process_sp->GetAddressByteSize();
    ExecutionContext exe_ctx(process_sp);
    current_sp =
        CreateValueObjectFromAddress("__value_", addr, exe_ctx, m_element_type);
  }

  // we need to copy current_sp into a new object otherwise we will end up with
  // all items named __value_
  DataExtractor data;
  Status error;
  current_sp->GetData(data, error);
  if (error.Fail())
    return lldb::ValueObjectSP();

  StreamString name;
  name.Printf("[%" PRIu64 "]", (uint64_t)idx);
  return CreateValueObjectFromData(name.GetString(), data,
                                   m_backend.GetExecutionContextRef(),
                                   m_element_type);
}

bool lldb_private::formatters::LibcxxStdListSyntheticFrontEnd::Update() {
  m_iterators.clear();
  m_head = m_tail = nullptr;
  m_node_address = 0;
  m_count = UINT32_MAX;
  m_loop_detected = 0;
  m_slow_runner.SetEntry(nullptr);
  m_fast_runner.SetEntry(nullptr);

  Status err;
  ValueObjectSP backend_addr(m_backend.AddressOf(err));
  m_list_capping_size = 0;
  if (m_backend.GetTargetSP())
    m_list_capping_size =
        m_backend.GetTargetSP()->GetMaximumNumberOfChildrenToDisplay();
  if (m_list_capping_size == 0)
    m_list_capping_size = 255;
  if (err.Fail() || !backend_addr)
    return false;
  m_node_address = backend_addr->GetValueAsUnsigned(0);
  if (!m_node_address || m_node_address == LLDB_INVALID_ADDRESS)
    return false;
  ValueObjectSP impl_sp(
      m_backend.GetChildMemberWithName(ConstString("__end_"), true));
  if (!impl_sp)
    return false;
  CompilerType list_type = m_backend.GetCompilerType();
  if (list_type.IsReferenceType())
    list_type = list_type.GetNonReferenceType();

  if (list_type.GetNumTemplateArguments() == 0)
    return false;
  lldb::TemplateArgumentKind kind;
  m_element_type = list_type.GetTemplateArgument(0, kind);
  m_head = impl_sp->GetChildMemberWithName(ConstString("__next_"), true).get();
  m_tail = impl_sp->GetChildMemberWithName(ConstString("__prev_"), true).get();
  return false;
}

bool lldb_private::formatters::LibcxxStdListSyntheticFrontEnd::
    MightHaveChildren() {
  return true;
}

size_t lldb_private::formatters::LibcxxStdListSyntheticFrontEnd::
    GetIndexOfChildWithName(const ConstString &name) {
  return ExtractIndexFromString(name.GetCString());
}

SyntheticChildrenFrontEnd *
lldb_private::formatters::LibcxxStdListSyntheticFrontEndCreator(
    CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
  return (valobj_sp ? new LibcxxStdListSyntheticFrontEnd(valobj_sp) : nullptr);
}
