// ::DataStructures::Globals::Start::

RBNode RB_NIL = { .color = RB_BLACK };
RBNode *P_RB_NIL = &RB_NIL;

RBDataNode RB_DN_NIL = {0};
RBDataNode *P_RB_DN_NIL = &RB_DN_NIL;

HashNode HT_NIL = {0};
HashNode *P_HT_NIL = &HT_NIL;

// ::DataStructures::Globals::End::



// ::DataStructures::RedBlackTree::Functions::Start::

static void RBTreeInit(RBTree *tree)
{
	Assert(tree != NULL, "RBTree is null");
	RB_NIL.right = RB_NIL.left = RB_NIL.parent = P_RB_NIL;
	RB_DN_NIL.next = P_RB_DN_NIL;

	tree->root = P_RB_NIL;
	tree->nil = P_RB_NIL;
}

static inline void RBTreePushDataNode(RBDataNode *first, RBDataNode *last, rawptr value)
{
	RBDataNode *data_node = FLMemAllocZeroed(sizeof(RBDataNode));
	data_node->data = value;
	RBQueuePush(first, last, data_node);
}

static inline RBNode *RBTreeInitNode(u64 key, rawptr value)
{
	RBNode *node = FLMemAllocZeroed(sizeof(RBNode));
	node->parent = node->left = node->right = P_RB_NIL;
	node->color = RB_BLACK;
	node->key = key;

	RBTreePushDataNode(node->bucket.first, node->bucket.last, value);

	return node;
}

static void RBTreeInsert(RBTree *tree, u64 key, rawptr value)
{
	RBNode *node = P_RB_NIL;

	node->left = node->right = tree->nil;
	node->color = RB_RED;

	if (tree->root == tree->nil)
	{
		node->color = RB_BLACK;
		node->parent = tree->nil;
		tree->root = node;
	}
	else 
	{
		RBNode *curr_node = tree->root;
		while (true)
		{
			Assert(curr_node != tree->nil, "Current Node is NIL");

			if (curr_node->key == key)
			{
				RBTreePushDataNode(curr_node->bucket.first, curr_node->bucket.last, value);
				break;
			}
			else if (curr_node->key < key)
			{
				if (curr_node->right == tree->nil)
				{
					node = RBTreeInitNode(key, value);
					node->parent = curr_node;
					curr_node->right = node;
					break;
				}
				else
				{
					curr_node = curr_node->right;
				}
			}
			else
			{
				if (curr_node->left == tree->nil)
				{
					node = RBTreeInitNode(key, value);
					node->parent = curr_node;
					curr_node->left = node;
					break;
				}
				else
				{
					curr_node = curr_node->left;
				}
			}
		}
	}

	if (node->parent->color != RB_BLACK && node != P_RB_NIL)
		RBTreeCorrect(tree, node);
}

static void RBTreeCorrect(RBTree *tree, RBNode *node)
{
	RBNode *gp = node->parent->parent;
	RBNode *p = node->parent;
	do
	{
		if (node == tree->root)
		{
			node->color = RB_BLACK;
			break;
		}

		if (gp == tree->nil)
		{
			p->color = RB_BLACK;
			break;
		}

		RBNodeDir dir = NodeDir(p);
		RBNode *unc = gp->child[1 - dir];
		if (unc == tree->nil || unc->color == RB_BLACK)
		{
			if (node == p->child[1 - dir])
			{
				RBTreeRotate(tree, p, dir);
				node = p;
				p = gp->child[dir];
			}

			RBTreeRotate(tree, gp, 1 - dir);
			p->color = RB_BLACK;
			gp->color = RB_RED;
			break;
		}

		p->color = RB_BLACK;
		unc->color = RB_BLACK;
		gp->color = RB_RED;
		node = gp;
		gp = node->parent->parent;
	} while ((p = node->parent));
}

static void RBTreeDelete(RBTree *tree, u64 key, rawptr value)
{
	RBNode *node = NULL;
	Assert(RBTreeSearch(tree, key, &node), "Unable to find node in RBTreeDelete");

	if (node->bucket.first->data != value)
	{
		Assert(node->bucket.first->next != P_RB_DN_NIL, "RBTreeDelete Failure: unable to find value to delete");
		RBDataNode *data_node = node->bucket.first->next;
		RBDataNode *prev_node = node->bucket.first;

		while (data_node != P_RB_DN_NIL)
		{
			if (data_node->data == value)
			{
				prev_node->next = data_node->next;
				if (data_node == node->bucket.last)
					node->bucket.last = prev_node;	

				FLMemFree(data_node);
			}
		}
	}
	else
	{
		FLMemFree(node->bucket.first);

		if (node == tree->root && node->left == tree->nil && node->right == tree->nil)
		{
			tree->root = tree->nil;
		}
		else if (node->left != tree->nil && node->right != tree->nil)
		{
			RBNode *ln = node->right;

			while (ln->left != tree->nil)
				ln = ln->left;

			node->key = ln->key;
			node->bucket = ln->bucket;

			if (node->right == ln)
				node->right = tree->nil;
			else
				ln->parent->left = tree->nil;

			ln->parent = tree->nil;
		}
		else if (node->color == RB_BLACK && node->left != tree->nil)
		{
			node->key = node->left->key;
			node->bucket = node->left->bucket;
			node->left = tree->nil;
		}
		else if (node->color == RB_BLACK && node->right != tree->nil)
		{
			node->key = node->right->key;
			node->bucket = node->right->bucket;
			node->right = tree->nil;
		}
		else if (node->color == RB_RED && node->right == tree->nil && node->left == tree->nil)
		{
			RBNodeDir dir = NodeDir(node);
			node->parent->child[dir] = tree->nil;
		}
		else 
		{
			RBNode *p = node->parent;
			RBNodeColor col = node->color;
			RBNode *s, *cn, *dn;

			RBNodeDir dir = NodeDir(node);
			p->child[dir] = tree->nil;
			
			goto start_deletion;

			do 
			{
				dir = NodeDir(node);
			start_deletion:
				s = p->child[1 - dir];
				dn = s->child[1 - dir];
				cn = s->child[dir];

				if (s->color == RB_RED)
				{
					RBTreeRotate(tree, p, dir);
					p->color = RB_RED;
					s->color = RB_BLACK;
					s = cn;

					dn = s->child[1 - dir];
					if (dn->color == RB_RED)
						goto rotate_sibling;
					cn = s->child[dir];
					if (cn->color == RB_RED)
						goto rotate_parent;

					s->color = RB_RED;
					p->color = RB_BLACK;
					return;
				}
				
				if (dn->color == RB_RED)
					goto rotate_parent;

				if (cn->color == RB_RED)
					goto rotate_sibling;

				if (p->color == RB_RED)
				{
					s->color = RB_RED;
					p->color = RB_BLACK;
					return;
				}

				if (p == tree->nil)
					return;

				s->color = RB_RED;
				node = p;
			} while ((p = node->parent));

		rotate_sibling:
			RBTreeRotate(tree, s, 1 - dir);
			s->color = RB_RED;
			cn->color = RB_BLACK;
			dn = s;
			s = cn;

		rotate_parent:
			RBTreeRotate(tree, p, dir);
			s->color = p->color;
			p->color = RB_BLACK;
			dn->color = RB_BLACK;
		}
	}
}

static b32 RBTreeSearchNearest(RBTree *tree, u64 key, RBNode **out_node)
{
	if (tree->root == tree->nil) return false;

	RBNode *node = tree->root;
	RBNode *nearest = tree->root;
	u64 nearest_diff = UINT64_MAX;

	while (true)
	{
		if (node == tree->nil)
			break;

		u64 diff = node->key - key;
		diff = Absu64(diff);

		if (diff == 0)
		{
			nearest = node;
			break;
		}

		if (diff < nearest_diff)
		{
			nearest_diff = diff;
			nearest = node;
		}

		if (node->key < key)
			node = node->right;
		else 
			node = node->left;
	}

	*out_node = nearest != tree->nil ? nearest : tree->nil;

	return *out_node != tree->nil;
}

static b32 RBTreeSearch(RBTree *tree, u64 key, RBNode **out_node)
{
	if (tree->root == tree->nil) return false;

	b32 found = false;
	RBNode *node = tree->root;

	while (true)
	{
		if (node->key == key)
		{
			found = true;
			break;
		}

		if (node == tree->nil)
			break;
		
		if (node->key < key)
			node = node->right;
		else
			node = node->left;
	}

	if (found)
		*out_node = node;

	return found;
}

static inline void RBTreeTransplant(RBTree *tree, RBNode *node, RBNode *placed_node)
{
	if (node->parent == tree->nil)
		tree->root = placed_node;
	else if (node == node->parent->left)
		node->parent->left = placed_node;
	else
		node->parent->right = placed_node;

	placed_node->parent = node->parent;
}

static void RBTreeRotate(RBTree *tree, RBNode *node, RBNodeDir dir)
{
	RBNode *p = node->parent;
	RBNode *root = node->child[1 - dir];
	RBNode *child = root->child[dir];

	node->child[1 - dir] = child;

	if (child != tree->nil)
		child->parent = node;

	root->child[dir] = node;
	root->parent = p;
	node->parent = root;

	if (p != tree->nil)
		p->child[node == p->right] = root;
	else
		tree->root = root;
}

static void RBTreeLeftRotate(RBTree *tree, RBNode *node)
{
	RBNode *right = node->right;
	if (right->left != tree->nil)
		node->right = right->left;

	if (node->parent == tree->nil)
		tree->root = right;
	else if (node->parent->left == node)
		node->parent->left = right;
	else
		node->parent->right = right;

	right->parent = node->parent;
	right->left = node;
	node->parent = right;
}

static void RBTreeRightRotate(RBTree *tree, RBNode *node)
{
	RBNode *left = node->left;
	if (left->right != tree->nil)
		node->left = left->right;

	if (node->parent == tree->nil)
		tree->root = left;
	else if (node->parent->left == node)
		node->parent->left = left;
	else
		node->parent->right = left;

	left->parent = node->parent;
	left->right = node;
	node->parent = left;
}

// ::DataStructures::RedBlackTree::Functions::End::



// ::DataStructures::HashTable::Functions::Start::

static void HashTableInit(HashTable *table, u32 init_size)
{
	table->cap = init_size;
	table->count = 0;
	table->free_lists.first = P_HT_NIL;
	table->free_lists.last = P_HT_NIL;

	table->lists = FLMemAlloc(sizeof(HashList) * init_size);
	for (u32 i = 0; i < init_size; i++)
	{
		table->lists[i].first = P_HT_NIL;
		table->lists[i].last = P_HT_NIL;
	}
}

static void HashTableConcatInPlace(HashList *list, HashList *to_concat)
{
	SLLConcatInPlaceNoCount(list, to_concat);
}

static void HashTableClear(HashTable *table)
{
	table->count = 0;
	for (u32 i = 0; i < table->count; i++)
	{
		HashTableConcatInPlace(&table->free_lists, &table->lists[i]);
	}
}

static HashNode *HashListPop(HashList *list)
{
	HashNode *result = list->first;
	HTQueuePop(list->first, list->last);
	return result;
}

static HashNode *HashTablePush(HashTable *table, u64 hash, KeyValuePair value)
{
	HashNode *node = NULL;
	if (table->free_lists.first != P_HT_NIL)
		node = HashListPop(&table->free_lists);
	else
		node = FLMemAlloc(sizeof(HashNode));

	node->next = P_HT_NIL;
	node->v = value;

	u64 index = hash % table->cap;
	HTQueuePush(table->lists[index].first, table->lists[index].last, node);
	table->count += 1;	

	return node;
}

static HashNode *HashTablePushU64U32(HashTable *table, u64 key, u32 value)
{
	u64 hash = HashFromString(String8Struct(&key));
	return HashTablePush(table, hash, (KeyValuePair){ .key_u64 = key, .value_u32 = value });
}

static HashNode *HashTablePushU64U64(HashTable *table, u64 key, u64 value)
{
	u64 hash = HashFromString(String8Struct(&key));
	return HashTablePush(table, hash, (KeyValuePair){ .key_u64 = key, .value_u64 = value });
}

static HashNode *HashTablePushU64String8(HashTable *table, u64 key, String8 value)
{
	u64 hash = HashFromString(String8Struct(&key));
	return HashTablePush(table, hash, (KeyValuePair){ .key_u64 = key, .value_string = value });
}

static HashNode *HashTablePushU64Rawptr(HashTable *table, u64 key, rawptr value)
{
	u64 hash = HashFromString(String8Struct(&key));
	return HashTablePush(table, hash, (KeyValuePair){ .key_u64 = key, .value_rawptr = value });
}

static HashNode *HashTablePushU64U64Split(HashTable *table, u64 key, u32 upper, u32 lower)
{
	u64 hash = HashFromString(String8Struct(&key));
	return HashTablePush(table, hash, (KeyValuePair){ .key_u64 = key, .value_u64_split = { .upper = upper, .lower = lower }});
}

static HashNode *HashTablePushRawptrU64(HashTable *table, rawptr key, u64 value)
{
	u64 hash = HashFromString(String8Struct(&key));
	return HashTablePush(table, hash, (KeyValuePair){ .key_rawptr = key, .value_u64 = value });
}

static KeyValuePair *HashTableSearchU64(HashTable *table, u64 key)
{
	KeyValuePair *result = NULL;

	u64 hash = HashFromString(String8Struct(&key));
	u64 index = hash % table->cap;
	HashList *list = table->lists + index;
	for (HashNode *node = list->first; node != P_HT_NIL; node = node->next)
	{
		if (node->v.key_u64 == key)
		{
			result = &node->v;
			break;
		}
	}

	return result;
}

static KeyValuePair *HashTableSearchRawptr(HashTable *table, rawptr key)
{
	KeyValuePair *result = NULL;

	u64 hash = HashFromString(String8Struct(&key));
	u64 index = hash % table->cap;
	HashList *list = table->lists + index;
	for (HashNode *node = list->first; node != P_HT_NIL; node = node->next)
	{
		if (node->v.key_rawptr == key)
		{
			result = &node->v;
			break;
		}
	}

	return result;
}

static void HashTableDeleteU64(HashTable *table, u64 key)
{
	u64 hash = HashFromString(String8Struct(&key));
	u64 index = hash % table->cap;
	HashList *list = table->lists + index;
	HashNode *prev = P_HT_NIL;
	for (HashNode *node = list->first; node != P_HT_NIL; node = node->next)
	{
		if (node->v.key_u64 == key)
		{
			if (prev != P_HT_NIL)
				prev->next = node->next;

			node->v.key_u64 = 0;
			node->v.value_u64 = 0;
			HTQueuePush(table->free_lists.first, table->free_lists.last, node);
			break;
		}
	}
}

static rawptr HashTableDeleteU64Rawptr(HashTable *table, u64 key)
{
	rawptr value = NULL;

	u64 hash = HashFromString(String8Struct(&key));
	u64 index = hash % table->cap;
	HashList *list = table->lists + index;
	HashNode *prev = P_HT_NIL;
	for (HashNode *node = list->first; node != P_HT_NIL; node = node->next)
	{
		if (node->v.key_u64 == key)
		{
			if (prev != P_HT_NIL)
				prev->next = node->next;
			
			value = node->v.value_rawptr;

			node->v.key_u64 = 0;
			node->v.value_rawptr = NULL;
			HTQueuePush(table->free_lists.first, table->free_lists.last, node);
			break;
		}
	}

	return value;
}

static U64Split HashTableDeleteU64U64Split(HashTable *table, u64 key)
{
	U64Split value = { .upper = UINT32_MAX };

	u64 hash = HashFromString(String8Struct(&key));
	u64 index = hash % table->cap;
	HashList *list = table->lists + index;
	HashNode *prev = P_HT_NIL;
	for (HashNode *node = list->first; node != P_HT_NIL; node = node->next)
	{
		if (node->v.key_u64 == key)
		{
			if (prev != P_HT_NIL)
				prev->next = node->next;
			
			value.upper = node->v.value_u64_split.upper;
			value.lower = node->v.value_u64_split.lower;

			node->v.key_u64 = 0;
			node->v.value_u64_split.upper = 0;
			node->v.value_u64_split.lower = 0;
			HTQueuePush(table->free_lists.first, table->free_lists.last, node);
			break;
		}
	}

	return value;
}

// ::DataStructures::HashTable::Functions::End::


