/*
 * Copyright (c) 2002, 2004, 2005 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 *
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: tree2.c,v 1.10 2005/07/12 23:23:38 ca Exp $")

#include "sm/error.h"
#include "sm/assert.h"
#include "sm/tree.h"

/*
**  XXX need to assign proper error module, look for sm_error_gen()
*/

/* local functions */
static void	 sm_tree_rotate_left(sm_tree_node_P *);
static void	 sm_tree_rotate_right(sm_tree_node_P *);

/*
**  SM_TREE_ROTATE_LEFT -- rotate tree left
**
**	Parameters:
**		n -- node to rotate
**
**	Returns:
**		nothing.
*/

static void
sm_tree_rotate_left(sm_tree_node_P *n)
{
	sm_tree_node_P q, p;

	SM_REQUIRE(n != NULL);
	SM_REQUIRE(*n != NULL);
#if SM_TREE_STAT
	++sm_tree_rotates;
#endif /* SM_TREE_STAT */
	q = *n;
	p = (*n)->sm_tree_parent;
	(*n)->sm_tree_parent = (*n)->sm_tree_right;
	*n = (*n)->sm_tree_right;
	(*n)->sm_tree_parent = p;
	q->sm_tree_right = (*n)->sm_tree_left;
	if (q->sm_tree_right != NULL)
		q->sm_tree_right->sm_tree_parent = q;
	(*n)->sm_tree_left = q;
	q->sm_tree_balance--;
	if ((*n)->sm_tree_balance > 0)
		q->sm_tree_balance -= (*n)->sm_tree_balance;
	(*n)->sm_tree_balance--;
	if (q->sm_tree_balance < 0)
		(*n)->sm_tree_balance += q->sm_tree_balance;
}

/*
**  SM_TREE_ROTATE_RIGHT -- rotate tree right
**
**	Parameters:
**		n -- node to rotate
**
**	Returns:
**		nothing.
*/

static void
sm_tree_rotate_right(sm_tree_node_P *n)
{
	sm_tree_node_P q, p;

	SM_REQUIRE(n != NULL);
	SM_REQUIRE(*n != NULL);
#if SM_TREE_STAT
	++sm_tree_rotates;
#endif /* SM_TREE_STAT */
	q = *n;
	p = (*n)->sm_tree_parent;
	(*n)->sm_tree_parent = (*n)->sm_tree_left;
	*n = (*n)->sm_tree_left;
	(*n)->sm_tree_parent = p;
	q->sm_tree_left = (*n)->sm_tree_right;
	if (q->sm_tree_left != NULL)
		q->sm_tree_left->sm_tree_parent = q;
	(*n)->sm_tree_right = q;
	q->sm_tree_balance++;
	if ((*n)->sm_tree_balance < 0)
		q->sm_tree_balance -= (*n)->sm_tree_balance;
	(*n)->sm_tree_balance++;
	if (q->sm_tree_balance > 0)
		(*n)->sm_tree_balance += q->sm_tree_balance;
}

/*
**  SM_TREE_INSERT2 -- insert a new node into a tree or overwrite the
**	data in an existing node
**
**	Parameters:
**		tree -- tree
**		n -- initial node
**		p -- should be NULL initially (used for recursion)
**		key -- key for insertion
**		len -- len of key
**		data -- data to insert
**
**	Returns:
**		>=0 on success (difference in height)
**		< 0 error code
*/

int
sm_tree_insert2(sm_tree_P tree, sm_tree_node_P *n, sm_tree_node_P p, sm_tree_key_T key, uint len, sm_tree_data_T data)
{
	int done;
	int cmp;

	SM_REQUIRE(n != NULL);
	done = 0;
	if (*n == NULL)
	{
		sm_ret_T ret;

		ret = sm_tree_node_alloc(tree, n, key, len, data);
		if (sm_is_err(ret))
			return ret;
		(*n)->sm_tree_parent = p;
		done = 1;
	}
	else
	{
		cmp = tree->sm_tree_compare(key, (*n)->sm_tree_key, len);
		if (cmp > 0)
		{
			if (sm_tree_insert2(tree, &(*n)->sm_tree_right, *n,
					key, len, data) > 0)
			{
				(*n)->sm_tree_balance++;
				if ((*n)->sm_tree_balance == 1)
					done = 1;
				else if ((*n)->sm_tree_balance == 2)
				{
					if ((*n)->sm_tree_right->sm_tree_balance
					    == -1)
						sm_tree_rotate_right(&(*n)->sm_tree_right);
					sm_tree_rotate_left(n);
				}
			}
		}
		else if (cmp < 0)
		{
			if (sm_tree_insert2(tree, &(*n)->sm_tree_left, *n,
					key, len, data) > 0)
			{
				(*n)->sm_tree_balance--;
				if ((*n)->sm_tree_balance == -1)
					done = 1;
				else if ((*n)->sm_tree_balance == -2)
				{
					if ((*n)->sm_tree_left->sm_tree_balance
					    == 1)
						sm_tree_rotate_left(&(*n)->sm_tree_left);
					sm_tree_rotate_right(n);
				}
			}
		}
		else
		{
			/* found key: overwrite data */
			sm_tree_assign_data((*n)->sm_tree_data, data);
		}
	}
	return done;
}

/*
**  SM_TREE_REMOVE2 -- remove a node from a tree
**
**	Parameters:
**		tree -- tree
**		n -- initial node
**		p -- should be NULL initially (used for recursion)
**		key -- key of node to remove
**		len -- len of key
**
**	Returns:
**		>=0: node removed
**			0: no rebalancing necessary
**			1: rebalancing might be necessary
**		-1: node not found
*/

int
sm_tree_remove2(sm_tree_P tree, sm_tree_node_P *n, sm_tree_node_P p, sm_tree_key_T key, uint len)
{
	int done;
	int c;

	SM_REQUIRE(n != NULL);
	if (*n == NULL)
		return -1;
	done = 0;
	c = tree->sm_tree_compare(key, (*n)->sm_tree_key, len);
	if (c < 0)
	{
		if (sm_tree_remove2(tree, &(*n)->sm_tree_left, *n, key, len)
			> 0)
		{
			(*n)->sm_tree_balance++;
			if ((*n)->sm_tree_balance == 0)
				done = 1;
			else if ((*n)->sm_tree_balance == 2)
			{
				if ((*n)->sm_tree_right->sm_tree_balance == -1)
					sm_tree_rotate_right(&(*n)->sm_tree_right);
				sm_tree_rotate_left(n);
				if ((*n)->sm_tree_balance == 0)
					done = 1;
			}
		}
	}
	else if (c > 0)
	{
		if (sm_tree_remove2(tree, &(*n)->sm_tree_right, *n, key, len)
			> 0)
		{
			(*n)->sm_tree_balance--;
			if ((*n)->sm_tree_balance == 0)
				done = 1;
			else if ((*n)->sm_tree_balance == -2)
			{
				if ((*n)->sm_tree_left->sm_tree_balance == 1)
					sm_tree_rotate_left(&(*n)->sm_tree_left);
				sm_tree_rotate_right(n);
				if ((*n)->sm_tree_balance == 0)
					done = 1;
			}
		}
	}
	else
	{
		/* found, now remove it properly */
		if ((*n)->sm_tree_right == NULL)
		{
			sm_tree_node_P n0;

			n0 = *n;
			*n = (*n)->sm_tree_left;
			if (*n != NULL)
				(*n)->sm_tree_parent = p;
			sm_tree_node_free(tree, n0);
			done = 1;
			SM_ASSERT(tree->sm_tree_used > 0);
			--tree->sm_tree_used;
		}
		else if ((*n)->sm_tree_left == NULL)
		{
			sm_tree_node_P n0;

			n0 = *n;
			*n = (*n)->sm_tree_right;
			if (*n != NULL)
				(*n)->sm_tree_parent = p;
			sm_tree_node_free(tree, n0);
			done = 1;
			SM_ASSERT(tree->sm_tree_used > 0);
			--tree->sm_tree_used;
		}
		else
		{
			sm_tree_node_P *qq;

			qq = &(*n)->sm_tree_left;
			while ((*qq)->sm_tree_right != NULL)
				qq = &(*qq)->sm_tree_right;
			sm_tree_assign_key((*n)->sm_tree_key,
					(*qq)->sm_tree_key);
			sm_tree_assign_key((*qq)->sm_tree_key, key);
			sm_tree_assign_data((*n)->sm_tree_data,
					(*qq)->sm_tree_data);
			if (sm_tree_remove2(tree, &(*n)->sm_tree_left, *n, key,
				len) > 0)
			{
				(*n)->sm_tree_balance++;
				if ((*n)->sm_tree_balance == 0)
					done = 1;
				else if ((*n)->sm_tree_balance == 2)
				{
					if ((*n)->sm_tree_right->sm_tree_balance == -1)
						sm_tree_rotate_right(&(*n)->sm_tree_right);
					sm_tree_rotate_left(n);
					if ((*n)->sm_tree_balance == 0)
						done = 1;
				}
			}
		}
	}
	return done;
}


syntax highlighted by Code2HTML, v. 0.9.1