// Copyright 2024 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#include "pw_containers/intrusive_multiset.h"

#include "pw_compilation_testing/negative_compilation.h"
#include "pw_containers/intrusive_set.h"
#include "pw_span/span.h"
#include "pw_unit_test/framework.h"

namespace {

// Base item.
class BaseItem {
 public:
  BaseItem(size_t key, const char* name) : key_(key), name_(name) {}

  constexpr const size_t& key() const { return key_; }
  constexpr const char* name() const { return name_; }
  void set_name(const char* name) { name_ = name; }

  constexpr bool operator<(const BaseItem& rhs) const {
    return key() < rhs.key();
  }

 private:
  size_t key_;
  const char* name_;
};

// A basic item that can be used in a set.
struct TestItem : public ::pw::IntrusiveMultiSet<TestItem>::Item,
                  public BaseItem {
  TestItem(size_t key, const char* name) : BaseItem(key, name) {}
};

// Test fixture.
class IntrusiveMultiSetTest : public ::testing::Test {
 protected:
  using IntrusiveMultiSet = ::pw::IntrusiveMultiSet<TestItem>;
  static constexpr size_t kNumItems = 10;

  void SetUp() override { multiset_.insert(items_.begin(), items_.end()); }

  void TearDown() override { multiset_.clear(); }

  std::array<TestItem, kNumItems> items_ = {{
      {30, "a"},
      {50, "b"},
      {20, "c"},
      {40, "d"},
      {10, "e"},
      {35, "A"},
      {55, "B"},
      {25, "C"},
      {45, "D"},
      {15, "E"},
  }};

  IntrusiveMultiSet multiset_;
};

// Unit tests.

TEST_F(IntrusiveMultiSetTest, Construct_Default) {
  IntrusiveMultiSet multiset;
  EXPECT_TRUE(multiset.empty());
  EXPECT_EQ(multiset.begin(), multiset.end());
  EXPECT_EQ(multiset.rbegin(), multiset.rend());
  EXPECT_EQ(multiset.size(), 0U);
  EXPECT_EQ(multiset.lower_bound({0, "."}), multiset.end());
  EXPECT_EQ(multiset.upper_bound({0, "."}), multiset.end());
}

TEST_F(IntrusiveMultiSetTest, Construct_ObjectIterators) {
  multiset_.clear();
  IntrusiveMultiSet multiset(items_.begin(), items_.end());
  EXPECT_FALSE(multiset.empty());
  EXPECT_EQ(multiset.size(), items_.size());
  multiset.clear();
}

TEST_F(IntrusiveMultiSetTest, Construct_ObjectIterators_Empty) {
  IntrusiveMultiSet multiset(items_.end(), items_.end());
  EXPECT_TRUE(multiset.empty());
  EXPECT_EQ(multiset.size(), 0U);
}

TEST_F(IntrusiveMultiSetTest, Construct_PointerIterators) {
  std::array<TestItem*, 3> ptrs = {&items_[0], &items_[1], &items_[2]};
  multiset_.clear();
  IntrusiveMultiSet multiset(ptrs.begin(), ptrs.end());
  EXPECT_FALSE(multiset.empty());
  EXPECT_EQ(multiset.size(), 3U);
  multiset.clear();
}

TEST_F(IntrusiveMultiSetTest, Construct_PointerIterators_Empty) {
  std::array<TestItem*, 0> ptrs;
  IntrusiveMultiSet multiset(ptrs.begin(), ptrs.end());
  EXPECT_TRUE(multiset.empty());
  EXPECT_EQ(multiset.size(), 0U);
  multiset.clear();
}

TEST_F(IntrusiveMultiSetTest, Construct_InitializerList) {
  multiset_.clear();
  IntrusiveMultiSet multiset({&items_[0], &items_[2], &items_[4]});
  auto iter = multiset.begin();
  EXPECT_EQ((iter++)->key(), 10U);
  EXPECT_EQ((iter++)->key(), 20U);
  EXPECT_EQ((iter++)->key(), 30U);
  EXPECT_EQ(iter, multiset.end());
  multiset.clear();
}

TEST_F(IntrusiveMultiSetTest, Construct_InitializerList_Empty) {
  IntrusiveMultiSet multiset({});
  EXPECT_TRUE(multiset.empty());
  EXPECT_EQ(multiset.size(), 0U);
}

TEST_F(IntrusiveMultiSetTest, Construct_CustomCompare) {
  auto greater_than = [](const BaseItem& lhs, const BaseItem& rhs) {
    return lhs.key() > rhs.key();
  };
  multiset_.clear();
  IntrusiveMultiSet multiset({&items_[0], &items_[2], &items_[4]},
                             std::move(greater_than));
  auto iter = multiset.begin();
  EXPECT_EQ((iter++)->key(), 30U);
  EXPECT_EQ((iter++)->key(), 20U);
  EXPECT_EQ((iter++)->key(), 10U);
  EXPECT_EQ(iter, multiset.end());
  multiset.clear();
}

//  A struct that is not a multiset item.
struct NotAnItem : public BaseItem {
  NotAnItem(size_t key, const char* name) : BaseItem(key, name) {}
};

#if PW_NC_TEST(IncompatibleItem)
PW_NC_EXPECT(
    "IntrusiveMultiSet items must be derived from IntrusiveMultiSet<T>::Item");

class BadItem : public ::pw::IntrusiveMultiSet<NotAnItem>::Item {
 public:
  constexpr bool operator<(const BadItem& rhs) const { return this < &rhs; }
};

[[maybe_unused]] ::pw::IntrusiveMultiSet<BadItem> bad_multiset1;

#elif PW_NC_TEST(DoesNotInheritFromItem)
PW_NC_EXPECT(
    "IntrusiveMultiSet items must be derived from IntrusiveMultiSet<T>::Item");

[[maybe_unused]] ::pw::IntrusiveMultiSet<NotAnItem> bad_multiset2;

#endif  // PW_NC_TEST

// Iterators

TEST_F(IntrusiveMultiSetTest, Iterator) {
  const IntrusiveMultiSet& multiset = multiset_;
  auto iter = multiset.begin();
  size_t key = 10;
  for (size_t i = 0; i < kNumItems; ++i) {
    auto& item = *iter++;
    EXPECT_EQ(item.key(), key);
    key += 5;
  }
  EXPECT_EQ(key, 60U);
  EXPECT_EQ(iter, multiset.end());
  EXPECT_EQ(iter, multiset.cend());
  for (size_t i = 0; i < kNumItems; ++i) {
    key -= 5;
    EXPECT_EQ((--iter)->key(), key);
  }
  EXPECT_EQ(key, 10U);
  EXPECT_EQ(iter, multiset.begin());
  EXPECT_EQ(iter, multiset.cbegin());
}

TEST_F(IntrusiveMultiSetTest, ReverseIterator) {
  const IntrusiveMultiSet& multiset = multiset_;
  auto iter = multiset.rbegin();
  size_t key = 55;
  for (size_t i = 0; i < kNumItems; ++i) {
    auto& item = *iter++;
    EXPECT_EQ(item.key(), key);
    key -= 5;
  }
  EXPECT_EQ(key, 5U);
  EXPECT_EQ(iter, multiset.rend());
  EXPECT_EQ(iter, multiset.crend());
  for (size_t i = 0; i < kNumItems; ++i) {
    key += 5;
    EXPECT_EQ((--iter)->key(), key);
  }
  EXPECT_EQ(key, 55U);
  EXPECT_EQ(iter, multiset.rbegin());
  EXPECT_EQ(iter, multiset.crbegin());
}

TEST_F(IntrusiveMultiSetTest, ConstIterator_CompareNonConst) {
  EXPECT_EQ(multiset_.end(), multiset_.cend());
}

// A multiset item that is distinct from TestItem
struct OtherItem : public ::pw::IntrusiveMultiSet<OtherItem>::Item,
                   public BaseItem {
  OtherItem(size_t key, const char* name) : BaseItem(key, name) {}
};

TEST_F(IntrusiveMultiSetTest, ConstIterator_CompareNonConst_CompilationFails) {
  ::pw::IntrusiveMultiSet<OtherItem> multiset;
#if PW_NC_TEST(CannotCompareIncompatibleIteratorsEqual)
  PW_NC_EXPECT("multiset_\.end\(\) == multiset\.end\(\)");
  static_cast<void>(multiset_.end() == multiset.end());
#elif PW_NC_TEST(CannotCompareIncompatibleIteratorsInequal)
  PW_NC_EXPECT("multiset_\.end\(\) != multiset\.end\(\)");
  static_cast<void>(multiset_.end() != multiset.end());
#endif  // PW_NC_TEST
}

#if PW_NC_TEST(CannotModifyThroughConstIterator)
PW_NC_EXPECT("function is not marked const|discards qualifiers");

TEST_F(IntrusiveMultiSetTest, ConstIterator_Modify) {
  const IntrusiveMultiSet& multiset = multiset_;
  auto iter = multiset.begin();
  iter->set_name("nope");
}

#endif  // PW_NC_TEST

// Capacity

TEST_F(IntrusiveMultiSetTest, IsEmpty) {
  const IntrusiveMultiSet& multiset = multiset_;
  EXPECT_FALSE(multiset.empty());
  multiset_.clear();
  EXPECT_TRUE(multiset.empty());
}

TEST_F(IntrusiveMultiSetTest, GetSize) {
  const IntrusiveMultiSet& multiset = multiset_;
  EXPECT_EQ(multiset.size(), kNumItems);
  multiset_.clear();
  EXPECT_EQ(multiset.size(), 0U);
}

TEST_F(IntrusiveMultiSetTest, GetMaxSize) {
  const IntrusiveMultiSet& multiset = multiset_;
  EXPECT_EQ(multiset.max_size(), size_t(std::numeric_limits<ptrdiff_t>::max()));
}

// Modifiers

// This functions allows tests to use `std::is_sorted` without specializing
// `std::less<TestItem>`. Since `std::less` is the default value for the
// `Compare` template parameter, leaving it untouched avoids accidentally
// masking type-handling errors.
constexpr bool LessThan(const TestItem& lhs, const TestItem& rhs) {
  return lhs < rhs;
}

TEST_F(IntrusiveMultiSetTest, Insert) {
  multiset_.clear();
  bool sorted = true;
  size_t prev_key = 0;
  for (auto& item : items_) {
    sorted &= prev_key < item.key();

    // Use the "hinted" version of insert.
    multiset_.insert(multiset_.end(), item);
    prev_key = item.key();
  }
  EXPECT_FALSE(sorted);

  EXPECT_EQ(multiset_.size(), kNumItems);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));
}

TEST_F(IntrusiveMultiSetTest, Insert_Duplicate) {
  TestItem item1(60, "1");
  TestItem item2(60, "2");

  auto iter = multiset_.insert(item1);
  EXPECT_STREQ(iter->name(), "1");

  iter = multiset_.insert(item2);
  EXPECT_STREQ(iter->name(), "2");

  EXPECT_EQ(multiset_.size(), kNumItems + 2);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));

  // Explicitly clear the multiset before item 1 goes out of scope.
  multiset_.clear();
}

TEST_F(IntrusiveMultiSetTest, Insert_ObjectIterators) {
  multiset_.clear();
  multiset_.insert(items_.begin(), items_.end());
  EXPECT_EQ(multiset_.size(), kNumItems);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));
}

TEST_F(IntrusiveMultiSetTest, Insert_ObjectIterators_Empty) {
  multiset_.insert(items_.end(), items_.end());
  EXPECT_EQ(multiset_.size(), kNumItems);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));
}

TEST_F(IntrusiveMultiSetTest, Insert_ObjectIterators_WithDuplicates) {
  std::array<TestItem, 3> items = {{
      {50, "B"},
      {40, "D"},
      {60, "F"},
  }};

  multiset_.insert(items.begin(), items.end());
  EXPECT_EQ(multiset_.size(), kNumItems + 3);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));

  auto iter = multiset_.find(items[0]);
  ASSERT_NE(iter, multiset_.end());
  EXPECT_STREQ((iter++)->name(), "b");
  EXPECT_STREQ(iter->name(), "B");

  iter = multiset_.find(items[1]);
  ASSERT_NE(iter, multiset_.end());
  EXPECT_STREQ((iter++)->name(), "d");
  EXPECT_STREQ(iter->name(), "D");

  iter = multiset_.find(items[2]);
  ASSERT_NE(iter, multiset_.end());
  EXPECT_STREQ(iter->name(), "F");

  // Explicitly clear the multiset before items goes out of scope.
  multiset_.clear();
}

TEST_F(IntrusiveMultiSetTest, Insert_PointerIterators) {
  multiset_.clear();
  std::array<TestItem*, 3> ptrs = {&items_[0], &items_[1], &items_[2]};

  multiset_.insert(ptrs.begin(), ptrs.end());
  EXPECT_EQ(multiset_.size(), 3U);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));
}

TEST_F(IntrusiveMultiSetTest, Insert_PointerIterators_Empty) {
  std::array<TestItem*, 0> ptrs;

  multiset_.insert(ptrs.begin(), ptrs.end());
  EXPECT_EQ(multiset_.size(), kNumItems);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));
}

TEST_F(IntrusiveMultiSetTest, Insert_PointerIterators_WithDuplicates) {
  TestItem item1(50, "B");
  TestItem item2(40, "D");
  TestItem item3(60, "F");
  std::array<TestItem*, 3> ptrs = {&item1, &item2, &item3};

  multiset_.insert(ptrs.begin(), ptrs.end());
  EXPECT_EQ(multiset_.size(), kNumItems + 3);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));

  auto iter = multiset_.find(item1);
  ASSERT_NE(iter, multiset_.end());
  EXPECT_STREQ((iter++)->name(), "b");
  EXPECT_STREQ(iter->name(), "B");

  iter = multiset_.find(item2);
  ASSERT_NE(iter, multiset_.end());
  EXPECT_STREQ((iter++)->name(), "d");
  EXPECT_STREQ(iter->name(), "D");

  iter = multiset_.find(item3);
  ASSERT_NE(iter, multiset_.end());
  EXPECT_STREQ(iter->name(), "F");

  // Explicitly clear the multiset before items goes out of scope.
  multiset_.clear();
}

TEST_F(IntrusiveMultiSetTest, Insert_InitializerList) {
  multiset_.clear();
  multiset_.insert({&items_[0], &items_[2], &items_[4]});
  EXPECT_EQ(multiset_.size(), 3U);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));
}

TEST_F(IntrusiveMultiSetTest, Insert_InitializerList_Empty) {
  multiset_.insert({});
  EXPECT_EQ(multiset_.size(), kNumItems);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));
}

TEST_F(IntrusiveMultiSetTest, Insert_InitializerList_WithDuplicates) {
  TestItem item1(50, "B");
  TestItem item2(40, "D");
  TestItem item3(60, "F");

  multiset_.insert({&item1, &item2, &item3});
  EXPECT_EQ(multiset_.size(), kNumItems + 3);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));

  auto iter = multiset_.find(item1);
  ASSERT_NE(iter, multiset_.end());
  EXPECT_STREQ((iter++)->name(), "b");
  EXPECT_STREQ(iter->name(), "B");

  iter = multiset_.find(item2);
  ASSERT_NE(iter, multiset_.end());
  EXPECT_STREQ((iter++)->name(), "d");
  EXPECT_STREQ(iter->name(), "D");

  iter = multiset_.find(item3);
  ASSERT_NE(iter, multiset_.end());
  EXPECT_STREQ(iter->name(), "F");

  // Explicitly clear the multiset before items goes out of scope.
  multiset_.clear();
}

// An item derived from TestItem.
struct DerivedItem : public TestItem {
  DerivedItem(size_t n, const char* name) : TestItem(n * 10, name) {}
};

TEST_F(IntrusiveMultiSetTest, Insert_DerivedItems) {
  DerivedItem item1(6, "f");
  multiset_.insert(item1);

  DerivedItem item2(7, "g");
  multiset_.insert(item2);

  EXPECT_EQ(multiset_.size(), kNumItems + 2);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));

  // Explicitly clear the multiset before items goes out of scope.
  multiset_.clear();
}

TEST_F(IntrusiveMultiSetTest, Insert_DerivedItems_CompilationFails) {
  ::pw::IntrusiveMultiSet<DerivedItem> derived_from_compatible_item_type;

  DerivedItem item1(6, "f");
  derived_from_compatible_item_type.insert(item1);

  EXPECT_EQ(derived_from_compatible_item_type.size(), 1U);

#if PW_NC_TEST(CannotAddBaseClassToDerivedClassSet)
  PW_NC_EXPECT("derived_from_compatible_item_type\.insert\(item2\)");

  TestItem item2(70, "g");
  derived_from_compatible_item_type.insert(item2);
#endif
  derived_from_compatible_item_type.clear();
}

TEST_F(IntrusiveMultiSetTest, Erase_OneItem) {
  for (size_t i = 0; i < kNumItems; ++i) {
    EXPECT_EQ(multiset_.size(), kNumItems);
    EXPECT_EQ(multiset_.erase(items_[i]), 1U);
    EXPECT_EQ(multiset_.size(), kNumItems - 1);
    auto iter = multiset_.find(items_[i]);
    EXPECT_EQ(iter, multiset_.end());
    multiset_.insert(items_[i]);
  }
}

TEST_F(IntrusiveMultiSetTest, Erase_OnlyItem) {
  multiset_.clear();
  multiset_.insert(items_[0]);
  EXPECT_EQ(multiset_.size(), 1U);

  EXPECT_EQ(multiset_.erase(items_[0]), 1U);
  EXPECT_EQ(multiset_.size(), 0U);
}

TEST_F(IntrusiveMultiSetTest, Erase_AllOnebyOne) {
  auto iter = multiset_.begin();
  for (size_t n = kNumItems; n != 0; --n) {
    ASSERT_NE(iter, multiset_.end());
    iter = multiset_.erase(iter);
  }
  EXPECT_EQ(iter, multiset_.end());
  EXPECT_EQ(multiset_.size(), 0U);
}

TEST_F(IntrusiveMultiSetTest, Erase_Range) {
  auto first = multiset_.begin();
  auto last = multiset_.end();
  ++first;
  --last;
  auto iter = multiset_.erase(first, last);
  EXPECT_EQ(multiset_.size(), 2U);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));
  EXPECT_EQ(iter->key(), 55U);
}

TEST_F(IntrusiveMultiSetTest, Erase_MissingItem) {
  EXPECT_EQ(multiset_.erase({100, "-"}), 0U);
}

TEST_F(IntrusiveMultiSetTest, Erase_Reinsert) {
  EXPECT_EQ(multiset_.size(), items_.size());

  EXPECT_EQ(multiset_.erase(items_[0]), 1U);
  EXPECT_EQ(multiset_.find(items_[0]), multiset_.end());

  EXPECT_EQ(multiset_.erase(items_[2]), 1U);
  EXPECT_EQ(multiset_.find(items_[2]), multiset_.end());

  EXPECT_EQ(multiset_.erase(items_[4]), 1U);
  EXPECT_EQ(multiset_.find(items_[4]), multiset_.end());

  EXPECT_EQ(multiset_.size(), items_.size() - 3);

  multiset_.insert(items_[4]);
  auto iter = multiset_.find(items_[4]);
  EXPECT_NE(iter, multiset_.end());

  multiset_.insert(items_[0]);
  iter = multiset_.find(items_[0]);
  EXPECT_NE(iter, multiset_.end());

  multiset_.insert(items_[2]);
  iter = multiset_.find(items_[2]);
  EXPECT_NE(iter, multiset_.end());

  EXPECT_EQ(multiset_.size(), items_.size());
}

TEST_F(IntrusiveMultiSetTest, Erase_Duplicate) {
  TestItem item1(32, "1");
  TestItem item2(32, "2");
  TestItem item3(32, "3");
  multiset_.insert(item1);
  multiset_.insert(item2);
  multiset_.insert(item3);

  auto iter = multiset_.find({32, "?"});
  ASSERT_NE(iter, multiset_.end());
  EXPECT_STREQ(iter->name(), "1");

  iter = multiset_.erase(iter);
  ASSERT_NE(iter, multiset_.end());
  EXPECT_STREQ(iter->name(), "2");

  iter = multiset_.erase(iter);
  ASSERT_NE(iter, multiset_.end());
  EXPECT_STREQ(iter->name(), "3");

  multiset_.erase(iter);
  EXPECT_EQ(multiset_.find({32, "?"}), multiset_.end());
}

TEST_F(IntrusiveMultiSetTest, Swap) {
  std::array<TestItem, 3> items = {{
      {50, "B"},
      {40, "D"},
      {60, "F"},
  }};
  IntrusiveMultiSet multiset(items.begin(), items.end());

  multiset_.swap(multiset);
  EXPECT_EQ(multiset.size(), kNumItems);
  EXPECT_TRUE(std::is_sorted(multiset.begin(), multiset.end(), LessThan));
  auto iter = multiset.begin();
  EXPECT_STREQ((iter++)->name(), "e");
  EXPECT_STREQ((iter++)->name(), "E");
  EXPECT_STREQ((iter++)->name(), "c");
  EXPECT_STREQ((iter++)->name(), "C");
  EXPECT_STREQ((iter++)->name(), "a");
  EXPECT_STREQ((iter++)->name(), "A");
  EXPECT_STREQ((iter++)->name(), "d");
  EXPECT_STREQ((iter++)->name(), "D");
  EXPECT_STREQ((iter++)->name(), "b");
  EXPECT_STREQ((iter++)->name(), "B");
  EXPECT_EQ(iter, multiset.end());
  multiset.clear();

  EXPECT_EQ(multiset_.size(), 3U);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));
  iter = multiset_.begin();
  EXPECT_STREQ((iter++)->name(), "D");
  EXPECT_STREQ((iter++)->name(), "B");
  EXPECT_STREQ((iter++)->name(), "F");
  EXPECT_EQ(iter, multiset_.end());

  // Explicitly clear the multiset before items goes out of scope.
  multiset_.clear();
}

TEST_F(IntrusiveMultiSetTest, Swap_Empty) {
  IntrusiveMultiSet multiset;

  multiset_.swap(multiset);
  EXPECT_EQ(multiset.size(), kNumItems);
  EXPECT_TRUE(std::is_sorted(multiset.begin(), multiset.end(), LessThan));
  auto iter = multiset.begin();
  EXPECT_STREQ((iter++)->name(), "e");
  EXPECT_STREQ((iter++)->name(), "E");
  EXPECT_STREQ((iter++)->name(), "c");
  EXPECT_STREQ((iter++)->name(), "C");
  EXPECT_STREQ((iter++)->name(), "a");
  EXPECT_STREQ((iter++)->name(), "A");
  EXPECT_STREQ((iter++)->name(), "d");
  EXPECT_STREQ((iter++)->name(), "D");
  EXPECT_STREQ((iter++)->name(), "b");
  EXPECT_STREQ((iter++)->name(), "B");
  EXPECT_EQ(iter, multiset.end());
  multiset.clear();

  EXPECT_EQ(multiset_.size(), 0U);
}

TEST_F(IntrusiveMultiSetTest, Merge) {
  std::array<TestItem, 3> items = {{
      {5, "f"},
      {75, "g"},
      {85, "h"},
  }};
  IntrusiveMultiSet multiset(items.begin(), items.end());

  multiset_.merge(multiset);
  EXPECT_TRUE(multiset.empty());
  EXPECT_EQ(multiset_.size(), kNumItems + 3);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));
  auto iter = multiset_.begin();
  EXPECT_STREQ((iter++)->name(), "f");
  EXPECT_STREQ((iter++)->name(), "e");
  EXPECT_STREQ((iter++)->name(), "E");
  EXPECT_STREQ((iter++)->name(), "c");
  EXPECT_STREQ((iter++)->name(), "C");
  EXPECT_STREQ((iter++)->name(), "a");
  EXPECT_STREQ((iter++)->name(), "A");
  EXPECT_STREQ((iter++)->name(), "d");
  EXPECT_STREQ((iter++)->name(), "D");
  EXPECT_STREQ((iter++)->name(), "b");
  EXPECT_STREQ((iter++)->name(), "B");
  EXPECT_STREQ((iter++)->name(), "g");
  EXPECT_STREQ((iter++)->name(), "h");
  EXPECT_EQ(iter, multiset_.end());

  // Explicitly clear the multiset before items goes out of scope.
  multiset_.clear();
}

TEST_F(IntrusiveMultiSetTest, Merge_Empty) {
  IntrusiveMultiSet multiset;

  multiset_.merge(multiset);
  EXPECT_EQ(multiset_.size(), kNumItems);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));

  multiset.merge(multiset_);
  EXPECT_TRUE(multiset_.empty());
  EXPECT_EQ(multiset.size(), kNumItems);
  EXPECT_TRUE(std::is_sorted(multiset.begin(), multiset.end(), LessThan));

  multiset.clear();
}

TEST_F(IntrusiveMultiSetTest, Merge_WithDuplicates) {
  std::array<TestItem, 3> items = {{
      {15, "f"},
      {45, "g"},
      {55, "h"},
  }};
  IntrusiveMultiSet multiset(items.begin(), items.end());

  multiset_.merge(multiset);
  EXPECT_TRUE(multiset.empty());
  EXPECT_EQ(multiset_.size(), kNumItems + 3);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));
  auto iter = multiset_.begin();
  EXPECT_STREQ((iter++)->name(), "e");
  EXPECT_STREQ((iter++)->name(), "E");
  EXPECT_STREQ((iter++)->name(), "f");
  EXPECT_STREQ((iter++)->name(), "c");
  EXPECT_STREQ((iter++)->name(), "C");
  EXPECT_STREQ((iter++)->name(), "a");
  EXPECT_STREQ((iter++)->name(), "A");
  EXPECT_STREQ((iter++)->name(), "d");
  EXPECT_STREQ((iter++)->name(), "D");
  EXPECT_STREQ((iter++)->name(), "g");
  EXPECT_STREQ((iter++)->name(), "b");
  EXPECT_STREQ((iter++)->name(), "B");
  EXPECT_STREQ((iter++)->name(), "h");
  EXPECT_EQ(iter, multiset_.end());

  // Explicitly clear the multiset before items goes out of scope.
  multiset_.clear();
}

TEST_F(IntrusiveMultiSetTest, Merge_Set) {
  std::array<TestItem, 3> items = {{
      {15, "f"},
      {45, "g"},
      {55, "h"},
  }};
  ::pw::IntrusiveSet<TestItem> set(items.begin(), items.end());

  multiset_.merge(set);
  EXPECT_TRUE(set.empty());
  EXPECT_EQ(multiset_.size(), kNumItems + 3);
  EXPECT_TRUE(std::is_sorted(multiset_.begin(), multiset_.end(), LessThan));
  auto iter = multiset_.begin();
  EXPECT_STREQ((iter++)->name(), "e");
  EXPECT_STREQ((iter++)->name(), "E");
  EXPECT_STREQ((iter++)->name(), "f");
  EXPECT_STREQ((iter++)->name(), "c");
  EXPECT_STREQ((iter++)->name(), "C");
  EXPECT_STREQ((iter++)->name(), "a");
  EXPECT_STREQ((iter++)->name(), "A");
  EXPECT_STREQ((iter++)->name(), "d");
  EXPECT_STREQ((iter++)->name(), "D");
  EXPECT_STREQ((iter++)->name(), "g");
  EXPECT_STREQ((iter++)->name(), "b");
  EXPECT_STREQ((iter++)->name(), "B");
  EXPECT_STREQ((iter++)->name(), "h");
  EXPECT_EQ(iter, multiset_.end());

  // Explicitly clear the multiset before items goes out of scope.
  multiset_.clear();
}

TEST_F(IntrusiveMultiSetTest, Count) {
  const IntrusiveMultiSet& multiset = multiset_;
  EXPECT_EQ(multiset.count({10, "?"}), 1U);
  EXPECT_EQ(multiset.count({20, "?"}), 1U);
  EXPECT_EQ(multiset.count({30, "?"}), 1U);
  EXPECT_EQ(multiset.count({40, "?"}), 1U);
  EXPECT_EQ(multiset.count({50, "?"}), 1U);
}

TEST_F(IntrusiveMultiSetTest, Count_NoSuchKey) {
  const IntrusiveMultiSet& multiset = multiset_;
  EXPECT_EQ(multiset.count({60, "?"}), 0U);
}

TEST_F(IntrusiveMultiSetTest, Count_WithDuplicates) {
  std::array<TestItem, 3> items = {{
      {50, "B"},
      {40, "D"},
      {60, "F"},
  }};
  multiset_.insert(items.begin(), items.end());

  EXPECT_EQ(multiset_.count({10, "?"}), 1U);
  EXPECT_EQ(multiset_.count({20, "?"}), 1U);
  EXPECT_EQ(multiset_.count({30, "?"}), 1U);
  EXPECT_EQ(multiset_.count({40, "?"}), 2U);
  EXPECT_EQ(multiset_.count({50, "?"}), 2U);
  EXPECT_EQ(multiset_.count({60, "?"}), 1U);

  // Explicitly clear the multiset before items goes out of scope.
  multiset_.clear();
}

TEST_F(IntrusiveMultiSetTest, Find) {
  const IntrusiveMultiSet& multiset = multiset_;
  size_t key = 10;
  for (size_t i = 0; i < kNumItems; ++i) {
    auto iter = multiset.find({key, "?"});
    ASSERT_NE(iter, multiset.end());
    EXPECT_EQ(iter->key(), key);
    key += 5;
  }
}

TEST_F(IntrusiveMultiSetTest, Find_NoSuchKey) {
  const IntrusiveMultiSet& multiset = multiset_;
  auto iter = multiset.find({60, "?"});
  EXPECT_EQ(iter, multiset.end());
}

TEST_F(IntrusiveMultiSetTest, Find_WithDuplicates) {
  std::array<TestItem, 3> items = {{
      {50, "B"},
      {40, "D"},
      {60, "F"},
  }};
  multiset_.insert(items.begin(), items.end());

  auto iter = multiset_.find({40, "?"});
  ASSERT_NE(iter, multiset_.end());
  EXPECT_EQ(iter->key(), 40U);
  EXPECT_EQ((iter++)->name(), "d");
  EXPECT_EQ(iter->key(), 40U);
  EXPECT_EQ(iter->name(), "D");

  iter = multiset_.find({50, "?"});
  ASSERT_NE(iter, multiset_.end());
  EXPECT_EQ(iter->key(), 50U);
  EXPECT_EQ((iter++)->name(), "b");
  EXPECT_EQ(iter->key(), 50U);
  EXPECT_EQ(iter->name(), "B");

  // Explicitly clear the multiset before items goes out of scope.
  multiset_.clear();
}

TEST_F(IntrusiveMultiSetTest, LowerBound) {
  const IntrusiveMultiSet& multiset = multiset_;
  auto iter = multiset.lower_bound({10, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "e");

  iter = multiset.lower_bound({20, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "c");

  iter = multiset.lower_bound({30, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "a");

  iter = multiset.lower_bound({40, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "d");

  iter = multiset.lower_bound({50, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "b");
}

TEST_F(IntrusiveMultiSetTest, LowerBound_NoExactKey) {
  const IntrusiveMultiSet& multiset = multiset_;
  auto iter = multiset.lower_bound({6, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "e");

  iter = multiset.lower_bound({16, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "c");

  iter = multiset.lower_bound({26, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "a");

  iter = multiset.lower_bound({36, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "d");

  iter = multiset.lower_bound({46, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "b");
}

TEST_F(IntrusiveMultiSetTest, LowerBound_OutOfRange) {
  const IntrusiveMultiSet& multiset = multiset_;
  EXPECT_EQ(multiset.lower_bound({56, "?"}), multiset.end());
}

TEST_F(IntrusiveMultiSetTest, LowerBound_WithDuplicates) {
  TestItem item1(20, "1");
  TestItem item2(40, "1");
  TestItem item3(40, "1");
  multiset_.insert(item1);
  multiset_.insert(item2);
  multiset_.insert(item3);
  EXPECT_EQ(multiset_.size(), items_.size() + 3);

  auto iter = multiset_.lower_bound({20, "?"});
  EXPECT_LT((--iter)->key(), 20U);
  EXPECT_EQ((++iter)->key(), 20U);
  EXPECT_EQ((++iter)->key(), 20U);
  EXPECT_GT((++iter)->key(), 20U);

  iter = multiset_.lower_bound({40, "?"});
  EXPECT_LT((--iter)->key(), 40U);
  EXPECT_EQ((++iter)->key(), 40U);
  EXPECT_EQ((++iter)->key(), 40U);
  EXPECT_EQ((++iter)->key(), 40U);
  EXPECT_GT((++iter)->key(), 40U);

  // Explicitly clear the multiset before items 1-3 go out of scope.
  multiset_.clear();
}

TEST_F(IntrusiveMultiSetTest, UpperBound) {
  const IntrusiveMultiSet& multiset = multiset_;
  auto iter = multiset.upper_bound({15, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "c");

  iter = multiset.upper_bound({25, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "a");

  iter = multiset.upper_bound({35, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "d");

  iter = multiset.upper_bound({45, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "b");
}

TEST_F(IntrusiveMultiSetTest, UpperBound_NoExactKey) {
  const IntrusiveMultiSet& multiset = multiset_;
  auto iter = multiset.upper_bound({6, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "e");

  iter = multiset.upper_bound({16, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "c");

  iter = multiset.upper_bound({26, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "a");

  iter = multiset.upper_bound({36, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "d");

  iter = multiset.upper_bound({46, "?"});
  ASSERT_NE(iter, multiset.end());
  EXPECT_STREQ(iter->name(), "b");
}

TEST_F(IntrusiveMultiSetTest, UpperBound_OutOfRange) {
  const IntrusiveMultiSet& multiset = multiset_;
  EXPECT_EQ(multiset.upper_bound({56, "?"}), multiset.end());
}

TEST_F(IntrusiveMultiSetTest, UpperBound_WithDuplicates) {
  TestItem item1(20, "1");
  TestItem item2(40, "1");
  TestItem item3(40, "1");
  multiset_.insert(item1);
  multiset_.insert(item2);
  multiset_.insert(item3);
  EXPECT_EQ(multiset_.size(), items_.size() + 3);

  auto iter = multiset_.upper_bound({20, "?"});
  EXPECT_GT(iter->key(), 20U);

  iter = multiset_.upper_bound({40, "?"});
  EXPECT_GT(iter->key(), 40U);

  // Explicitly clear the multiset before items 1-3 go out of scope.
  multiset_.clear();
}

TEST_F(IntrusiveMultiSetTest, EqualRange) {
  const IntrusiveMultiSet& multiset = multiset_;

  auto pair = multiset.equal_range({10, "?"});
  IntrusiveMultiSet::const_iterator lower = pair.first;
  IntrusiveMultiSet::const_iterator upper = pair.second;
  ASSERT_NE(lower, multiset.end());
  EXPECT_STREQ(lower->name(), "e");
  ASSERT_NE(upper, multiset.end());
  EXPECT_STREQ(upper->name(), "E");

  std::tie(lower, upper) = multiset.equal_range({20, "?"});
  ASSERT_NE(lower, multiset.end());
  EXPECT_STREQ(lower->name(), "c");
  ASSERT_NE(upper, multiset.end());
  EXPECT_STREQ(upper->name(), "C");

  std::tie(lower, upper) = multiset.equal_range({30, "?"});
  ASSERT_NE(lower, multiset.end());
  EXPECT_STREQ(lower->name(), "a");
  ASSERT_NE(upper, multiset.end());
  EXPECT_STREQ(upper->name(), "A");

  std::tie(lower, upper) = multiset.equal_range({40, "?"});
  ASSERT_NE(lower, multiset.end());
  EXPECT_STREQ(lower->name(), "d");
  ASSERT_NE(upper, multiset.end());
  EXPECT_STREQ(upper->name(), "D");

  std::tie(lower, upper) = multiset.equal_range({50, "?"});
  ASSERT_NE(lower, multiset.end());
  EXPECT_STREQ(lower->name(), "b");
  ASSERT_NE(upper, multiset.end());
  EXPECT_STREQ(upper->name(), "B");
}

TEST_F(IntrusiveMultiSetTest, EqualRange_NoExactKey) {
  const IntrusiveMultiSet& multiset = multiset_;

  auto pair = multiset.equal_range({6, "?"});
  IntrusiveMultiSet::const_iterator lower = pair.first;
  IntrusiveMultiSet::const_iterator upper = pair.second;
  ASSERT_NE(lower, multiset.end());
  EXPECT_STREQ(lower->name(), "e");
  ASSERT_NE(upper, multiset.end());
  EXPECT_STREQ(upper->name(), "e");

  std::tie(lower, upper) = multiset.equal_range({16, "?"});
  ASSERT_NE(lower, multiset.end());
  EXPECT_STREQ(lower->name(), "c");
  ASSERT_NE(upper, multiset.end());
  EXPECT_STREQ(upper->name(), "c");

  std::tie(lower, upper) = multiset.equal_range({26, "?"});
  ASSERT_NE(lower, multiset.end());
  EXPECT_STREQ(lower->name(), "a");
  ASSERT_NE(upper, multiset.end());
  EXPECT_STREQ(upper->name(), "a");

  std::tie(lower, upper) = multiset.equal_range({36, "?"});
  ASSERT_NE(lower, multiset.end());
  EXPECT_STREQ(lower->name(), "d");
  ASSERT_NE(upper, multiset.end());
  EXPECT_STREQ(upper->name(), "d");

  std::tie(lower, upper) = multiset.equal_range({46, "?"});
  ASSERT_NE(lower, multiset.end());
  EXPECT_STREQ(lower->name(), "b");
  ASSERT_NE(upper, multiset.end());
  EXPECT_STREQ(upper->name(), "b");
}

TEST_F(IntrusiveMultiSetTest, EqualRange_OutOfRange) {
  const IntrusiveMultiSet& multiset = multiset_;

  auto pair = multiset.equal_range({56, "?"});
  IntrusiveMultiSet::const_iterator lower = pair.first;
  IntrusiveMultiSet::const_iterator upper = pair.second;
  EXPECT_EQ(lower, multiset.end());
  EXPECT_EQ(upper, multiset.end());
}

TEST_F(IntrusiveMultiSetTest, EqualRange_WithDuplicates) {
  TestItem item1(40, "1");
  TestItem item2(40, "2");
  TestItem item3(40, "3");
  multiset_.insert(item1);
  multiset_.insert(item2);
  multiset_.insert(item3);

  auto result = multiset_.equal_range({40, "?"});
  EXPECT_EQ(std::distance(result.first, result.second), 4);

  // Explicitly clear the multiset before items 1-3 go out of scope.
  multiset_.clear();
}

}  // namespace
