// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "include/cppgc/allocation.h"
#include "include/cppgc/custom-space.h"
#include "src/heap/cppgc/heap-page.h"
#include "src/heap/cppgc/raw-heap.h"
#include "test/unittests/heap/cppgc/tests.h"

namespace cppgc {

class CustomSpace1 : public CustomSpace<CustomSpace1> {
 public:
  static constexpr size_t kSpaceIndex = 0;
};

class CustomSpace2 : public CustomSpace<CustomSpace2> {
 public:
  static constexpr size_t kSpaceIndex = 1;
};

namespace internal {

namespace {

size_t g_destructor_callcount;

class TestWithHeapWithCustomSpaces : public testing::TestWithPlatform {
 protected:
  TestWithHeapWithCustomSpaces() {
    Heap::HeapOptions options;
    options.custom_spaces.emplace_back(std::make_unique<CustomSpace1>());
    options.custom_spaces.emplace_back(std::make_unique<CustomSpace2>());
    heap_ = Heap::Create(platform_, std::move(options));
    g_destructor_callcount = 0;
  }

  void PreciseGC() {
    heap_->ForceGarbageCollectionSlow(
        ::testing::UnitTest::GetInstance()->current_test_info()->name(),
        "Testing", cppgc::Heap::StackState::kNoHeapPointers);
  }

  cppgc::Heap* GetHeap() const { return heap_.get(); }

 private:
  std::unique_ptr<cppgc::Heap> heap_;
};

class RegularGCed final : public GarbageCollected<RegularGCed> {
 public:
  void Trace(Visitor*) const {}
};

class CustomGCed1 final : public GarbageCollected<CustomGCed1> {
 public:
  ~CustomGCed1() { g_destructor_callcount++; }
  void Trace(Visitor*) const {}
};
class CustomGCed2 final : public GarbageCollected<CustomGCed2> {
 public:
  ~CustomGCed2() { g_destructor_callcount++; }
  void Trace(Visitor*) const {}
};

class CustomGCedBase : public GarbageCollected<CustomGCedBase> {
 public:
  void Trace(Visitor*) const {}
};
class CustomGCedFinal1 final : public CustomGCedBase {
 public:
  ~CustomGCedFinal1() { g_destructor_callcount++; }
};
class CustomGCedFinal2 final : public CustomGCedBase {
 public:
  ~CustomGCedFinal2() { g_destructor_callcount++; }
};

}  // namespace

}  // namespace internal

template <>
struct SpaceTrait<internal::CustomGCed1> {
  using Space = CustomSpace1;
};

template <>
struct SpaceTrait<internal::CustomGCed2> {
  using Space = CustomSpace2;
};

template <typename T>
struct SpaceTrait<
    T, std::enable_if_t<std::is_base_of<internal::CustomGCedBase, T>::value>> {
  using Space = CustomSpace1;
};

namespace internal {

TEST_F(TestWithHeapWithCustomSpaces, AllocateOnCustomSpaces) {
  auto* regular =
      MakeGarbageCollected<RegularGCed>(GetHeap()->GetAllocationHandle());
  auto* custom1 =
      MakeGarbageCollected<CustomGCed1>(GetHeap()->GetAllocationHandle());
  auto* custom2 =
      MakeGarbageCollected<CustomGCed2>(GetHeap()->GetAllocationHandle());
  EXPECT_EQ(RawHeap::kNumberOfRegularSpaces,
            NormalPage::FromPayload(custom1)->space()->index());
  EXPECT_EQ(RawHeap::kNumberOfRegularSpaces + 1,
            NormalPage::FromPayload(custom2)->space()->index());
  EXPECT_EQ(static_cast<size_t>(RawHeap::RegularSpaceType::kNormal1),
            NormalPage::FromPayload(regular)->space()->index());
}

TEST_F(TestWithHeapWithCustomSpaces,
       AllocateOnCustomSpacesSpecifiedThroughBase) {
  auto* regular =
      MakeGarbageCollected<RegularGCed>(GetHeap()->GetAllocationHandle());
  auto* custom1 =
      MakeGarbageCollected<CustomGCedFinal1>(GetHeap()->GetAllocationHandle());
  auto* custom2 =
      MakeGarbageCollected<CustomGCedFinal2>(GetHeap()->GetAllocationHandle());
  EXPECT_EQ(RawHeap::kNumberOfRegularSpaces,
            NormalPage::FromPayload(custom1)->space()->index());
  EXPECT_EQ(RawHeap::kNumberOfRegularSpaces,
            NormalPage::FromPayload(custom2)->space()->index());
  EXPECT_EQ(static_cast<size_t>(RawHeap::RegularSpaceType::kNormal1),
            NormalPage::FromPayload(regular)->space()->index());
}

TEST_F(TestWithHeapWithCustomSpaces, SweepCustomSpace) {
  MakeGarbageCollected<CustomGCedFinal1>(GetHeap()->GetAllocationHandle());
  MakeGarbageCollected<CustomGCedFinal2>(GetHeap()->GetAllocationHandle());
  MakeGarbageCollected<CustomGCed1>(GetHeap()->GetAllocationHandle());
  MakeGarbageCollected<CustomGCed2>(GetHeap()->GetAllocationHandle());
  EXPECT_EQ(0u, g_destructor_callcount);
  PreciseGC();
  EXPECT_EQ(4u, g_destructor_callcount);
}

}  // namespace internal

// Test custom space compactability.

class CompactableCustomSpace : public CustomSpace<CompactableCustomSpace> {
 public:
  static constexpr size_t kSpaceIndex = 0;
  static constexpr bool kSupportsCompaction = true;
};

class NotCompactableCustomSpace
    : public CustomSpace<NotCompactableCustomSpace> {
 public:
  static constexpr size_t kSpaceIndex = 1;
  static constexpr bool kSupportsCompaction = false;
};

class DefaultCompactableCustomSpace
    : public CustomSpace<DefaultCompactableCustomSpace> {
 public:
  static constexpr size_t kSpaceIndex = 2;
  // By default space are not compactable.
};

namespace internal {
namespace {

class TestWithHeapWithCompactableCustomSpaces
    : public testing::TestWithPlatform {
 protected:
  TestWithHeapWithCompactableCustomSpaces() {
    Heap::HeapOptions options;
    options.custom_spaces.emplace_back(
        std::make_unique<CompactableCustomSpace>());
    options.custom_spaces.emplace_back(
        std::make_unique<NotCompactableCustomSpace>());
    options.custom_spaces.emplace_back(
        std::make_unique<DefaultCompactableCustomSpace>());
    heap_ = Heap::Create(platform_, std::move(options));
    g_destructor_callcount = 0;
  }

  void PreciseGC() {
    heap_->ForceGarbageCollectionSlow("TestWithHeapWithCompactableCustomSpaces",
                                      "Testing",
                                      cppgc::Heap::StackState::kNoHeapPointers);
  }

  cppgc::Heap* GetHeap() const { return heap_.get(); }

 private:
  std::unique_ptr<cppgc::Heap> heap_;
};

class CompactableGCed final : public GarbageCollected<CompactableGCed> {
 public:
  void Trace(Visitor*) const {}
};
class NotCompactableGCed final : public GarbageCollected<NotCompactableGCed> {
 public:
  void Trace(Visitor*) const {}
};
class DefaultCompactableGCed final
    : public GarbageCollected<DefaultCompactableGCed> {
 public:
  void Trace(Visitor*) const {}
};

}  // namespace
}  // namespace internal

template <>
struct SpaceTrait<internal::CompactableGCed> {
  using Space = CompactableCustomSpace;
};
template <>
struct SpaceTrait<internal::NotCompactableGCed> {
  using Space = NotCompactableCustomSpace;
};
template <>
struct SpaceTrait<internal::DefaultCompactableGCed> {
  using Space = DefaultCompactableCustomSpace;
};

namespace internal {

TEST_F(TestWithHeapWithCompactableCustomSpaces,
       AllocateOnCompactableCustomSpaces) {
  auto* compactable =
      MakeGarbageCollected<CompactableGCed>(GetHeap()->GetAllocationHandle());
  auto* not_compactable = MakeGarbageCollected<NotCompactableGCed>(
      GetHeap()->GetAllocationHandle());
  auto* default_compactable = MakeGarbageCollected<DefaultCompactableGCed>(
      GetHeap()->GetAllocationHandle());
  EXPECT_TRUE(NormalPage::FromPayload(compactable)->space()->is_compactable());
  EXPECT_FALSE(
      NormalPage::FromPayload(not_compactable)->space()->is_compactable());
  EXPECT_FALSE(
      NormalPage::FromPayload(default_compactable)->space()->is_compactable());
}

}  // namespace internal

}  // namespace cppgc