//-----------------------------------------------------------------------------
// Copyright © 2003 - Philip Howard - All rights reserved
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//-----------------------------------------------------------------------------
// package	libh/avl
// homepage	http://libh.slashusr.org/
//-----------------------------------------------------------------------------
// author	Philip Howard
// email	libh at ipal dot org
// homepage	http://phil.ipal.org/
//-----------------------------------------------------------------------------
// This file is best viewed using a fixed spaced font such as Courier
// and in a display at least 120 columns wide.
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// G. M. Adelson-Velskii and E. M. Landis are the inventors of the algorithm
// bearing their initials, AVL.  Their algorithm for height-balanced binary
// search trees is the basis of the code in this package.  Some modification
// is made to the balancing operations, and features are added for practical
// purposes.
//-----------------------------------------------------------------------------

#include "avl_lib.h"

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	avl_put
//
// purpose	Put a node into the given binary tree at the location
//		indicated by the key link within the node.
//
// arguments	1 (AVL *) pointer to binary tree
//		2 (void *) pointer to node
//		3 (void * *) where to store pointer to replaced node
//		4 (int) option: allow duplicate insert
//
// note		If argument 3 is non-NULL, then replacement is allowed.
//		If argument 3 is NULL, then replacement is not allowed.
//
// returns	(int) -1: node not replaced or inserted
//		(int)  0: node replaces an existing node
//		(int)  1: node inserted as new
//		(int)  2: node inserted as duplicate
//
// note		If the allow duplicate insert option is true, then the node
//		is inserted regardless of whether one already exists with
//		that key or not.  The order duplicate nodes are retrieved,
//		and which node will match a find, is not defined.
//-----------------------------------------------------------------------------
// macro	avl_insert
//
// purpose	Call avl_put to do an insert only, without duplicate.
//-----------------------------------------------------------------------------
// macro	avl_insert_dup
//
// purpose	Call avl_put to do an insert with duplication allowed.
//-----------------------------------------------------------------------------
// macro	avl_replace
//
// purpose	Call avl_put to do an insert or replacement.
//-----------------------------------------------------------------------------
int
avl_put (
    AVL *	arg_tree
    ,
    void *	arg_node
    ,
    void * *	arg_rep_node
    ,
    int		arg_dup_ok
    )
__PROTO_END__
{
    avl_link *		new_p		;
    avl_link *		link_p		;

    int			match		;
    int			action		;


#if AVL_CHECK_ARGS
    //-- Make sure we were given a binary tree.
    if ( ! arg_tree ) return -1;

    //-- Make sure we were given a node.
    if ( ! arg_node ) return -1;
#endif

    //-- Point to the key link in the node.
    new_p = avl_node_to_link( arg_tree, arg_node );

    //----------------------------------------------------------------------
    // Search for where to put this new node.
    //
    // If the tree is empty, it simply becomes the apex.
    //
    // If it exactly matches an existing node and replacement is allowed,
    // then that node can be replaced where it is with no tree shape change.
    //
    // Otherwise, a hole must must be found, a nil link, thus adding the new
    // node as a leaf.  Then the tree is balanced to integrate it, which may
    // move the new node to a non-leaf position.
    //----------------------------------------------------------------------
    action = 1;
    if ( ! arg_tree->link.hi ) {
	//-- Add first node to tree.
	link_p = & ( arg_tree->link );
	link_p->hi = new_p;
    } else {
	link_p = arg_tree->link.hi;
	for (;;) {
	    match = avl_compare_keys( new_p, link_p, arg_tree );
	    if ( match == 0 ) {
		//-- Match found, check if duplicate or replace.
		if ( arg_dup_ok ) {
		    action = 2;
		    //-- Always go hi to add a duplicate.
		    if ( ! link_p->hi ) {
			link_p->hi = new_p;
			break;
		    }
		    //-- No hi opening here, continue that way.
		    link_p = link_p->hi;
		} else {
		    //-- No duplicating, must replace or return.
		    if ( arg_rep_node ) goto do_replace;
		    return -1;
		}
	    } else if ( match < 0 ) {
		//-- Check only the lo branch for an opening.
		if ( ! link_p->lo ) {
		    //-- Link the open lo branch to the new node.
		    link_p->lo = new_p;
		    break;
		}
		link_p = link_p->lo;
	    } else {
		//-- Check only the hi branch for an opening.
		if ( ! link_p->hi ) {
		    //-- Link the open hi branch to the new node.
		    link_p->hi = new_p;
		    break;
		}
		link_p = link_p->hi;
	    }
	}
    }

    //-- Finish linking new node back to insert point.
    new_p->pa    = link_p;

    //-- Initialize the new node as a leaf.
    new_p->lo    = NULL;
    new_p->hi    = NULL;
    new_p->depth = 1;

    //-- Balance from the parent of the new node.
    avl_balance( arg_tree, link_p );

    //-- Count the new node.
    avl_count_inc_nodes( arg_tree );

    //-- Remember the node as currently selected.
    arg_tree->select = new_p;

    // Return new or dup status.
    return action;

//-----------------------------------------------------------------------------

do_replace:
    //----------------------------------------------------------
    // Replace this old node with the new one right where it is.
    // Balancing will not be needed as the tree keeps its shape.
    //----------------------------------------------------------
    {	avl_link * pa_p;

	//-- Copy pointer to parent link.
	new_p->pa = pa_p = link_p->pa;

	//-- Copy the depth which stays the same.
	new_p->depth = link_p->depth;

	//-- Update parent to point back to new node.
	if ( pa_p->lo == link_p ) pa_p->lo = new_p;
#if AVL_PARANOID_LOGIC
	else if ( pa_p->hi != link_p ) {
	    return avl_abort( arg_tree, link_p, "avl_put: orphan node being replaced\n" );
	}
#endif
	else pa_p->hi = new_p;
    }

    //-- Copy child links and make them point back if they exist.
    {	avl_link * ch_p;
	if ( ( ch_p = new_p->lo = link_p->lo ) ) ch_p->pa = new_p;
	if ( ( ch_p = new_p->hi = link_p->hi ) ) ch_p->pa = new_p;
    }

#if AVL_CLEAN_NODE
    //-- Clean up the old node to detect misuse.
    link_p->lo		= NULL;
    link_p->hi		= NULL;
    link_p->pa		= NULL;
    link_p->depth	= 0;
#endif /* AVL_CLEAN_NODE */

    //-- Store the old node back for caller.
    * arg_rep_node = avl_link_to_node( arg_tree, link_p );

    //-- Remember the node as currently selected.
    arg_tree->select = new_p;

    //-- Return indicating replace.
    return 1;
}

