/*--------------------------------------------------------------------------
 *
 * test_rbtree.c
 *		Test correctness of red-black tree operations.
 *
 * Copyright (c) 2009-2024, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *		src/test/modules/test_rbtree/test_rbtree.c
 *
 * -------------------------------------------------------------------------
 */

#include "postgres.h"

#include "common/pg_prng.h"
#include "fmgr.h"
#include "lib/rbtree.h"
#include "utils/memutils.h"

PG_MODULE_MAGIC;


/*
 * Our test trees store an integer key, and nothing else.
 */
typedef struct IntRBTreeNode
{
	RBTNode		rbtnode;
	int			key;
} IntRBTreeNode;


/*
 * Node comparator.  We don't worry about overflow in the subtraction,
 * since none of our test keys are negative.
 */
static int
irbt_cmp(const RBTNode *a, const RBTNode *b, void *arg)
{
	const IntRBTreeNode *ea = (const IntRBTreeNode *) a;
	const IntRBTreeNode *eb = (const IntRBTreeNode *) b;

	return ea->key - eb->key;
}

/*
 * Node combiner.  For testing purposes, just check that library doesn't
 * try to combine unequal keys.
 */
static void
irbt_combine(RBTNode *existing, const RBTNode *newdata, void *arg)
{
	const IntRBTreeNode *eexist = (const IntRBTreeNode *) existing;
	const IntRBTreeNode *enew = (const IntRBTreeNode *) newdata;

	if (eexist->key != enew->key)
		elog(ERROR, "red-black tree combines %d into %d",
			 enew->key, eexist->key);
}

/* Node allocator */
static RBTNode *
irbt_alloc(void *arg)
{
	return (RBTNode *) palloc(sizeof(IntRBTreeNode));
}

/* Node freer */
static void
irbt_free(RBTNode *node, void *arg)
{
	pfree(node);
}

/*
 * Create a red-black tree using our support functions
 */
static RBTree *
create_int_rbtree(void)
{
	return rbt_create(sizeof(IntRBTreeNode),
					  irbt_cmp,
					  irbt_combine,
					  irbt_alloc,
					  irbt_free,
					  NULL);
}

/*
 * Generate a random permutation of the integers 0..size-1
 */
static int *
GetPermutation(int size)
{
	int		   *permutation;
	int			i;

	permutation = (int *) palloc(size * sizeof(int));

	permutation[0] = 0;

	/*
	 * This is the "inside-out" variant of the Fisher-Yates shuffle algorithm.
	 * Notionally, we append each new value to the array and then swap it with
	 * a randomly-chosen array element (possibly including itself, else we
	 * fail to generate permutations with the last integer last).  The swap
	 * step can be optimized by combining it with the insertion.
	 */
	for (i = 1; i < size; i++)
	{
		int			j = pg_prng_uint64_range(&pg_global_prng_state, 0, i);

		if (j < i)				/* avoid fetching undefined data if j=i */
			permutation[i] = permutation[j];
		permutation[j] = i;
	}

	return permutation;
}

/*
 * Populate an empty RBTree with "size" integers having the values
 * 0, step, 2*step, 3*step, ..., inserting them in random order
 */
static void
rbt_populate(RBTree *tree, int size, int step)
{
	int		   *permutation = GetPermutation(size);
	IntRBTreeNode node;
	bool		isNew;
	int			i;

	/* Insert values.  We don't expect any collisions. */
	for (i = 0; i < size; i++)
	{
		node.key = step * permutation[i];
		rbt_insert(tree, (RBTNode *) &node, &isNew);
		if (!isNew)
			elog(ERROR, "unexpected !isNew result from rbt_insert");
	}

	/*
	 * Re-insert the first value to make sure collisions work right.  It's
	 * probably not useful to test that case over again for all the values.
	 */
	if (size > 0)
	{
		node.key = step * permutation[0];
		rbt_insert(tree, (RBTNode *) &node, &isNew);
		if (isNew)
			elog(ERROR, "unexpected isNew result from rbt_insert");
	}

	pfree(permutation);
}

/*
 * Check the correctness of left-right traversal.
 * Left-right traversal is correct if all elements are
 * visited in increasing order.
 */
static void
testleftright(int size)
{
	RBTree	   *tree = create_int_rbtree();
	IntRBTreeNode *node;
	RBTreeIterator iter;
	int			lastKey = -1;
	int			count = 0;

	/* check iteration over empty tree */
	rbt_begin_iterate(tree, LeftRightWalk, &iter);
	if (rbt_iterate(&iter) != NULL)
		elog(ERROR, "left-right walk over empty tree produced an element");

	/* fill tree with consecutive natural numbers */
	rbt_populate(tree, size, 1);

	/* iterate over the tree */
	rbt_begin_iterate(tree, LeftRightWalk, &iter);

	while ((node = (IntRBTreeNode *) rbt_iterate(&iter)) != NULL)
	{
		/* check that order is increasing */
		if (node->key <= lastKey)
			elog(ERROR, "left-right walk gives elements not in sorted order");
		lastKey = node->key;
		count++;
	}

	if (lastKey != size - 1)
		elog(ERROR, "left-right walk did not reach end");
	if (count != size)
		elog(ERROR, "left-right walk missed some elements");
}

/*
 * Check the correctness of right-left traversal.
 * Right-left traversal is correct if all elements are
 * visited in decreasing order.
 */
static void
testrightleft(int size)
{
	RBTree	   *tree = create_int_rbtree();
	IntRBTreeNode *node;
	RBTreeIterator iter;
	int			lastKey = size;
	int			count = 0;

	/* check iteration over empty tree */
	rbt_begin_iterate(tree, RightLeftWalk, &iter);
	if (rbt_iterate(&iter) != NULL)
		elog(ERROR, "right-left walk over empty tree produced an element");

	/* fill tree with consecutive natural numbers */
	rbt_populate(tree, size, 1);

	/* iterate over the tree */
	rbt_begin_iterate(tree, RightLeftWalk, &iter);

	while ((node = (IntRBTreeNode *) rbt_iterate(&iter)) != NULL)
	{
		/* check that order is decreasing */
		if (node->key >= lastKey)
			elog(ERROR, "right-left walk gives elements not in sorted order");
		lastKey = node->key;
		count++;
	}

	if (lastKey != 0)
		elog(ERROR, "right-left walk did not reach end");
	if (count != size)
		elog(ERROR, "right-left walk missed some elements");
}

/*
 * Check the correctness of the rbt_find operation by searching for
 * both elements we inserted and elements we didn't.
 */
static void
testfind(int size)
{
	RBTree	   *tree = create_int_rbtree();
	int			i;

	/* Insert even integers from 0 to 2 * (size-1) */
	rbt_populate(tree, size, 2);

	/* Check that all inserted elements can be found */
	for (i = 0; i < size; i++)
	{
		IntRBTreeNode node;
		IntRBTreeNode *resultNode;

		node.key = 2 * i;
		resultNode = (IntRBTreeNode *) rbt_find(tree, (RBTNode *) &node);
		if (resultNode == NULL)
			elog(ERROR, "inserted element was not found");
		if (node.key != resultNode->key)
			elog(ERROR, "find operation in rbtree gave wrong result");
	}

	/*
	 * Check that not-inserted elements can not be found, being sure to try
	 * values before the first and after the last element.
	 */
	for (i = -1; i <= 2 * size; i += 2)
	{
		IntRBTreeNode node;
		IntRBTreeNode *resultNode;

		node.key = i;
		resultNode = (IntRBTreeNode *) rbt_find(tree, (RBTNode *) &node);
		if (resultNode != NULL)
			elog(ERROR, "not-inserted element was found");
	}
}

/*
 * Check the correctness of the rbt_find_less() and rbt_find_great() functions
 * by searching for an equal key and iterating the lesser keys then the greater
 * keys.
 */
static void
testfindltgt(int size)
{
	RBTree	   *tree = create_int_rbtree();

	/*
	 * Using the size as the random key to search wouldn't allow us to get at
	 * least one greater match, so we do size - 1
	 */
	int			randomKey = pg_prng_uint64_range(&pg_global_prng_state, 0, size - 1);
	bool		keyDeleted;
	IntRBTreeNode searchNode = {.key = randomKey};
	IntRBTreeNode *lteNode;
	IntRBTreeNode *gteNode;
	IntRBTreeNode *node;

	/* Insert natural numbers */
	rbt_populate(tree, size, 1);

	/*
	 * Since the search key is included in the naturals of the tree, we're
	 * sure to find an equal match
	 */
	lteNode = (IntRBTreeNode *) rbt_find_less(tree, (RBTNode *) &searchNode, true);
	gteNode = (IntRBTreeNode *) rbt_find_great(tree, (RBTNode *) &searchNode, true);

	if (lteNode == NULL || lteNode->key != searchNode.key)
		elog(ERROR, "rbt_find_less() didn't find the equal key");

	if (gteNode == NULL || gteNode->key != searchNode.key)
		elog(ERROR, "rbt_find_great() didn't find the equal key");

	if (lteNode != gteNode)
		elog(ERROR, "rbt_find_less() and rbt_find_great() found different equal keys");

	/* Find the rest of the naturals lesser than the search key */
	keyDeleted = false;
	for (; searchNode.key > 0; searchNode.key--)
	{
		/*
		 * Find the next key.  If the current key is deleted, we can pass
		 * equal_match == true and still find the next one.
		 */
		node = (IntRBTreeNode *) rbt_find_less(tree, (RBTNode *) &searchNode,
											   keyDeleted);

		/* ensure we find a lesser match */
		if (!node || !(node->key < searchNode.key))
			elog(ERROR, "rbt_find_less() didn't find a lesser key");

		/* randomly delete the found key or leave it */
		keyDeleted = (pg_prng_uint64_range(&pg_global_prng_state, 0, 1) == 1);
		if (keyDeleted)
			rbt_delete(tree, (RBTNode *) node);
	}

	/* Find the rest of the naturals greater than the search key */
	keyDeleted = false;
	for (searchNode.key = randomKey; searchNode.key < size - 1; searchNode.key++)
	{
		/*
		 * Find the next key.  If the current key is deleted, we can pass
		 * equal_match == true and still find the next one.
		 */
		node = (IntRBTreeNode *) rbt_find_great(tree, (RBTNode *) &searchNode,
												keyDeleted);

		/* ensure we find a greater match */
		if (!node || !(node->key > searchNode.key))
			elog(ERROR, "rbt_find_great() didn't find a greater key");

		/* randomly delete the found key or leave it */
		keyDeleted = (pg_prng_uint64_range(&pg_global_prng_state, 0, 1) == 1);
		if (keyDeleted)
			rbt_delete(tree, (RBTNode *) node);
	}

	/* Check out of bounds searches find nothing */
	searchNode.key = -1;
	node = (IntRBTreeNode *) rbt_find_less(tree, (RBTNode *) &searchNode, true);
	if (node != NULL)
		elog(ERROR, "rbt_find_less() found non-inserted element");
	searchNode.key = 0;
	node = (IntRBTreeNode *) rbt_find_less(tree, (RBTNode *) &searchNode, false);
	if (node != NULL)
		elog(ERROR, "rbt_find_less() found non-inserted element");
	searchNode.key = size;
	node = (IntRBTreeNode *) rbt_find_great(tree, (RBTNode *) &searchNode, true);
	if (node != NULL)
		elog(ERROR, "rbt_find_great() found non-inserted element");
	searchNode.key = size - 1;
	node = (IntRBTreeNode *) rbt_find_great(tree, (RBTNode *) &searchNode, false);
	if (node != NULL)
		elog(ERROR, "rbt_find_great() found non-inserted element");
}

/*
 * Check the correctness of the rbt_leftmost operation.
 * This operation should always return the smallest element of the tree.
 */
static void
testleftmost(int size)
{
	RBTree	   *tree = create_int_rbtree();
	IntRBTreeNode *result;

	/* Check that empty tree has no leftmost element */
	if (rbt_leftmost(tree) != NULL)
		elog(ERROR, "leftmost node of empty tree is not NULL");

	/* fill tree with consecutive natural numbers */
	rbt_populate(tree, size, 1);

	/* Check that leftmost element is the smallest one */
	result = (IntRBTreeNode *) rbt_leftmost(tree);
	if (result == NULL || result->key != 0)
		elog(ERROR, "rbt_leftmost gave wrong result");
}

/*
 * Check the correctness of the rbt_delete operation.
 */
static void
testdelete(int size, int delsize)
{
	RBTree	   *tree = create_int_rbtree();
	int		   *deleteIds;
	bool	   *chosen;
	int			i;

	/* fill tree with consecutive natural numbers */
	rbt_populate(tree, size, 1);

	/* Choose unique ids to delete */
	deleteIds = (int *) palloc(delsize * sizeof(int));
	chosen = (bool *) palloc0(size * sizeof(bool));

	for (i = 0; i < delsize; i++)
	{
		int			k = pg_prng_uint64_range(&pg_global_prng_state, 0, size - 1);

		while (chosen[k])
			k = (k + 1) % size;
		deleteIds[i] = k;
		chosen[k] = true;
	}

	/* Delete elements */
	for (i = 0; i < delsize; i++)
	{
		IntRBTreeNode find;
		IntRBTreeNode *node;

		find.key = deleteIds[i];
		/* Locate the node to be deleted */
		node = (IntRBTreeNode *) rbt_find(tree, (RBTNode *) &find);
		if (node == NULL || node->key != deleteIds[i])
			elog(ERROR, "expected element was not found during deleting");
		/* Delete it */
		rbt_delete(tree, (RBTNode *) node);
	}

	/* Check that deleted elements are deleted */
	for (i = 0; i < size; i++)
	{
		IntRBTreeNode node;
		IntRBTreeNode *result;

		node.key = i;
		result = (IntRBTreeNode *) rbt_find(tree, (RBTNode *) &node);
		if (chosen[i])
		{
			/* Deleted element should be absent */
			if (result != NULL)
				elog(ERROR, "deleted element still present in the rbtree");
		}
		else
		{
			/* Else it should be present */
			if (result == NULL || result->key != i)
				elog(ERROR, "delete operation removed wrong rbtree value");
		}
	}

	/* Delete remaining elements, so as to exercise reducing tree to empty */
	for (i = 0; i < size; i++)
	{
		IntRBTreeNode find;
		IntRBTreeNode *node;

		if (chosen[i])
			continue;
		find.key = i;
		/* Locate the node to be deleted */
		node = (IntRBTreeNode *) rbt_find(tree, (RBTNode *) &find);
		if (node == NULL || node->key != i)
			elog(ERROR, "expected element was not found during deleting");
		/* Delete it */
		rbt_delete(tree, (RBTNode *) node);
	}

	/* Tree should now be empty */
	if (rbt_leftmost(tree) != NULL)
		elog(ERROR, "deleting all elements failed");

	pfree(deleteIds);
	pfree(chosen);
}

/*
 * SQL-callable entry point to perform all tests
 *
 * Argument is the number of entries to put in the trees
 */
PG_FUNCTION_INFO_V1(test_rb_tree);

Datum
test_rb_tree(PG_FUNCTION_ARGS)
{
	int			size = PG_GETARG_INT32(0);

	if (size <= 0 || size > MaxAllocSize / sizeof(int))
		elog(ERROR, "invalid size for test_rb_tree: %d", size);
	testleftright(size);
	testrightleft(size);
	testfind(size);
	testfindltgt(size);
	testleftmost(size);
	testdelete(size, Max(size / 10, 1));
	PG_RETURN_VOID();
}
