
/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples. 
*       Copyright (C) 1993 Microsoft Corporation.
*       All rights reserved. 
*       This source code is only intended as a supplement to 
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the 
*       Microsoft samples programs.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: TREE.C
*
* Functions supporting an unbalanced binary tree.
*
* Functions:
*
* tree_delitem()
* tree_newitem()
* tree_getitem()
* tree_create()
* tree_delete()
* tree_update()
* tree_find()
* tree_search()
* tree_addafter()
* ctree_create()
* ctree_delete()
* ctree_update()
* ctree_getcount()
* ctree_find()
*
* Comments:
*
* TREE is a data type providing a map between a KEY and a VALUE. The KEY is a
* 32-bit DWORD, and the VALUE is any arbitrary area of storage.
*
* Mmemory is allocated from gmem_get, using hHeap as the heap handle.
* hHeap must be declared and initialised elsewhere.
*
* Currently implemented as a unbalanced binary tree.
*
****************************************************************************/

#include <windows.h>
#include <stdlib.h>
#include <memory.h>

#include "gutils.h"
#include "tree.h"


/* -- data types ----------------------------------------------- */

/* on creating a tree, we return a TREE handle. This is in fact a pointer
 * to a struct tree, defined here.
 */
struct tree {
        HANDLE hHeap;
        TREEITEM first;
};

/* each element in the tree is stored in a TREEITEM. a TREEITEM handle
 * is a pointer to a struct treeitem, defined here
 */
struct treeitem {
        TREE root;

        TREEKEY key;

        TREEITEM left, right;

        UINT length;            /* length of the user's data */

        LPVOID data;            /* pointer to our copy of the users data */

};

/***************************************************************************
 * Function: tree_delitems
 *
 * Purpose:
 *
 * Free up an element of the tree. Recursively calls itself to
 * free left and right children
 */
void
tree_delitem(TREEITEM item)
{
        if (item == NULL) {
                return;
        }
        if (item->left != NULL) {
                tree_delitem(item->left);
        }
        if (item->right != NULL) {
                tree_delitem(item->right);
        }
        if (item->data != NULL) {
                gmem_free(item->root->hHeap, item->data, item->length);
        }

        gmem_free(item->root->hHeap, (LPSTR) item, sizeof(struct treeitem));
}

/***************************************************************************
 * Function: tree_newitem
 *
 * Purpose:
 *
 * Creates a new treeitem, with a data block of length bytes.
 * If the value pointer is not NULL, initialise the data block with
 * the contents of value.
 */
TREEITEM
tree_newitem(TREE root, TREEKEY key, LPVOID value, UINT length)
{
        TREEITEM item;

        item = (TREEITEM) gmem_get(root->hHeap, sizeof(struct treeitem));

        item->root = root;
        item->key = key;
        item->left = NULL;
        item->right = NULL;
        item->length = length;
        item->data = gmem_get(root->hHeap, length);
        if (value != NULL) {
                memcpy(item->data, value, length);
        }

        return(item);
}


/***************************************************************************
 * Function: tree_getitem
 *
 * Purpose:
 *
 * Finds the item with the given key. If it does not exist, return
 * the parent item to which it would be attached. Returns NULL if
 * no items in the tree
 */
TREEITEM
tree_getitem(TREE tree, TREEKEY key)
{
        TREEITEM item, prev;


        prev = NULL;
        for (item = tree->first; item != NULL; ) {
                
                if (item->key == key) {
                        return(item);
                }

                /* not this item - go on to the correct child item.
                 * remember this item as if the child is NULL, this item
                 * will be the correct insertion point for the new item
                 */
                prev = item;

                if (key < item->key) {
                        item = item->left;
                } else {
                        item = item->right;
                }
        }       
        /* prev is the parent - or null if nothing in tree */
        return(prev);
}

/***************************************************************************
 * Function: tree_create
 *
 * Purpose:
 *
 * Creates an empty tree. hHeap is the handle to use for all
 * memory allocations for this tree.
 */
TREE APIENTRY
tree_create(HANDLE hHeap)
{
        TREE tree;

        tree = (TREE) gmem_get(hHeap, sizeof(struct tree));
        tree->first = NULL;
        tree->hHeap = hHeap;
        return(tree);
}


/***************************************************************************
 * Function: tree_delete
 *
 * Purpose:
 *
 * Deletes an entire tree, including all the user data
 */
void APIENTRY
tree_delete(TREE tree)
{

        tree_delitem(tree->first);

        gmem_free(tree->hHeap, (LPSTR) tree, sizeof(struct tree));
}

/***************************************************************************
 * Function: tree_update
 *
 * Purpose:
 *
 * Adds a new element to the tree, mapping the key given to the value given.
 * The value is a block of storage: a copy of this is inserted into the tree.
 * We return a pointer to the copy of the data in the tree.
 *
 * The value pointer can be NULL: in this case, we insert a block of
 * length bytes, but don't initialise it. You get a pointer to it and
 * can initialise it yourself.
 *
 * If the key already exists, the value will be replaced with the new data.
 */
LPVOID APIENTRY
tree_update(TREE tree, TREEKEY key, LPVOID value, UINT length)
{
        TREEITEM item;

        /* find the place in the tree for this key to go */
        item = tree_getitem(tree, key);

        if (item == NULL) {
                /* there is nothing in the tree: this item should
                 * go at the top
                 */
                tree->first = tree_newitem(tree, key, value, length);
                return(tree->first->data);
        }

        /* is this the same key ? */
        if (item->key == key) {

                /* this key already inserted. re-alloc the data */
                if (length != item->length) {
                        gmem_free(tree->hHeap, item->data, item->length);
                        item->data = gmem_get(tree->hHeap, length);
                }
                /* don't initialise block if no pointer passed */
                if (value != NULL) {
                        memcpy(item->data, value, length);
                }
                return(item->data);
        }

        /* not the same key - getitem returned the parent for
         * the new tree. insert it as a child of item.
         */
        return(tree_addafter(tree, &item, key, value, length));
}

/***************************************************************************
 * Function: tree_find
 *
 * Purpose:
 *
 * Returns a pointer to the value (data block) for a given key. Returns
 * null if not found.
 */
LPVOID APIENTRY
tree_find(TREE tree, TREEKEY key)
{
        TREEITEM item;

        /* find the correct place in the tree */
        item = tree_getitem(tree, key);

        if (item == NULL) {
                /* nothing in the tree */
                return(NULL);
        }

        if (item->key != key) {
                /* this key not in. getitem has returned parent */
                return(NULL);
        }

        /* found the right element - return pointer to the
         * data block
         */
        return(item->data);
}

/* The next two routines are an optimisation for a common tree operation. In
 * this case, the user will want to insert a new element only if
 * the key is not there. If it is there, he will want to modify the
 * existing value (increment a reference count, for example).
 *
 * If tree_search fails to find the key, it will return a TREEITEM handle
 * for the parent. This can be passed to tree_addafter to insert the
 * new element without re-searching the tree.
 */

/***************************************************************************
 * Function: tree_search
 *
 * Purpose:
 *
 * Find an element. If not, find it's correct parent item
 */
LPVOID APIENTRY
tree_search(TREE tree, TREEKEY key, PTREEITEM pplace)
{
        TREEITEM item;

        item = tree_getitem(tree, key);

        if (item == NULL) {
                /* no items in tree. set placeholder to NULL to
                 * indicate insert at top of tree
                 */
                *pplace = NULL;         

                /* return NULL to indicate key not found */
                return(NULL);
        }

        if (item->key == key) {
                /* found the key already there -
                 * set pplace to null just for safety
                 */
                *pplace = NULL;

                /* give the user a pointer to his data */
                return(item->data);
        }


        /* key was not found - getitem has returned the parent
         * - set this as the place for new insertions
         */
        *pplace = item;         

        /* return NULL to indicate that the key was not found */
        return(NULL);
}

/***************************************************************************
 * Function: tree_addafter
 *
 * Purpose:
 *
 * Insert a key in the position already found by tree_search.
 *
 * Return a pointer to the user's data in the tree. If the value
 * pointer passed in is null, then we allocate the block, but don't
 * initialise it to anything.
 */
LPVOID APIENTRY
tree_addafter(TREE tree, PTREEITEM place, TREEKEY key, LPVOID value, UINT length)
{
        TREEITEM item, child;

        item = *place;
        if (item == NULL) {
                tree->first = tree_newitem(tree, key, value, length);
                return (tree->first->data);
        }               

        child = tree_newitem(tree, key, value, length);         
        if (child->key < item->key ) {
                /* should go on left leg */
                item->left = child;
        } else {        
                item->right = child;
        }
        return(child->data);
}


/* --- ctree ------------------------------------------------------*/

/*
 * ctree is a class of tree built on top of the tree interface. a
 * ctree keeps count of the number of insertions of identical keys.
 *
 * We do this be adding a long counter to the beginning of the user
 * data before inserting into the tree. If the key is not found, we set
 * this to one. If the key was already there, we *do not* insert the
 * data (data is always from the first insertion) - we simply increment
 * the count.
 */

/* Create a tree for use by CTREE - same as an ordinary tree
 */
TREE APIENTRY
ctree_create(HANDLE hHeap)
{
        return(tree_create(hHeap));
}

/*
 * Delete a ctree - same as for TREE
 */
void APIENTRY
ctree_delete(TREE tree)
{
        tree_delete(tree);
}


/***************************************************************************
 * Function: ctree_update
 *
 * Purpose:
 *
 * Insert an element in the tree. If the element is not there,
 * insert the data and set the reference count for this key to 1.
 * If the key was there already, don't change the data, just increment
 * the reference count
 *
 * If the value pointer is not null, we initialise the value block
 * in the tree to contain this.
 *
 * We return a pointer to the users data in the tree
 */
LPVOID APIENTRY
ctree_update(TREE tree, TREEKEY key, LPVOID value, UINT length)
{
        TREEITEM item;
        long FAR * pcounter;
        LPVOID datacopy;

        pcounter = tree_search(tree, key, &item);

        if (pcounter == NULL) {
                /* element not found - insert a new one
                 * the data block for this element should be
                 * the user's block with our reference count at
                 * the beginning
                 */
                pcounter = tree_addafter(tree, &item, key, NULL,
                                length + sizeof(long));
                *pcounter = 1;
                /* add on size of one long to get the start of the user
                 * data
                 */
                datacopy = pcounter + 1;
                if (value != NULL) {
                        memcpy(datacopy, value, length);
                }
                return(datacopy);
        }

        /* key was already there - increment reference count and
         * return pointer to data
         */

        (*pcounter)++;

        /* add on size of one long to get the start of the user
         * data
         */
        datacopy = pcounter + 1;
        return(datacopy);
}

/***************************************************************************
 * Function: ctree_getcount
 *
 * Purpose:
 *
 * Return the reference count for this key 
 */
long APIENTRY
ctree_getcount(TREE tree, TREEKEY key)
{
        long FAR * pcounter;

        pcounter = tree_find(tree, key);
        if (pcounter == NULL) {
                return(0);
        }
        return(*pcounter);
}

/***************************************************************************
 * Function: ctree_find
 *
 * Purpose:
 *
 * Return a pointer to the user's data block for this key,
 * or NULL if key not present
 */
LPVOID APIENTRY
ctree_find(TREE tree, TREEKEY key)
{
        long FAR * pcounter;


        pcounter = tree_find(tree, key);
        if (pcounter == NULL) {
                return(0);
        }

        /* increment pointer by size of 1 long to point to
         * user's datablock
         */
        return(pcounter+1);
}











