// SPDX-License-Identifier: BSD-2-Clause
// Copyright CM4all GmbH
// author: Max Kellermann <max.kellermann@ionos.com>

#include "util/IntrusiveTreeSet.hxx"

#include <gtest/gtest.h>

#include <cstdlib> // for std::rand()
#include <deque>
#include <set>
#include <string>

namespace {

struct IntItem final : IntrusiveTreeSetHook<IntrusiveHookMode::TRACK> {
	int value;

	IntItem(int _value) noexcept:value(_value) {}

	struct GetKey {
		constexpr int operator()(const IntItem &item) const noexcept {
			return item.value;
		}
	};
};

} // anonymous namespace

template<bool constant_time_size>
static void
TestBasic()
{
	IntItem a{1}, b{2}, c{3}, d{4}, e{5}, f{1};

	IntrusiveTreeSet<IntItem,
		IntrusiveTreeSetOperators<IntItem, IntItem::GetKey>,
		IntrusiveTreeSetBaseHookTraits<IntItem>,
		IntrusiveTreeSetOptions{.constant_time_size=constant_time_size}> set;

	EXPECT_EQ(set.size(), 0U);
	EXPECT_TRUE(set.empty());

	EXPECT_FALSE(a.is_linked());
	EXPECT_FALSE(b.is_linked());

	set.insert(b);

	EXPECT_FALSE(a.is_linked());
	EXPECT_TRUE(b.is_linked());

	EXPECT_EQ(set.size(), 1U);
	EXPECT_EQ(set.find(2), set.iterator_to(b));
	EXPECT_EQ(&set.front(), &b);

	set.insert(a);
	EXPECT_EQ(&set.front(), &a);

	EXPECT_TRUE(a.is_linked());
	EXPECT_TRUE(b.is_linked());

	set.insert(c);
	EXPECT_EQ(&set.front(), &a);

	EXPECT_EQ(set.size(), 3U);

	EXPECT_NE(set.find(3), set.end());
	EXPECT_EQ(set.find(3), set.iterator_to(c));

	EXPECT_EQ(set.find(4), set.end());

	EXPECT_TRUE(c.is_linked());

	set.erase(set.iterator_to(c));

	EXPECT_FALSE(c.is_linked());

	EXPECT_EQ(set.size(), 2U);
	EXPECT_EQ(set.find(3), set.end());
	EXPECT_EQ(&set.front(), &a);

	set.insert(c);
	set.insert(d);
	set.insert(e);

	EXPECT_EQ(set.size(), 5U);
	EXPECT_EQ(&set.front(), &a);

	EXPECT_EQ(set.find(1), set.iterator_to(a));
	EXPECT_EQ(set.find(2), set.iterator_to(b));
	EXPECT_EQ(set.find(3), set.iterator_to(c));
	EXPECT_EQ(set.find(4), set.iterator_to(d));
	EXPECT_EQ(set.find(5), set.iterator_to(e));

	EXPECT_TRUE(a.is_linked());
	EXPECT_FALSE(f.is_linked());

	set.erase(set.iterator_to(a));
	EXPECT_FALSE(a.is_linked());
	EXPECT_FALSE(f.is_linked());
	EXPECT_EQ(set.find(1), set.end());
	EXPECT_EQ(set.size(), 4U);
	EXPECT_EQ(&set.front(), &b);

	set.insert(f);
	EXPECT_FALSE(a.is_linked());
	EXPECT_TRUE(f.is_linked());
	EXPECT_EQ(set.find(1), set.iterator_to(f));
	EXPECT_EQ(set.size(), 5U);
	EXPECT_EQ(&set.front(), &f);

	set.pop_front();
	EXPECT_FALSE(f.is_linked());

	set.clear_and_dispose([](auto *i){ i->value = -1; });

	EXPECT_EQ(set.size(), 0U);
	EXPECT_TRUE(set.empty());

	EXPECT_EQ(a.value, 1);
	EXPECT_EQ(b.value, -1);
	EXPECT_EQ(c.value, -1);
	EXPECT_EQ(d.value, -1);
	EXPECT_EQ(e.value, -1);
	EXPECT_EQ(f.value, 1);
}

TEST(IntrusiveTreeSet, Basic)
{
	TestBasic<false>();
	TestBasic<true>();
}

template<int... values>
static constexpr auto
MakeIntItems(std::integer_sequence<int, values...>) noexcept
	-> std::array<IntItem, sizeof...(values)>
{
	return {values...};
}

TEST(IntrusiveTreeSet, RandomOrder)
{
	auto items = MakeIntItems(std::make_integer_sequence<int, 32>());

	IntrusiveTreeSet<IntItem,
			 IntrusiveTreeSetOperators<IntItem, IntItem::GetKey>> set;

	set.insert(items[0]);
	set.insert(items[5]);
	set.insert(items[10]);
	set.insert(items[15]);
	set.insert(items[20]);
	set.insert(items[25]);
	set.insert(items[30]);
	set.insert(items[1]);
	set.insert(items[2]);
	set.insert(items[3]);
	set.insert(items[31]);
	set.insert(items[4]);
	set.insert(items[6]);
	set.insert(items[7]);
	set.insert(items[21]);
	set.insert(items[22]);
	set.insert(items[23]);
	set.insert(items[24]);
	set.insert(items[26]);
	set.insert(items[8]);
	set.insert(items[9]);
	set.insert(items[11]);
	set.insert(items[12]);
	set.insert(items[13]);
	set.insert(items[14]);
	set.insert(items[27]);
	set.insert(items[28]);
	set.insert(items[29]);
	set.insert(items[16]);
	set.insert(items[17]);
	set.insert(items[18]);
	set.insert(items[19]);

	EXPECT_EQ(set.size(), items.size());

	for (const auto &i : items) {
		EXPECT_TRUE(i.is_linked());
	}

	int expected = 0;
	for (const auto &i : set) {
		EXPECT_EQ(i.value, expected++);
	}

	for (std::size_t remove = 0; remove < items.size(); ++remove) {
		EXPECT_TRUE(items[remove].is_linked());
		set.pop_front();
		EXPECT_FALSE(items[remove].is_linked());

#ifndef NDEBUG
		set.Check();
#endif

		expected = remove + 1;
		for (const auto &i : set) {
			EXPECT_EQ(i.value, expected++);
		}
	}
}

TEST(IntrusiveTreeSet, LargeRandom)
{
	std::set<int> dedup;
	std::deque<std::unique_ptr<IntItem>> items;
	IntrusiveTreeSet<IntItem,
			 IntrusiveTreeSetOperators<IntItem, IntItem::GetKey>> set;

	std::srand(42);
	for (unsigned i = 0; i < 1024; ++i) {
		/* prevent duplicates to avoid different ordering of
		   identical values in the std::deque and in the
		   IntrusiveTreeSet */
		int value;
		while (true) {
			value = std::rand();
			if (dedup.insert(value).second)
				break;
		}

		items.emplace_back(std::make_unique<IntItem>(value));
		set.insert(*items.back());

#ifndef NDEBUG
		set.Check();
#endif
	}

	std::sort(items.begin(), items.end(), [](const auto &a, const auto &b){
		return a->value < b->value;
	});

	std::size_t idx = 0;

	for (const auto &i : set) {
		EXPECT_EQ(i.value, items[idx++]->value);
	}

	while (!set.empty()) {
		EXPECT_FALSE(items.empty());
		EXPECT_TRUE(items.front()->is_linked());
		EXPECT_EQ(items.front()->value, set.front().value);

		// erase the front element
		set.pop_front();
		EXPECT_FALSE(items.front()->is_linked());
		items.pop_front();

#ifndef NDEBUG
		set.Check();
#endif

		// erase a random element
		const auto r = std::next(items.begin(), std::rand() % items.size());
		EXPECT_TRUE((*r)->is_linked());
		set.erase(set.iterator_to(**r));
		EXPECT_FALSE((*r)->is_linked());
		items.erase(r);

#ifndef NDEBUG
		set.Check();
#endif

		idx = 0;
		for (const auto &i : set) {
			EXPECT_EQ(i.value, items[idx++]->value);
		}
	}

	EXPECT_TRUE(items.empty());
}

/**
 * Verify that is_linked() returns false for all items after clear().
 */
TEST(IntrusiveTreeSet, ClearIsLinked)
{
	IntItem a{1}, b{2}, c{3}, d{4}, e{5};

	IntrusiveTreeSet<IntItem,
			 IntrusiveTreeSetOperators<IntItem, IntItem::GetKey>> set;

	set.insert(a);
	set.insert(b);
	set.insert(c);
	set.insert(d);
	set.insert(e);

	EXPECT_TRUE(a.is_linked());
	EXPECT_TRUE(b.is_linked());
	EXPECT_TRUE(c.is_linked());
	EXPECT_TRUE(d.is_linked());
	EXPECT_TRUE(e.is_linked());
	EXPECT_EQ(set.size(), 5U);

	set.clear();

	EXPECT_TRUE(set.empty());
	EXPECT_FALSE(a.is_linked());
	EXPECT_FALSE(b.is_linked());
	EXPECT_FALSE(c.is_linked());
	EXPECT_FALSE(d.is_linked());
	EXPECT_FALSE(e.is_linked());

	/* verify items can be re-inserted after clear() */
	set.insert(c);
	set.insert(a);

	EXPECT_EQ(set.size(), 2U);
	EXPECT_TRUE(a.is_linked());
	EXPECT_FALSE(b.is_linked());
	EXPECT_TRUE(c.is_linked());
	EXPECT_EQ(&set.front(), &a);
	EXPECT_EQ(set.find(1), set.iterator_to(a));
	EXPECT_EQ(set.find(3), set.iterator_to(c));

#ifndef NDEBUG
	set.Check();
#endif
}

/**
 * Verify that is_linked() returns false for all items after
 * clear_and_dispose().
 */
TEST(IntrusiveTreeSet, ClearAndDisposeIsLinked)
{
	IntItem a{1}, b{2}, c{3};

	IntrusiveTreeSet<IntItem,
			 IntrusiveTreeSetOperators<IntItem, IntItem::GetKey>> set;

	set.insert(a);
	set.insert(b);
	set.insert(c);

	unsigned dispose_count = 0;
	set.clear_and_dispose([&dispose_count](auto *) { ++dispose_count; });

	EXPECT_EQ(dispose_count, 3U);
	EXPECT_TRUE(set.empty());
	EXPECT_FALSE(a.is_linked());
	EXPECT_FALSE(b.is_linked());
	EXPECT_FALSE(c.is_linked());

	/* verify items can be re-inserted after clear_and_dispose() */
	set.insert(b);

	EXPECT_EQ(set.size(), 1U);
	EXPECT_TRUE(b.is_linked());
	EXPECT_EQ(&set.front(), &b);

#ifndef NDEBUG
	set.Check();
#endif
}

struct ZeroIntItem final : IntrusiveTreeSetHook<IntrusiveHookMode::TRACK> {
	int value = 0;

	struct GetKey {
		constexpr int operator()(const ZeroIntItem &item) const noexcept {
			return item.value;
		}
	};
};

/**
 * Fill a tree with many all-zero values.  This verifies the
 * robustness of the RedBlackTree implementation for this corner case.
 */
TEST(IntrusiveTreeSet, Zero)
{
	std::array<ZeroIntItem, 1024> items;

	IntrusiveTreeSet<ZeroIntItem,
			 IntrusiveTreeSetOperators<ZeroIntItem, ZeroIntItem::GetKey>> set;

	set.insert(items[0]);
	set.insert(items[5]);
	set.insert(items[10]);
	set.insert(items[15]);
	set.insert(items[20]);
	set.insert(items[25]);
	set.insert(items[30]);
	set.insert(items[1]);
	set.insert(items[2]);
	set.insert(items[3]);
	set.insert(items[31]);
	set.insert(items[4]);
	set.insert(items[6]);
	set.insert(items[7]);
	set.insert(items[21]);
	set.insert(items[22]);
	set.insert(items[23]);
	set.insert(items[24]);
	set.insert(items[26]);
	set.insert(items[8]);
	set.insert(items[9]);
	set.insert(items[11]);
	set.insert(items[12]);
	set.insert(items[13]);
	set.insert(items[14]);
	set.insert(items[27]);
	set.insert(items[28]);
	set.insert(items[29]);
	set.insert(items[16]);
	set.insert(items[17]);
	set.insert(items[18]);
	set.insert(items[19]);

	for (std::size_t i = 32; i < items.size(); ++i)
		set.insert(items[i]);

	EXPECT_EQ(set.size(), items.size());

	for (const auto &i : items) {
		EXPECT_TRUE(i.is_linked());
	}

	for (const auto &i : set) {
		EXPECT_EQ(i.value, 0);
	}

	for (auto &i : items) {
		EXPECT_TRUE(i.is_linked());
		set.erase(set.iterator_to(i));
		EXPECT_FALSE(i.is_linked());

#ifndef NDEBUG
		set.Check();
#endif
	}

	EXPECT_TRUE(set.empty());
}
