/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 * bltTree.c --
 *
 * Copyright 2015 George A. Howlett. All rights reserved.  
 *
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions are
 *   met:
 *
 *   1) Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *   2) Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the
 *      distribution.
 *   3) Neither the name of the authors nor the names of its contributors
 *      may be used to endorse or promote products derived from this
 *      software without specific prior written permission.
 *   4) Products derived from this software may not be called "BLT" nor may
 *      "BLT" appear in their names without specific prior written
 *      permission from the author.
 *
 *   THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED
 *   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *   DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 *   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 *   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 *   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 *   OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 *   IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#define BUILD_BLT_TCL_PROCS 1
#include "bltInt.h"

#ifdef HAVE_CTYPE_H
  #include <ctype.h>
#endif /* HAVE_CTYPE_H */

#include "bltAlloc.h"
#include "bltNsUtil.h"
#include "bltArrayObj.h"
#include "bltTree.h"

/* TODO:
 *      Traces and notifiers should be in one list in tree object.
 *      Notifier is always fired.
 *      Incorporate first/next tag routines ?
 */

static Tcl_InterpDeleteProc TreeInterpDeleteProc;
static Blt_TreeApplyProc SizeApplyProc;
static Tcl_IdleProc NotifyIdleEventProc;
static Tcl_IdleProc TraceIdleEventProc;

#define TREE_THREAD_KEY         "BLT Tree Data"
#define TREE_MAGIC              ((unsigned int) 0x46170277)
#define TREE_DESTROYED          (1<<0)

#define TREE_NODE_REBUILD_SIZE  3U

typedef struct _Blt_TreeNode Node;
typedef struct _Blt_Tree Tree;
typedef struct _Blt_TreeObject TreeObject;
typedef struct _Blt_TreeVariable Variable;

/*
 * Blt_TreeVariable --
 *
 *      A tree node may have zero or more variables that are represented by
 *      these container structures.  Each variable has both the name of the
 *      variable (Blt_TreeUid) and its value (Tcl_Obj).  Variables are
 *      private or public.  Private variables are only be seen by the tree
 *      client that created the variable.
 * 
 *      The set of variables in a node are organized in one of two
 *      ways. They are stored in a linked list in order that they were
 *      created.  Also, they may be placed into a hash table when the
 *      number of variables reaches a high-water mark.
 *
 */
struct _Blt_TreeVariable {
    Blt_TreeUid uid;                 /* String identifying the variable. */
    Tcl_Obj *objPtr;                 /* Data representation. */
    Blt_Tree owner;                  /* Non-NULL if privately owned. */
    struct _Blt_TreeVariable *nextPtr; /* Next variable in the chain. */
    struct _Blt_TreeVariable *prevPtr; /* Last variable in the chain. */

    struct _Blt_TreeVariable *nextHashPtr; /* Next variable in hash table
                                            * bucket. This is used when
                                            * node has more than a
                                            * high-water number of
                                            * variables. */
    struct _Blt_TreeVariable *prevHashPtr; /* Previous variable in hash
                                            * table bucket. This is used
                                            * (primarily for deletion) when
                                            * node has more than a
                                            * high-water number of
                                            * variables. */
};

#include <stdio.h>
#include <string.h>
/* The following header is required for LP64 compilation */
#include <stdlib.h>

#include "bltHash.h"

static void DestroyVariables(Blt_TreeNode node);

static Variable *FindVariable(Blt_TreeNode node, Blt_TreeUid uid);
static Variable *CreateVariable(Blt_TreeNode node, Blt_TreeUid uid, 
        int *newPtr);

static int DeleteVariable(Blt_TreeNode node, Blt_TreeVariable var);

static Variable *FirstVariable(Blt_TreeNode, 
        Blt_TreeVariableIterator *iterPtr);

static Variable *NextVariable(Blt_TreeVariableIterator *iterPtr);

/*
 * When there are this many entries per bucket, on average, rebuild the
 * hash table to make it larger.
 */
#define REBUILD_MULTIPLIER  3
#define START_LOGSIZE       5           /* Initial hash table size is 32. */
#define NODE_HIGH_WATER     10          /* Start a hash table when a node
                                         * has this many child nodes. */
#define VAR_HIGH_WATER    10            /* Start a hash table when a node
                                         * has this many variables. */
#define VAR_LOW_WATER       (VAR_HIGH_WATER << 1)
#define NODE_LOW_WATER      (NODE_HIGH_WATER << 1)

#if (SIZEOF_VOID_P == 8)
#define RANDOM_INDEX(i)         HashOneWord(mask, downshift, i)
static Blt_Hash HashOneWord(uint64_t mask, unsigned int downshift, 
        const void *key);
#define BITSPERWORD             64
#else 

/*
 * The following macro takes a preliminary integer hash value and produces
 * an index into a hash tables bucket list.  The idea is to make it so that
 * preliminary values that are arbitrarily similar will end up in different
 * buckets.  The hash function was taken from a random-number generator.
 */
#define RANDOM_INDEX(i) \
    (((((size_t) (i))*1103515245) >> downshift) & mask)
#define BITSPERWORD             32
#endif /* SIZEOF_VOID_P == 8 */

#define DOWNSHIFT_START         (BITSPERWORD - 2) 

/*
 * The hash table below is used to keep track of all the Blt_TreeUids
 * created so far.
 */
typedef struct _Blt_TreeInterpData {
    Tcl_Interp *interp;
    Blt_HashTable treeTable;            /* Table of trees. */
    unsigned int nextId;                /* Identifier used to generate
                                         * automatic tree names. */
} TreeInterpData;

typedef struct {
    Tcl_Interp *interp;
    ClientData clientData;
    Blt_TreeUid uid;
    Blt_TreeNotifyEventProc *proc;
    Blt_TreeNotifyEvent event;
    unsigned int mask;
    int notifyPending;
} NotifyEventHandler;

typedef struct {
    /* This must match _Blt_TreeTrace in bltTree.h */
    ClientData clientData;
    const char *keyPattern;             /* Pattern to be matched */
    Node *nodePtr;                      /* Node to be matched.  */
    unsigned int mask;
    Blt_TreeTraceProc *proc;

    /* Private fields */
    const char *withTag;
    Tree *treePtr;
    Blt_ChainLink readLink;             /* Pointer to list of read
                                         * traces. */
    Blt_ChainLink writeLink;            /* Pointer to list of write
                                         * traces. */
    Blt_HashTable idleTable;            /* Table of do-when-idle event
                                         * callbacks. */
    Tcl_Interp *interp;
} TraceHandler;

typedef struct {
    TraceHandler *tracePtr;             /* Trace matched. */
    Tcl_Interp *interp;                 /* Source interpreter. */
    Blt_TreeUid uid;                    /* Key that matched. */
    int flags;                          /* Flags that matched. */
    int64_t inode;                      /* Node that matched. */
    Blt_HashEntry *hashPtr;             /* Pointer to this entry in the
                                         * trace's idle event table. */
} TraceIdleEvent;

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_GetInterpData --
 *
 *      Creates or retrieves data associated with tree data objects for a
 *      particular thread.  We're using Tcl_GetAssocData rather than the
 *      Tcl thread routines so BLT can work with pre-8.0 TCL versions that
 *      don't have thread support.
 *
 * Results:
 *      Returns a pointer to the tree interpreter data.
 *
 *---------------------------------------------------------------------------
 */
static Blt_TreeInterpData *
Blt_Tree_GetInterpData(Tcl_Interp *interp)
{
    Tcl_InterpDeleteProc *proc;
    TreeInterpData *dataPtr;

    dataPtr = (TreeInterpData *)
        Tcl_GetAssocData(interp, TREE_THREAD_KEY, &proc);
    if (dataPtr == NULL) {
        dataPtr = Blt_AssertMalloc(sizeof(TreeInterpData));
        dataPtr->interp = interp;
        Tcl_SetAssocData(interp, TREE_THREAD_KEY, TreeInterpDeleteProc,
                 dataPtr);
        Blt_InitHashTable(&dataPtr->treeTable, BLT_STRING_KEYS);
    }
    return dataPtr;
}


const char *
Blt_Tree_NodeIdAscii(Node *nodePtr)
{
    static char stringRep[200];

    Blt_FmtString(stringRep, 200, "%ld", nodePtr->inode);
    return stringRep;
}

static Tree *
FirstClient(TreeObject *corePtr)
{
    Blt_ChainLink link;

    link = Blt_Chain_FirstLink(corePtr->clients); 
    if (link == NULL) {
        return NULL;
    }
    return Blt_Chain_GetValue(link);
}

static Tree *
NextClient(Tree *treePtr)
{
    Blt_ChainLink link;

    if (treePtr == NULL) {
        return NULL;
    }
    link = Blt_Chain_NextLink(treePtr->link); 
    if (link == NULL) {
        return NULL;
    }
    return Blt_Chain_GetValue(link);
}

/*
 *---------------------------------------------------------------------------
 *
 * NewNode --
 *
 *      Creates a new node in the tree without installing it.  The number
 *      of nodes in the tree is incremented and a unique serial number is
 *      generated for the node.
 *
 *      Also, all nodes have a label.  If no label was provided (name is
 *      NULL) then automatically generate one in the form "nodeN" where N
 *      is the serial number of the node.
 *
 * Results:
 *      Returns a pointer to the new node.
 *
 * -------------------------------------------------------------- 
 */
static Node *
NewNode(TreeObject *corePtr, const char *name, long inode)
{
    Node *nodePtr;

    /* Create the node structure */
    nodePtr = Blt_Pool_AllocItem(corePtr->nodePool, sizeof(Node));
    nodePtr->inode = inode;
    nodePtr->corePtr = corePtr;
    nodePtr->parentPtr = NULL;
    nodePtr->depth = 0;
    nodePtr->flags = 0;
    nodePtr->nextPtr = nodePtr->prevPtr = NULL;
    nodePtr->firstChildPtr = nodePtr->lastChildPtr = NULL;
    nodePtr->numChildren = 0;
    nodePtr->firstVarPtr = nodePtr->lastVarPtr = NULL;     
    nodePtr->varTable = NULL;     
    nodePtr->varTableSize2 = 0;
    nodePtr->numVariables = 0;
    nodePtr->nodeTable = NULL;
    nodePtr->nodeTableSize2 = 0;
    nodePtr->nextHashPtr = nodePtr->prevHashPtr = NULL;

    nodePtr->label = NULL;
    if (name != NULL) {
        nodePtr->label = Blt_Tree_GetUidFromNode(nodePtr, name);
    }
    corePtr->numNodes++;
    return nodePtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * ReleaseTagTable --
 *
 *---------------------------------------------------------------------------
 */
static void
ReleaseTagTable(Blt_TreeTagTable *tablePtr)
{
    tablePtr->refCount--;
    if (tablePtr->refCount <= 0) {
        Blt_HashEntry *hPtr;
        Blt_HashSearch cursor;

        for (hPtr = Blt_FirstHashEntry(&tablePtr->tagTable, &cursor); 
            hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
            Blt_TreeTagEntry *tePtr;

            tePtr = Blt_GetHashValue(hPtr);
            Blt_DeleteHashTable(&tePtr->nodeTable);
            Blt_Free(tePtr);
        }
        Blt_DeleteHashTable(&tablePtr->tagTable);
        Blt_Free(tablePtr);
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * ResetDepths --
 *
 *      Called after moving a node, resets the depths of each node for the
 *      entire branch (node and it's decendants).
 *
 * Results: 
 *      None.
 *
 * ---------------------------------------------------------------------- 
 */
static void
ResetDepths(Node *parentPtr, long depth)
{
    Node *childPtr;

    parentPtr->corePtr->depth = parentPtr->depth = depth;
    /* Also reset the depth for each descendant node. */
    for (childPtr = parentPtr->firstChildPtr; childPtr != NULL; 
         childPtr = childPtr->nextPtr) {
        ResetDepths(childPtr, depth + 1);
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * RebuildNodeTable --
 *
 *      This procedure is invoked when the ratio of entries to hash buckets
 *      becomes too large.  It creates a new table with a larger bucket
 *      array and moves all of the entries into the new table.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Memory gets reallocated and entries get re-hashed to new buckets.
 *
 *---------------------------------------------------------------------------
 */
static void
RebuildNodeTable(Node *parentPtr)       /* Table to enlarge. */
{
    Node **bp, **bend;
    unsigned int downshift;
    size_t mask;
    Node **buckets;
    size_t numBuckets;

    numBuckets = (1 << parentPtr->nodeTableSize2);
    bend = parentPtr->nodeTable + numBuckets;

    /*
     * Allocate and initialize the new bucket array, and set up hashing
     * constants for new array size.
     */
    parentPtr->nodeTableSize2 += 2;
    numBuckets = (1 << parentPtr->nodeTableSize2);
    buckets = Blt_AssertCalloc(numBuckets, sizeof(Node *));
    /*
     * Move all of the existing entries into the new bucket array, based on
     * their new hash values.
     */
    mask = numBuckets - 1;
    downshift = DOWNSHIFT_START - parentPtr->nodeTableSize2;
    for (bp = parentPtr->nodeTable; bp < bend; bp++) {
        Node *nodePtr, *nextPtr;

        for (nodePtr = *bp; nodePtr != NULL; nodePtr = nextPtr) {
            Node **firstPtrPtr;
    
            nextPtr = nodePtr->nextHashPtr;
            firstPtrPtr = buckets + RANDOM_INDEX(nodePtr->label);
            /* Prepending to the beginning of the bucket. */
            if (*firstPtrPtr != NULL) {
                (*firstPtrPtr)->prevHashPtr = nodePtr;
            }
            nodePtr->nextHashPtr = *firstPtrPtr;
            *firstPtrPtr = nodePtr;
        }
    }
    Blt_Free(parentPtr->nodeTable);
    parentPtr->nodeTable = buckets;
}

#ifdef notdef
static void
PrintNodeTable(Node *parentPtr)       
{
    Node **bp, **bend;
    size_t numBuckets;
    int count;
    
    if (parentPtr->nodeTable != NULL) {
        numBuckets = (1 << parentPtr->nodeTableSize2);
        bend = parentPtr->nodeTable + numBuckets;
        
        /*
         * Allocate and initialize the new bucket array, and set up hashing
         * constants for new array size.
         */
        /*
         * Move all of the existing entries into the new bucket array, based on
         * their new hash values.
         */
        count = 0;
        for (bp = parentPtr->nodeTable; bp < bend; bp++) {
            Node *nodePtr;
            int childCount;
            
            count++;
            childCount = 0;
            fprintf(stderr, "Bucket #%d\n", count);
            for (nodePtr = *bp; nodePtr != NULL; nodePtr = nodePtr->nextHashPtr) {
                childCount++;
                fprintf(stderr, "  Node #%d (%s)\n", childCount,
                        nodePtr->label);
            }
        }
    }
}  
#endif

/*
 *---------------------------------------------------------------------------
 *
 * MakeNodeTable --
 *
 *      Generates a hash table from the nodes list of children.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Hash table for child nodes is created.
 *
 *---------------------------------------------------------------------------
 */
static void
MakeNodeTable(Node *parentPtr)
{
    Node **buckets;
    Node *childPtr, *nextPtr;
    int downshift;
    size_t mask;
    size_t numBuckets;

    assert(parentPtr->nodeTable == NULL);
    parentPtr->nodeTableSize2 = START_LOGSIZE;
    numBuckets = 1 << parentPtr->nodeTableSize2;
    buckets = Blt_AssertCalloc(numBuckets, sizeof(Node *));
    mask = numBuckets - 1;
    downshift = DOWNSHIFT_START - parentPtr->nodeTableSize2;
    for (childPtr = parentPtr->firstChildPtr; childPtr != NULL; 
         childPtr = nextPtr) {
        Node **firstPtrPtr;

        nextPtr = childPtr->nextPtr;
        firstPtrPtr = buckets + RANDOM_INDEX(childPtr->label);
        /* Prepending to the beginning of the bucket. */
        if (*firstPtrPtr != NULL) {
            (*firstPtrPtr)->prevHashPtr = childPtr;
        }
        childPtr->nextHashPtr = *firstPtrPtr;
        *firstPtrPtr = childPtr;
    }
    parentPtr->nodeTable = buckets;
}

/*
 *---------------------------------------------------------------------------
 *
 * LinkBefore --
 *
 *      Inserts a link preceding a given link.
 *
 * Results:
 *      None.
 *
 *---------------------------------------------------------------------------
 */
static void
LinkBefore(
    Node *parentPtr,                    /* Parent to hold the new entry. */
    Node *nodePtr,                      /* New node to be inserted. */
    Node *beforePtr)                    /* Node to link before. */
{
    if (parentPtr->firstChildPtr == NULL) {
        parentPtr->lastChildPtr = parentPtr->firstChildPtr = nodePtr;
    } else if (beforePtr == NULL) {     /* Append onto the end of the chain */
        nodePtr->nextPtr = NULL;
        nodePtr->prevPtr = parentPtr->lastChildPtr;
        parentPtr->lastChildPtr->nextPtr = nodePtr;
        parentPtr->lastChildPtr = nodePtr;
    } else {
        nodePtr->prevPtr = beforePtr->prevPtr;
        nodePtr->nextPtr = beforePtr;
        if (beforePtr == parentPtr->firstChildPtr) {
            parentPtr->firstChildPtr = nodePtr;
        } else {
            beforePtr->prevPtr->nextPtr = nodePtr;
        }
        beforePtr->prevPtr = nodePtr;
    }
    parentPtr->numChildren++;
    nodePtr->parentPtr = parentPtr;

    /* 
     * Check if there as so many children that an addition hash table should
     * be created.
     */
    if (parentPtr->nodeTable == NULL) {
        if (parentPtr->numChildren > NODE_HIGH_WATER) {
            MakeNodeTable(parentPtr);
        }
    } else {
        Node **firstPtrPtr;
        size_t numBuckets;
        unsigned int downshift;
        size_t mask;

        numBuckets = (1 << parentPtr->nodeTableSize2);
        mask = numBuckets - 1;
        downshift = DOWNSHIFT_START - parentPtr->nodeTableSize2;
        firstPtrPtr = parentPtr->nodeTable + RANDOM_INDEX(nodePtr->label);
        /* Prepending to the beginning of the bucket. */
        if (*firstPtrPtr != NULL) {
            (*firstPtrPtr)->prevHashPtr = nodePtr;
        }
        nodePtr->nextHashPtr = *firstPtrPtr;
        *firstPtrPtr = nodePtr;
        /*
         * If the table has exceeded a decent size, rebuild it with many more
         * buckets.
         */
        if (parentPtr->numChildren >= (numBuckets * TREE_NODE_REBUILD_SIZE)) {
            RebuildNodeTable(parentPtr);
        }
    } 
}


/*
 *---------------------------------------------------------------------------
 *
 * UnlinkNode --
 *
 *      Unlinks a link from the chain. The link is not deallocated, but only
 *      removed from the chain.
 *
 * Results:
 *      None.
 *
 *---------------------------------------------------------------------------
 */
static void
UnlinkNode(Node *nodePtr)
{
    Node *parentPtr;
    int unlinked;                       /* Indicates if the link is actually
                                         * removed from the chain. */
    parentPtr = nodePtr->parentPtr;
    unlinked = FALSE;
    if (parentPtr->firstChildPtr == nodePtr) {
        parentPtr->firstChildPtr = nodePtr->nextPtr;
        unlinked = TRUE;
    }
    if (parentPtr->lastChildPtr == nodePtr) {
        parentPtr->lastChildPtr = nodePtr->prevPtr;
        unlinked = TRUE;
    }
    if (nodePtr->nextPtr != NULL) {
        nodePtr->nextPtr->prevPtr = nodePtr->prevPtr;
        unlinked = TRUE;
    }
    if (nodePtr->prevPtr != NULL) {
        nodePtr->prevPtr->nextPtr = nodePtr->nextPtr;
        unlinked = TRUE;
    }
    if (unlinked) {
        parentPtr->numChildren--;
    }
    nodePtr->prevPtr = nodePtr->nextPtr = NULL;
    if (parentPtr->nodeTable != NULL) {
        Node **firstPtrPtr;
        unsigned int downshift;
        size_t mask;

        mask = (1 << parentPtr->nodeTableSize2) - 1;
        downshift = DOWNSHIFT_START - parentPtr->nodeTableSize2;
        firstPtrPtr = parentPtr->nodeTable + RANDOM_INDEX(nodePtr->label);
        if (*firstPtrPtr == nodePtr) {
            *firstPtrPtr = nodePtr->nextHashPtr;
            if (*firstPtrPtr != NULL) {
                (*firstPtrPtr)->prevHashPtr = NULL;
            }
        } else {
            Node *nextPtr, *prevPtr;

            /* Note: Node may not be in the hash table. */
            nextPtr = nodePtr->nextHashPtr;
            prevPtr = nodePtr->prevHashPtr;
            if (prevPtr != NULL) {
                prevPtr->nextHashPtr = nextPtr;
            }
            if (nextPtr != NULL) {
                nextPtr->prevHashPtr = prevPtr;
            }
        }
    } 
    nodePtr->nextHashPtr = nodePtr->prevHashPtr = NULL;
    if (parentPtr->numChildren < NODE_LOW_WATER) {
        Blt_Free(parentPtr->nodeTable);
        parentPtr->nodeTable = NULL;
    }
}


/*
 *---------------------------------------------------------------------------
 *
 * ReorderNodes --
 *
 *      Special case of reordering the children node list after sorting.
 *      We don't have to unlink from the parent or remove the node from the
 *      node hash table (sorting doesn't change that). We just rebuild the
 *      the node list and reattach it to the parent.
 *
 * Results:
 *      None.
 *
 *---------------------------------------------------------------------------
 */
static void
ReorderNodes(Node *parentPtr, long numNodes, Node **nodeArr)
{
    long i;
    Node *nodePtr;

    assert(numNodes > 1);
    nodePtr = nodeArr[0];
    nodePtr->prevPtr = NULL;
    for (i = 1; i < numNodes; i++) {
        Node *nextPtr;

        nextPtr = nodeArr[i];
        nodePtr->nextPtr = nextPtr;
        nextPtr->prevPtr = nodePtr;
        nodePtr = nextPtr;
    }        
    parentPtr->firstChildPtr = nodeArr[0];
    parentPtr->lastChildPtr = nodePtr;
    nodePtr->nextPtr = NULL;
}

/*
 *---------------------------------------------------------------------------
 *
 * FreeNode --
 *
 *      Unlinks a given node from the tree, removes its data, and frees memory
 *      allocated to the node.
 *
 * Results:
 *      None.
 *
 * -------------------------------------------------------------- 
 */
static void
FreeNode(TreeObject *corePtr, Node *nodePtr)
{
    Blt_HashEntry *hPtr;

    /* Destroy any variables associated with this node. */
    if (nodePtr->firstVarPtr != NULL) { 
        DestroyVariables(nodePtr);
    }
    if (nodePtr->nodeTable != NULL) {
        Blt_Free(nodePtr->nodeTable);
    } 
    UnlinkNode(nodePtr);
    corePtr->numNodes--;
    hPtr = Blt_FindHashEntry(&corePtr->nodeTable,
                             (const char *)(intptr_t)nodePtr->inode);
    assert(hPtr);
    Blt_DeleteHashEntry(&corePtr->nodeTable, hPtr);
    Blt_Pool_FreeItem(corePtr->nodePool, nodePtr);
}


/*
 *---------------------------------------------------------------------------
 *
 * TeardownTree --
 *
 *      Destroys an entire branch.  This is a special purpose routine used to
 *      speed up the final clean up of the tree.
 *
 * Results: 
 *      None.
 *
 * ---------------------------------------------------------------------- 
 */
static void
TeardownTree(TreeObject *corePtr, Node *parentPtr)
{
    Node *childPtr, *nextPtr;
    
    if (parentPtr->nodeTable != NULL) {
        Blt_Free(parentPtr->nodeTable);
        parentPtr->nodeTable = NULL;
    } 
    if (parentPtr->firstVarPtr != NULL) {
        DestroyVariables(parentPtr);
    }
    for (childPtr = parentPtr->firstChildPtr; childPtr != NULL; 
         childPtr = nextPtr) {
        nextPtr = childPtr->nextPtr;
        TeardownTree(corePtr, childPtr);
    }
    Blt_Pool_FreeItem(corePtr->nodePool, parentPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * DestroyTreeObject --
 *
 *      Destroys a tree object, freeing its nodes and memory.
 *
 * Results: 
 *      None.
 *
 * ---------------------------------------------------------------------- 
 */
static void
DestroyTreeObject(TreeObject *corePtr)
{
    corePtr->flags |= TREE_DESTROYED;
    corePtr->numNodes = 0;

    assert(Blt_Chain_GetLength(corePtr->clients) == 0);
    Blt_Chain_Destroy(corePtr->clients);
    if (corePtr->pathObjPtr != NULL) {
        Tcl_DecrRefCount(corePtr->pathObjPtr);
    }
    TeardownTree(corePtr, corePtr->root);
    Blt_Pool_Destroy(corePtr->nodePool);
    Blt_Pool_Destroy(corePtr->varPool);
    Blt_DeleteHashTable(&corePtr->nodeTable);
    Blt_DeleteHashTable(&corePtr->uidTable);
    Blt_Free(corePtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * ReleaseTreeObject --
 *
 *      Removes the client from the core tree object's list of clients.  If
 *      there are no more clients, then the tree object itself is destroyed.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      The client is detached from the tree object.  The tree object is
 *      possibly destroyed, freeing memory.
 *
 *---------------------------------------------------------------------------
 */
static void
ReleaseTreeObject(Tree *treePtr) 
{
    if ((treePtr->link != NULL) && (treePtr->corePtr != NULL)) {
        /* Remove the client from the core's list */
        Blt_Chain_DeleteLink(treePtr->corePtr->clients, treePtr->link);
        if (Blt_Chain_GetLength(treePtr->corePtr->clients) == 0) {
            DestroyTreeObject(treePtr->corePtr);
        }
        treePtr->corePtr = NULL;
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * NewTreeObject --
 *
 *      Creates and initializes a new tree object. Trees always contain a root
 *      node, so one is allocated here.
 *
 * Results:
 *      Returns a pointer to the new tree object is successful, NULL
 *      otherwise.  If a tree can't be generated, interp->result will contain
 *      an error message.
 *
 *---------------------------------------------------------------------------
 */
static TreeObject *
NewTreeObject(TreeInterpData *dataPtr)
{
    TreeObject *corePtr;
    int isNew;
    Blt_HashEntry *hPtr;

    corePtr = Blt_Calloc(1, sizeof(TreeObject));
    if (corePtr == NULL) {
        return NULL;
    }
    corePtr->dataPtr = dataPtr;
    corePtr->varPool = Blt_Pool_Create(BLT_FIXED_SIZE_ITEMS);
    corePtr->nodePool = Blt_Pool_Create(BLT_FIXED_SIZE_ITEMS);
    corePtr->clients = Blt_Chain_Create();
    corePtr->depth = 1;
    corePtr->notifyFlags = 0;
    Blt_InitHashTable(&corePtr->uidTable, BLT_STRING_KEYS);
    Blt_InitHashTableWithPool(&corePtr->nodeTable, BLT_ONE_WORD_KEYS);
    /* Put root node in table. */
    hPtr = Blt_CreateHashEntry(&corePtr->nodeTable, (const char *)(intptr_t)0,
                               &isNew);
    corePtr->root = NewNode(corePtr, "", 0);
    corePtr->pathObjPtr = Tcl_NewStringObj("", -1);
    Blt_SetHashValue(hPtr, corePtr->root);
    return corePtr;
}

static Tree *
FindClientInNamespace(TreeInterpData *dataPtr, Blt_ObjectName *objNamePtr)
{
    Tcl_DString ds;
    const char *qualName;
    Blt_HashEntry *hPtr;

    qualName = Blt_MakeQualifiedName(objNamePtr, &ds);
    hPtr = Blt_FindHashEntry(&dataPtr->treeTable, qualName);
    Tcl_DStringFree(&ds);
    if (hPtr == NULL) {
        return NULL;
    }
    return Blt_GetHashValue(hPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * GetTree --
 *
 *      Searches for the tree object associated by the name given.
 *
 * Results:
 *      Returns a pointer to the tree if found, otherwise NULL.
 *
 *---------------------------------------------------------------------------
 */
static Tree *
GetTree(TreeInterpData *dataPtr, const char *name, int flags)
{
    Blt_ObjectName objName;
    Tree *treePtr;
    Tcl_Interp *interp;

    treePtr = NULL;
    interp = dataPtr->interp;
    if (!Blt_ParseObjectName(interp, name, &objName, BLT_NO_DEFAULT_NS)) {
        return NULL;
    }
    if (objName.nsPtr != NULL) { 
        treePtr = FindClientInNamespace(dataPtr, &objName);
    } else { 
        if (flags & NS_SEARCH_CURRENT) {
            /* Look first in the current namespace. */
            objName.nsPtr = Tcl_GetCurrentNamespace(interp);
            treePtr = FindClientInNamespace(dataPtr, &objName);
        }
        if ((treePtr == NULL) && (flags & NS_SEARCH_GLOBAL)) {
            objName.nsPtr = Tcl_GetGlobalNamespace(interp);
            treePtr = FindClientInNamespace(dataPtr, &objName);
        }
    }
    return treePtr;
}

static void
ResetTree(Tree *treePtr) 
{
    Blt_ChainLink link, next;

    /* Remove any Write traces that may be set. */
    for (link = Blt_Chain_FirstLink(treePtr->writeTraces); link != NULL; 
        link = next) {
        Blt_TreeTrace trace;

        next = Blt_Chain_NextLink(link);
        trace = Blt_Chain_GetValue(link);
        Blt_Tree_DeleteTrace(trace);
    }
    /* Remove any Read traces that may be set. */
    for (link = Blt_Chain_FirstLink(treePtr->readTraces); link != NULL; 
        link = next) {
        Blt_TreeTrace trace;

        next = Blt_Chain_NextLink(link);
        trace = Blt_Chain_GetValue(link);
        Blt_Tree_DeleteTrace(trace);
    }

    /* And any event handlers. */
    for (link = Blt_Chain_FirstLink(treePtr->events); 
        link != NULL; link = Blt_Chain_NextLink(link)) {
        NotifyEventHandler *notifyPtr;

        notifyPtr = Blt_Chain_GetValue(link);
        if (notifyPtr->notifyPending) {
            Tcl_CancelIdleCall(NotifyIdleEventProc, notifyPtr);
        }
        Blt_Free(notifyPtr);
    }
    Blt_Chain_Reset(treePtr->events);
}

static void
DestroyTree(Tree *treePtr) 
{
    TreeInterpData *dataPtr;

    dataPtr = treePtr->corePtr->dataPtr;
    if (treePtr->tagTablePtr != NULL) {
        ReleaseTagTable(treePtr->tagTablePtr);
    }
    ResetTree(treePtr);
    if (treePtr->hPtr != NULL) {
        Blt_DeleteHashEntry(&dataPtr->treeTable, treePtr->hPtr);
    }
    Blt_Chain_Destroy(treePtr->readTraces);
    Blt_Chain_Destroy(treePtr->writeTraces);
    Blt_Chain_Destroy(treePtr->events);
    treePtr->magic = 0;
    ReleaseTreeObject(treePtr);
    Blt_Free(treePtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * TreeInterpDeleteProc --
 *
 *      This is called when the interpreter hosting the tree object is deleted
 *      from the interpreter.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Destroys all remaining trees and removes the hash table used to
 *      register tree names.
 *
 *---------------------------------------------------------------------------
 */
/* ARGSUSED */
static void
TreeInterpDeleteProc(ClientData clientData, Tcl_Interp *interp)
{
    TreeInterpData *dataPtr = clientData;
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    
    for (hPtr = Blt_FirstHashEntry(&dataPtr->treeTable, &cursor);
         hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
        Tree *treePtr;

        treePtr = Blt_GetHashValue(hPtr);
        treePtr->hPtr = NULL;
        DestroyTree(treePtr);
    }
    Blt_DeleteHashTable(&dataPtr->treeTable);
    Tcl_DeleteAssocData(interp, TREE_THREAD_KEY);
    Blt_Free(dataPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * NotifyIdleEventProc --
 *
 *      Used to invoke event handler routines at some idle point.  This
 *      routine is called from the TCL event loop.  Errors generated by the
 *      event handler routines are backgrounded.
 *      
 * Results:
 *      None.
 *
 *---------------------------------------------------------------------------
 */
static void
NotifyIdleEventProc(ClientData clientData)
{
    NotifyEventHandler *notifyPtr = clientData;
    int result;

    notifyPtr->notifyPending = FALSE;
    notifyPtr->mask |= TREE_NOTIFY_ACTIVE;
    result = (*notifyPtr->proc)(notifyPtr->clientData, &notifyPtr->event);
    notifyPtr->mask &= ~TREE_NOTIFY_ACTIVE;
    if (result != TCL_OK) {
        Tcl_BackgroundError(notifyPtr->interp);
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * CheckEventHandlers --
 *
 *      Traverses the list of client event callbacks and checks if one matches
 *      the given event.  A client may trigger an action that causes the tree
 *      to notify it.  The can be prevented by setting the
 *      TREE_NOTIFY_FOREIGN_ONLY bit in the event handler.
 *
 *      If a matching handler is found, a callback may be called either
 *      immediately or at the next idle time depending upon the
 *      TREE_NOTIFY_WHENIDLE bit.
 *
 *      Since a handler routine may trigger yet another call to itself,
 *      callbacks are ignored while the event handler is executing.
 *      
 * Results:
 *      None.
 *
 *---------------------------------------------------------------------------
 */
static void
CheckEventHandlers(Tree *treePtr, int isSource, Blt_TreeNotifyEvent *eventPtr)
{
    Blt_ChainLink link, next;

    eventPtr->tree = treePtr;
    for (link = Blt_Chain_FirstLink(treePtr->events); link != NULL; 
        link = next) {
        NotifyEventHandler *notifyPtr;

        next = Blt_Chain_NextLink(link);
        notifyPtr = Blt_Chain_GetValue(link);
        if ((notifyPtr->mask & TREE_NOTIFY_ACTIVE) ||
            (notifyPtr->mask & eventPtr->type) == 0) {
            continue;                   /* Ignore callbacks that are generated
                                         * inside of a notify handler
                                         * routine. */
        }
        if ((isSource) && (notifyPtr->mask & TREE_NOTIFY_FOREIGN_ONLY)) {
            continue;                   /* Don't notify yourself. */
        }
        if (notifyPtr->mask & TREE_NOTIFY_WHENIDLE) {
            if (!notifyPtr->notifyPending) {
                notifyPtr->notifyPending = TRUE;
                notifyPtr->event = *eventPtr;
                Tcl_DoWhenIdle(NotifyIdleEventProc, notifyPtr);
            }
        } else {
            int result;

            notifyPtr->mask |= TREE_NOTIFY_ACTIVE;
            result = (*notifyPtr->proc) (notifyPtr->clientData, eventPtr);
            notifyPtr->mask &= ~TREE_NOTIFY_ACTIVE;
            if (result != TCL_OK) {
                Tcl_BackgroundError(notifyPtr->interp);
            }
        }
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * NotifyClients --
 *
 *      Traverses the list of clients for a particular tree and notifies each
 *      client that an event occurred.  Clients indicate interest in a
 *      particular event through a bit flag.
 *
 *---------------------------------------------------------------------------
 */
static void
NotifyClients(Tree *sourcePtr, TreeObject *corePtr, Node *nodePtr, 
              int eventFlag)
{
    Blt_TreeNotifyEvent event;
    Tree *treePtr;

    event.type = eventFlag;
    event.inode = nodePtr->inode;
    event.node = nodePtr;
    /* 
     * Issue callbacks to each client indicating that a new node has been
     * created.
     */
    for (treePtr = FirstClient(corePtr); treePtr != NULL; 
         treePtr = NextClient(treePtr)) {
        int isSource;

        isSource = (treePtr == sourcePtr);
        CheckEventHandlers(treePtr, isSource, &event);
    }
}

static void
FreeVariable(Node *nodePtr, Variable *varPtr)
{
    if (varPtr->objPtr != NULL) {
        Tcl_DecrRefCount(varPtr->objPtr);
    }
    Blt_Pool_FreeItem(nodePtr->corePtr->varPool, varPtr);
}



#if (SIZEOF_VOID_P == 8)
/*
 *---------------------------------------------------------------------------
 *
 * HashOneWord --
 *
 *      Compute a one-word hash value of a 64-bit word, which then can be
 *      used to generate a hash index.
 *
 *      From Knuth, it's a multiplicative hash.  Multiplies an unsigned
 *      64-bit value with the golden ratio (sqrt(5) - 1) / 2.  The
 *      downshift value is 64 - n, when n is the log2 of the size of the
 *      hash table.
 *              
 * Results:
 *      The return value is a one-word summary of the information in 64 bit
 *      word.
 *
 * Side effects:
 *      None.
 *
 *---------------------------------------------------------------------------
 */
static Blt_Hash
HashOneWord(uint64_t mask, unsigned int downshift, const void *key)
{
    uint64_t a0, a1;
    uint64_t y0, y1;
    uint64_t y2, y3; 
    uint64_t p1, p2;
    uint64_t result;

    /* Compute key * GOLDEN_RATIO in 128-bit arithmetic */
    a0 = (uint64_t)key & 0x00000000FFFFFFFF; 
    a1 = (uint64_t)key >> 32;
    
    y0 = a0 * 0x000000007f4a7c13;
    y1 = a0 * 0x000000009e3779b9; 
    y2 = a1 * 0x000000007f4a7c13;
    y3 = a1 * 0x000000009e3779b9; 
    y1 += y0 >> 32;                     /* Can't carry */ 
    y1 += y2;                           /* Might carry */
    if (y1 < y2) {
        y3 += (1LL << 32);              /* Propagate */ 
    }

    /* 128-bit product: p1 = loword, p2 = hiword */
    p1 = ((y1 & 0x00000000FFFFFFFF) << 32) + (y0 & 0x00000000FFFFFFFF);
    p2 = y3 + (y1 >> 32);
    
    /* Left shift the value downward by the size of the table */
    if (downshift > 0) { 
        if (downshift < 64) { 
            result = ((p2 << (64 - downshift)) | (p1 >> (downshift & 63))); 
        } else { 
            result = p2 >> (downshift & 63); 
        } 
    } else { 
        result = p1;
    } 
    /* Finally mask off the high bits */
    return (Blt_Hash)(result & mask);
}

#endif /* SIZEOF_VOID_P == 8 */

/*
 *---------------------------------------------------------------------------
 *
 * RebuildTable --
 *
 *      This procedure is invoked when the ratio of entries to hash buckets
 *      becomes too large.  It creates a new table with a larger bucket
 *      array and moves all of the entries into the new table.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Memory gets reallocated and entries get re-hashed to new buckets.
 *
 *---------------------------------------------------------------------------
 */
static void
RebuildVariableTable(Node *nodePtr)        /* Table to enlarge. */
{
    Variable **bp, **bend, **buckets, **oldBuckets;
    size_t numBuckets;
    unsigned int downshift;
    size_t mask;

    oldBuckets = nodePtr->varTable;
    numBuckets = (1 << nodePtr->varTableSize2);
    bend = oldBuckets + numBuckets;

    /*
     * Allocate and initialize the new bucket array, and set up hashing
     * constants for new array size.
     */
    nodePtr->varTableSize2 += 2;
    numBuckets = (1 << nodePtr->varTableSize2);
    buckets = Blt_AssertCalloc(numBuckets, sizeof(Variable *));

    /*
     * Move all of the existing entries into the new bucket array, based on
     * their hash values.
     */
    mask = numBuckets - 1;
    downshift = DOWNSHIFT_START - nodePtr->varTableSize2;
    for (bp = oldBuckets; bp < bend; bp++) {
        Variable *varPtr, *nextPtr;

        for (varPtr = *bp; varPtr != NULL; varPtr = nextPtr) {
            Variable **firstPtrPtr;

            nextPtr = varPtr->nextHashPtr;
            firstPtrPtr = buckets + RANDOM_INDEX(varPtr->uid);
            /* Prepending to the beginning of the bucket. */
            if (*firstPtrPtr != NULL) {
                (*firstPtrPtr)->prevHashPtr = varPtr;
            }
            varPtr->nextHashPtr = *firstPtrPtr;
            *firstPtrPtr = varPtr;
        }
    }
    nodePtr->varTable = buckets;
    Blt_Free(oldBuckets);
}

static void
MakeVariableTable(Node *nodePtr)
{
    unsigned int numBuckets;
    Variable **buckets;
    unsigned int mask;
    int downshift;
    Variable *varPtr, *nextPtr;

    assert(nodePtr->varTable == NULL);

    /* Generate hash table from list of variables. */
    nodePtr->varTableSize2 = START_LOGSIZE;
    numBuckets = 1 << nodePtr->varTableSize2;
      buckets = Blt_AssertCalloc(numBuckets, sizeof(Variable *));
    mask = numBuckets - 1;
    downshift = DOWNSHIFT_START - nodePtr->varTableSize2;
    for (varPtr = nodePtr->firstVarPtr; varPtr != NULL; varPtr = nextPtr) {
        Variable **firstPtrPtr;

        nextPtr = varPtr->nextPtr;
        firstPtrPtr = buckets + RANDOM_INDEX(varPtr->uid);
        /* Prepending to the beginning of the bucket. */
        if (*firstPtrPtr != NULL) {
            (*firstPtrPtr)->prevHashPtr = varPtr;
        }
        varPtr->nextHashPtr = *firstPtrPtr;
        *firstPtrPtr = varPtr;
    }
    nodePtr->varTable = buckets;
}

/*
 *---------------------------------------------------------------------------
 *
 * DeleteVariable --
 *
 *      Remove a single entry from a hash table.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      The entry given by entryPtr is deleted from its table and should
 *      never again be used by the caller.  
 *
 *---------------------------------------------------------------------------
 */
static int
DeleteVariable(Node *nodePtr, Variable *varPtr)
{
    if (nodePtr->varTable != NULL) {
        Variable **firstPtrPtr;
        unsigned int downshift;
        size_t mask;

        mask = (1 << nodePtr->varTableSize2) - 1;
        downshift = DOWNSHIFT_START - nodePtr->varTableSize2;
        firstPtrPtr = nodePtr->varTable + RANDOM_INDEX(varPtr->uid);
        if (*firstPtrPtr == varPtr) {
            *firstPtrPtr = varPtr->nextHashPtr;
            if (*firstPtrPtr != NULL) {
                (*firstPtrPtr)->prevHashPtr = NULL;
            }
        } else {
            Variable *prevPtr, *nextPtr;

            /* Note: Variable may not be in the hash table. */
            prevPtr = varPtr->prevHashPtr;
            nextPtr = varPtr->nextHashPtr;
            if (prevPtr != NULL) {
                prevPtr->nextHashPtr = varPtr->nextHashPtr;
            }
            if (nextPtr != NULL) {
                nextPtr->prevHashPtr = prevPtr;
            }
        }
    } 
    if (nodePtr->firstVarPtr == varPtr) {
        nodePtr->firstVarPtr = varPtr->nextPtr;
    }
    if (nodePtr->lastVarPtr == varPtr) {
        nodePtr->lastVarPtr = varPtr->prevPtr;
    }
    if (varPtr->nextPtr != NULL) {
        varPtr->nextPtr->prevPtr = varPtr->prevPtr;
    }
    if (varPtr->prevPtr != NULL) {
        varPtr->prevPtr->nextPtr = varPtr->nextPtr;
    }
    nodePtr->numVariables--;
    FreeVariable(nodePtr, varPtr);
    if (nodePtr->numVariables < VAR_LOW_WATER) {
        Blt_Free(nodePtr->varTable);
        nodePtr->varTable = NULL;
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * DestroyVariables --
 *
 *      Free up everything associated with a hash table except for the
 *      record for the table itself.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      The hash table is no longer useable.
 *
 *---------------------------------------------------------------------------
 */
static void
DestroyVariables(Node *nodePtr)
{
    Variable *varPtr, *nextPtr;

    /* Free the variable hash table. */
    if (nodePtr->varTable != NULL) {
        Blt_Free(nodePtr->varTable);
    } 
    /* Free all the node's variables from the variable list. */
    for (varPtr = nodePtr->firstVarPtr; varPtr != NULL; varPtr = nextPtr) {
        nextPtr = varPtr->nextPtr;
        FreeVariable(nodePtr, varPtr);
    }
    /* Reset the variable pointers and counts. */
    nodePtr->firstVarPtr = nodePtr->lastVarPtr = NULL;
    nodePtr->varTable = NULL;
    nodePtr->numVariables = 0;
    nodePtr->varTableSize2 = 0;
}

/*
 *---------------------------------------------------------------------------
 *
 * FirstVariable --
 *
 *      Set up an iterator record that can be used to step through all the
 *      variables in the node.
 *
 * Results:
 *      The return value is a pointer to the first variable in tablePtr, or
 *      NULL if tablePtr has no entries in it.  The contents of *iterPtr is
 *      initialized so that subsequent calls to Blt_Tree_NextVariable will
 *      return all of the variables in the table, one at a time.
 *
 * Side effects:
 *      None.
 *
 *---------------------------------------------------------------------------
 */
static Variable *
FirstVariable(Node *nodePtr, Blt_TreeVariableIterator *iterPtr)
{
    iterPtr->node = nodePtr;
    iterPtr->nextIndex = 0;
    iterPtr->nextVar = nodePtr->firstVarPtr;
    return NextVariable(iterPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * NextVariable --
 *
 *      Once a hash table enumeration has been initiated by calling
 *      Blt_Tree_FirstVariable, this procedure may be called to return
 *      successive elements of the table.
 *
 * Results:
 *      The return value is the next entry in the hash table being
 *      enumerated, or NULL if the end of the table is reached.
 *
 * Side effects:
 *      None.
 *
 *---------------------------------------------------------------------------
 */
static Variable *
NextVariable(
    Blt_TreeVariableIterator *iterPtr)  /* Place to store information
                                           * about progress through the
                                           * table.  Must have been
                                           * initialized by calling
                                           * Blt_Tree_FirstVariable. */
{
    Variable *varPtr;

    varPtr = iterPtr->nextVar;
    if (varPtr != NULL) {
        iterPtr->nextVar = varPtr->nextPtr;
    }
    return varPtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * FindVariable --
 *
 *      Given a hash table with one-word keys, and a one-word key, find the
 *      entry with a matching key.
 *
 * Results:
 *      The return value is a token for the matching entry in the hash
 *      table, or NULL if there was no matching entry.
 *
 * Side effects:
 *      None.
 *
 *---------------------------------------------------------------------------
 */
static Variable *
FindVariable(Node *nodePtr, Blt_TreeUid uid)
{

    if (nodePtr->varTable != NULL) {
        Variable *bucket, *varPtr;
        size_t mask;
        unsigned int downshift;

        mask = (1 << nodePtr->varTableSize2) - 1;
        downshift = DOWNSHIFT_START - nodePtr->varTableSize2;
        bucket = nodePtr->varTable[RANDOM_INDEX(uid)];

        /* Search all of the entries in the appropriate bucket. */
        for (varPtr = bucket; varPtr != NULL; varPtr = varPtr->nextHashPtr) {
            if (varPtr->uid == uid) {
                return varPtr;
            }
        }
    } else {
        Variable *varPtr;

        /* Linear search through node's variable list. */
        for (varPtr = nodePtr->firstVarPtr; varPtr != NULL ; 
             varPtr = varPtr->nextPtr) {
            if (varPtr->uid == uid) {
                return varPtr;
            }
        }
    }
    return NULL;
}

/*
 *---------------------------------------------------------------------------
 *
 * CreateVariable --
 *
 *      Find the variable with a matching uid (name).  If there is no
 *      matching value, then create a new one.
 *
 * Results:
 *      The return value is a pointer to the matching variable.  If this is
 *      a newly-created variable, then *newPtr will be set to a non-zero
 *      value; otherwise *newPtr will be set to 0.
 *
 * Side effects:
 *      A new variable may be added to the hash table.
 *
 *---------------------------------------------------------------------------
 */
static Variable *
CreateVariable(
    Node *nodePtr,
    Blt_TreeUid uid,                    /* Key to use to find or create
                                         * matching entry. */
    int *isNewPtr)                      /* (out) If non-zero, indicates a
                                         * new hash entry was created. */
{
    Variable *varPtr;
    
    varPtr = FindVariable(nodePtr, uid);
    if (varPtr != NULL) {
        *isNewPtr = FALSE;
        return varPtr;
    }
    /* Variable not found. Add a new variable to the list. */
    *isNewPtr = TRUE;
    varPtr = Blt_Pool_AllocItem(nodePtr->corePtr->varPool, sizeof(Variable));
    memset(varPtr, 0, sizeof(Variable));
    varPtr->uid = uid;
    if (nodePtr->firstVarPtr == NULL) {
        nodePtr->lastVarPtr = nodePtr->firstVarPtr = varPtr;
    } else {
        /* Append variable to end of list. */
        varPtr->prevPtr = nodePtr->lastVarPtr;
        nodePtr->lastVarPtr->nextPtr = varPtr;
        nodePtr->lastVarPtr = varPtr;
    }
    nodePtr->numVariables++;
    if (nodePtr->varTable == NULL) {
        /* 
         * If we reach a threshold number of variables, create a hash table
         * of variables.
         */
        if (nodePtr->numVariables > VAR_HIGH_WATER) {
            MakeVariableTable(nodePtr);
        }
    } else {
        Variable **firstPtrPtr;
        size_t numBuckets;
        unsigned int downshift;
        size_t mask;

        numBuckets = (1 << nodePtr->varTableSize2);
        mask = numBuckets - 1;
        downshift = DOWNSHIFT_START - nodePtr->varTableSize2;
        firstPtrPtr = nodePtr->varTable + RANDOM_INDEX((void *)uid);
        /* Prepending to the beginning of the bucket. */
        if (*firstPtrPtr != NULL) {
            (*firstPtrPtr)->prevHashPtr = varPtr;
        }
        varPtr->nextHashPtr = *firstPtrPtr;
        *firstPtrPtr = varPtr;
        /*
         * If the table has exceeded a decent size, rebuild it with many
         * more buckets.
         */
        if ((unsigned int)nodePtr->numVariables >= (numBuckets * 3)) {
            RebuildVariableTable(nodePtr);
        }
    } 
    return varPtr;
}

/* Public Routines */

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_GetUid --
 *
 *      Given a string, returns a unique identifier for the string.
 *
 *---------------------------------------------------------------------------
 */
Blt_TreeUid
Blt_Tree_GetUid(Tree *treePtr, const char *string) /* String to convert. */
{
    Blt_HashEntry *hPtr;
    int isNew;
    Blt_HashTable *tablePtr;

    tablePtr = &treePtr->corePtr->uidTable;
    hPtr = Blt_CreateHashEntry(tablePtr, string, &isNew);
    return (Blt_TreeUid)Blt_GetHashKey(tablePtr, hPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_GetUidFromNode --
 *
 *      Given a string, returns a unique identifier for the string.
 *
 *---------------------------------------------------------------------------
 */
Blt_TreeUid
Blt_Tree_GetUidFromNode(Node *nodePtr, const char *string)
{
    Blt_HashEntry *hPtr;
    int isNew;
    Blt_HashTable *tablePtr;

    tablePtr = &nodePtr->corePtr->uidTable;
    hPtr = Blt_CreateHashEntry(tablePtr, string, &isNew);
    return (Blt_TreeUid)Blt_GetHashKey(tablePtr, hPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_CreateNode --
 *
 *      Creates a new node in the given parent node.  The name and position
 *      in the parent are also provided.
 *
 *---------------------------------------------------------------------------
 */
Blt_TreeNode
Blt_Tree_CreateNode(
    Tree *treePtr,                      /* The tree client that is creating
                                         * this node.  If NULL, indicates
                                         * to trigger notify events on
                                         * behalf of the initiating client
                                         * also. */
    Node *parentPtr,                    /* Parent node where the new node
                                         * will be inserted. */
    const char *name,                   /* Name of node. */
    Node *beforePtr)                    /* Node to insert new node before.
                                         * If NULL, new node is insert at
                                         * the end of the list. */
{
    Blt_HashEntry *hPtr;
    Node *nodePtr;                      /* Node to be inserted. */
    TreeObject *corePtr;
    long inode;
    int isNew;

    corePtr = parentPtr->corePtr;

    /* Generate an unique serial number for this node.  */
    do {
        inode = corePtr->nextInode++;
        hPtr = Blt_CreateHashEntry(&corePtr->nodeTable,
                   (const char *)(intptr_t)inode, &isNew);
    } while (!isNew);
    nodePtr = NewNode(corePtr, name, inode);
    Blt_SetHashValue(hPtr, nodePtr);

    if (beforePtr == TREE_INSERT_PREPEND) {
        beforePtr = parentPtr->firstChildPtr;
    }
    LinkBefore(parentPtr, nodePtr, beforePtr);
    nodePtr->depth = parentPtr->depth + 1;
    /* 
     * Issue callbacks to each client indicating that a new node has been
     * created.
     */
    NotifyClients(treePtr, corePtr, nodePtr, TREE_NOTIFY_CREATE);
    return nodePtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_CreateNodeWithId --
 *
 *      Like Blt_Tree_CreateNode, but provides a specific id to use for the
 *      node.  If the tree already contains a node by that id, NULL is
 *      returned.
 *
 *---------------------------------------------------------------------------
 */
Blt_TreeNode
Blt_Tree_CreateNodeWithId(
    Tree *treePtr,
    Node *parentPtr,                    /* Parent node where the new node will
                                         * be inserted. */
    const char *name,                   /* Name of node. */
    long inode,                         /* Requested id of the new node. If a
                                         * node by this id already exists in
                                         * the tree, no node is created. */
    Node *beforePtr)                    /* Node to insert new node before.
                                         * If NULL, new node is insert at
                                         * the end of the list. */
{
    Blt_HashEntry *hPtr;
    Node *nodePtr;                      /* Node to be inserted. */
    TreeObject *corePtr;
    int isNew;

    corePtr = parentPtr->corePtr;
    hPtr = Blt_CreateHashEntry(&corePtr->nodeTable,
                               (const char *)(intptr_t)inode, &isNew);
    if (!isNew) {
        nodePtr = Blt_GetHashValue(hPtr);
        fprintf(stderr, "inode=%ld,%ld (%s) aleady exists\n", inode, 
                nodePtr->parentPtr->inode,  nodePtr->label);
        return NULL;
    }
    nodePtr = NewNode(corePtr, name, inode);
    Blt_SetHashValue(hPtr, nodePtr);

    LinkBefore(parentPtr, nodePtr, beforePtr);
    nodePtr->depth = parentPtr->depth + 1;
    /* 
     * Issue callbacks to each client indicating that a new node has been
     * created.
     */
    NotifyClients(treePtr, corePtr, nodePtr, TREE_NOTIFY_CREATE);
    return nodePtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_MoveNode --
 *
 *      Move an entry into a new location in the hierarchy.
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
int
Blt_Tree_MoveNode(Tree *treePtr, Node *nodePtr, Node *parentPtr, 
                  Node *beforePtr)
{
    TreeObject *corePtr = nodePtr->corePtr;
    long newDepth;

    if (nodePtr == beforePtr) {
        return TCL_ERROR;
    }
    if ((beforePtr != NULL) && (beforePtr->parentPtr != parentPtr)) {
        return TCL_ERROR;
    }
    if (nodePtr->parentPtr == NULL) {
        return TCL_ERROR;               /* Can't move root. */
    }
    /* Verify that the node isn't an ancestor of the new parent. */
    if (Blt_Tree_IsAncestor(nodePtr, parentPtr)) {
        return TCL_ERROR;
    }
    UnlinkNode(nodePtr);

    /* Relink the node as a child of the new parent. */
    LinkBefore(parentPtr, nodePtr, beforePtr);
    newDepth = parentPtr->depth + 1;
    if (nodePtr->depth != newDepth) { 
        /* Recursively reset the depths of all descendant nodes. */
        ResetDepths(nodePtr, newDepth);
    }

    /* 
     * Issue callbacks to each client indicating that a node has been moved.
     */
    NotifyClients(treePtr, corePtr, nodePtr, TREE_NOTIFY_MOVE);
    return TCL_OK;
}

int
Blt_Tree_DeleteNode(Tree *treePtr, Node *nodePtr)
{
    TreeObject *corePtr = nodePtr->corePtr;
    Node *childPtr, *nextPtr;

    /* In depth-first order, delete each descendant node. */
    for (childPtr = nodePtr->firstChildPtr; childPtr != NULL; 
         childPtr = nextPtr) {
        nextPtr = childPtr->nextPtr;
        Blt_Tree_DeleteNode(treePtr, childPtr);
    }
    /* 
     * Issue callbacks to each client indicating that the node can no longer
     * be used.
     */
    NotifyClients(treePtr, corePtr, nodePtr, TREE_NOTIFY_DELETE);

    /* Now remove the actual node. */
    FreeNode(corePtr, nodePtr);
    return TCL_OK;
}

Blt_TreeNode
Blt_Tree_GetNodeFromIndex(Tree *treePtr, long inode)
{
    TreeObject *corePtr = treePtr->corePtr;
    Blt_HashEntry *hPtr;

    hPtr = Blt_FindHashEntry(&corePtr->nodeTable,
                             (const char *)(intptr_t)inode);
    if (hPtr != NULL) {
        return Blt_GetHashValue(hPtr);
    }
    return NULL;
}

long
Blt_Tree_GetNextId(Tree *treePtr)
{
    TreeObject *corePtr = treePtr->corePtr;

    return corePtr->nextInode + 1;
}


Blt_TreeTrace
Blt_Tree_CreateTrace(Tree *treePtr, Node *nodePtr, const char *keyPattern,
                     const char *tagName, unsigned int mask, 
                     Blt_TreeTraceProc *proc, ClientData clientData)
{
    TraceHandler *tracePtr;

    tracePtr = Blt_AssertCalloc(1, sizeof (TraceHandler));
    if (mask & TREE_TRACE_READS) {
        tracePtr->readLink = Blt_Chain_Append(treePtr->readTraces, tracePtr);
    } 
    if (mask & TREE_TRACE_WCU) {
        tracePtr->writeLink = Blt_Chain_Append(treePtr->writeTraces, tracePtr);
    }
    if (keyPattern != NULL) {
        tracePtr->keyPattern = Blt_AssertStrdup(keyPattern);
    }
    if (tagName != NULL) {
        tracePtr->withTag = Blt_AssertStrdup(tagName);
    }
    tracePtr->treePtr = treePtr;
    tracePtr->proc = proc;
    tracePtr->clientData = clientData;
    tracePtr->mask = mask;
    tracePtr->nodePtr = nodePtr;
    tracePtr->interp = treePtr->interp;
    Blt_InitHashTable(&tracePtr->idleTable, sizeof(TraceIdleEvent)/sizeof(int));
    return (Blt_TreeTrace)tracePtr;
}

void
Blt_Tree_DeleteTrace(Blt_TreeTrace trace)
{
    TraceHandler *tracePtr = (TraceHandler *)trace;
    Blt_HashEntry *hPtr;
    Blt_HashSearch iter;
    Tree *treePtr;

    treePtr = tracePtr->treePtr;
    if (tracePtr->readLink) {
        Blt_Chain_DeleteLink(treePtr->readTraces, tracePtr->readLink);
    }
    if (tracePtr->writeLink) {
        Blt_Chain_DeleteLink(treePtr->writeTraces, tracePtr->writeLink);
    }
    /* There may be idle events pending for this trace. Remove the idle
     * callback and free the clientdata memory.  */
    for (hPtr = Blt_FirstHashEntry(&tracePtr->idleTable, &iter);
         hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) {
        TraceIdleEvent *eventPtr;
        
        eventPtr = Blt_GetHashValue(hPtr);
        Tcl_CancelIdleCall(TraceIdleEventProc, eventPtr);
        Blt_Free(eventPtr);
    }
    Blt_DeleteHashTable(&tracePtr->idleTable);
    if (tracePtr->keyPattern != NULL) {
        Blt_Free(tracePtr->keyPattern);
    }
    if (tracePtr->withTag != NULL) {
        Blt_Free(tracePtr->withTag);
    }
    Blt_Free(tracePtr);
}

void
Blt_Tree_RelabelNodeWithoutNotify(Node *nodePtr, const char *string)
{
    Node **firstPtrPtr;
    Node *parentPtr;
    size_t mask;
    unsigned int downshift;

    nodePtr->label = Blt_Tree_GetUidFromNode(nodePtr, string);
    parentPtr = nodePtr->parentPtr;
    if ((parentPtr == NULL) || (parentPtr->nodeTable == NULL)) {
        return;                         /* Root node. */
    }
    mask = (1 << parentPtr->nodeTableSize2) - 1;
    downshift = DOWNSHIFT_START - parentPtr->nodeTableSize2;

    /* Changing the node's name requires that we rehash the node in the
     * parent's table of children. */
    firstPtrPtr = parentPtr->nodeTable + RANDOM_INDEX(nodePtr->label);
    if (*firstPtrPtr == nodePtr) {
        *firstPtrPtr = nodePtr->nextHashPtr;
        if (*firstPtrPtr != NULL) {
            (*firstPtrPtr)->prevHashPtr = NULL;
        }
    } else {
        Node *nextPtr, *prevPtr;
        
        /* Note: Node may not be in the hash table. */
        nextPtr = nodePtr->nextHashPtr;
        prevPtr = nodePtr->prevHashPtr;
        if (prevPtr != NULL) {
            prevPtr->nextHashPtr = nextPtr;
        }
        if (nextPtr != NULL) {
            nextPtr->prevHashPtr = prevPtr;
        }
    }

    firstPtrPtr = parentPtr->nodeTable + RANDOM_INDEX(nodePtr->label);
    /* Prepend the relabeled node to the beginning of the bucket. */
    if (*firstPtrPtr != NULL) {
        (*firstPtrPtr)->prevHashPtr = nodePtr;
    }
    nodePtr->nextHashPtr = *firstPtrPtr;
    nodePtr->prevHashPtr = NULL;
    *firstPtrPtr = nodePtr;
} 

void
Blt_Tree_RelabelNode(Tree *treePtr, Node *nodePtr, const char *string)
{
    Blt_Tree_RelabelNodeWithoutNotify(nodePtr, string);
    NotifyClients(treePtr, treePtr->corePtr, nodePtr, 
        TREE_NOTIFY_RELABEL);
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_FindChild --
 *
 *      Searches for the named node in a parent's chain of siblings.  
 *
 *
 * Results:
 *      If found, the child node is returned, otherwise NULL.
 *
 *---------------------------------------------------------------------------
 */
Blt_TreeNode
Blt_Tree_FindChild(Node *parentPtr, const char *string)
{
    Blt_TreeUid uid;
    
    uid = Blt_Tree_GetUidFromNode(parentPtr, string);
    if (parentPtr->nodeTable != NULL) {
        unsigned int downshift;
        size_t mask;
        Node *bucketPtr;
        Node *nodePtr;

        mask = (1 << parentPtr->nodeTableSize2) - 1;
        downshift = DOWNSHIFT_START - parentPtr->nodeTableSize2;
        bucketPtr = parentPtr->nodeTable[RANDOM_INDEX(uid)];

        /* Search all of the entries in the appropriate bucket. */
        for (nodePtr = bucketPtr; nodePtr != NULL; 
             nodePtr = nodePtr->nextHashPtr) {
            if (uid == nodePtr->label) {
                return nodePtr;
            }
        }
    } else {
        Node *nodePtr;

        for (nodePtr = parentPtr->firstChildPtr; nodePtr != NULL; 
             nodePtr = nodePtr->nextPtr) {
            if (uid == nodePtr->label) {
                return nodePtr;
            }
        }
    }
    return NULL;
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_NodePosition --
 *
 *      Returns the position of the node in its parent's list of children.
 *      The root's position is 0.
 *
 *---------------------------------------------------------------------------
 */
long
Blt_Tree_NodePosition(Node *nodePtr)
{
    Node *parentPtr;
    long count;

    count = 0;
    parentPtr = nodePtr->parentPtr;
    if (parentPtr != NULL) {
        Node *childPtr;

        for (childPtr = parentPtr->firstChildPtr; childPtr != NULL; 
             childPtr = childPtr->nextPtr) {
            if (childPtr == nodePtr) {
                break;
            }
            count++;
        }
    }
    return count;
}

Blt_TreeNode 
Blt_Tree_FirstChild(Node *parentPtr)
{
    return parentPtr->firstChildPtr;
}

Blt_TreeNode 
Blt_Tree_LastChild(Node *parentPtr)
{
    return parentPtr->lastChildPtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_PrevNode --
 *
 *      Returns the "previous" node in the tree.  This node (in depth-first
 *      order) is its parent, if the node has no siblings that are previous
 *      to it.  Otherwise it is the last descendant of the last sibling.
 *      In this case, descend the sibling's hierarchy, using the last child
 *      at any ancestor, with we we find a leaf.
 *
 *---------------------------------------------------------------------------
 */
Blt_TreeNode
Blt_Tree_PrevNode(
    Node *rootPtr,                      /* Root of subtree. If NULL,
                                         * indicates the tree's root. */
    Node *nodePtr)                      /* Current node in subtree. */
{
    Node *prevPtr;

    if (rootPtr == NULL) {
        rootPtr = nodePtr->corePtr->root;
    }
    if (nodePtr == rootPtr) {
        return NULL;                    /* The root is the first node. */
    }
    prevPtr = nodePtr->prevPtr;
    if (prevPtr == NULL) {
        /* There are no siblings previous to this one, so pick the parent. */
        return nodePtr->parentPtr;
    }
    /*
     * Traverse down the right-most thread, in order to select the next
     * entry.  Stop when we reach a leaf.
     */
    nodePtr = prevPtr;
    while ((prevPtr = nodePtr->lastChildPtr) != NULL) {
        nodePtr = prevPtr;
    }
    return nodePtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_NextNode --
 *
 *      Returns the "next" node in relation to the given node.  The next
 *      node (in depth-first order) is either the first child of the given
 *      node the next sibling if the node has no children (the node is a
 *      leaf).  If the given node is the last sibling, then try it's parent
 *      next sibling.  Continue until we either find a next sibling for
 *      some ancestor or we reach the root node.  In this case the current
 *      node is the last node in the tree.
 *
 *---------------------------------------------------------------------------
 */
Blt_TreeNode
Blt_Tree_NextNode(
    Node *rootPtr,                      /* Root of subtree. If NULL,
                                         * indicates the tree's root. */
    Node *nodePtr)                      /* Current node in subtree. */
{
    Node *nextPtr;

    /* If children exist, pick the first child node. */
    nextPtr = nodePtr->firstChildPtr;
    if (nextPtr != NULL) {
        return nextPtr;
    }
    /* 
     * Otherwise, back up until we can find a level where we can pick a
     * "next sibling".  For the last entry we'll thread our way back to the
     * root.
     */
    if (rootPtr == NULL) {
        rootPtr = nodePtr->corePtr->root;
    }
    while (nodePtr != rootPtr) {
        nextPtr = nodePtr->nextPtr;
        if (nextPtr != NULL) {
            return nextPtr;
        }
        nodePtr = nodePtr->parentPtr;
    }
    return NULL;                        /* At root, no next node. */
}


int
Blt_Tree_IsBefore(Node *nodePtr1, Node *nodePtr2)
{
    long depth;
    long i;
    Node *nodePtr;

    if (nodePtr1 == nodePtr2) {
        return FALSE;
    }
    depth = MIN(nodePtr1->depth, nodePtr2->depth);
    if (depth == 0) {                   /* One of the nodes is root. */
        return (nodePtr1->parentPtr == NULL);
    }
    /* 
     * Traverse back from the deepest node, until both nodes are at the
     * same depth.  Check if this ancestor node is the same for both nodes.
     */
    for (i = nodePtr1->depth; i > depth; i--) {
        nodePtr1 = nodePtr1->parentPtr;
    }
    if (nodePtr1 == nodePtr2) {
        return FALSE;
    }
    for (i = nodePtr2->depth; i > depth; i--) {
        nodePtr2 = nodePtr2->parentPtr;
    }
    if (nodePtr2 == nodePtr1) {
        return TRUE;
    }

    /* 
     * First find the mutual ancestor of both nodes.  Look at each
     * preceding ancestor level-by-level for both nodes.  Eventually we'll
     * find a node that's the parent of both ancestors.  Then find the
     * first ancestor in the parent's list of subnodes.
     */
    for (i = depth; i > 0; i--) {
        if (nodePtr1->parentPtr == nodePtr2->parentPtr) {
            break;
        }
        nodePtr1 = nodePtr1->parentPtr;
        nodePtr2 = nodePtr2->parentPtr;
    }
    for (nodePtr = nodePtr1->parentPtr->firstChildPtr; nodePtr != NULL; 
         nodePtr = nodePtr->nextPtr) {
        if (nodePtr == nodePtr1) {
            return TRUE;
        } else if (nodePtr == nodePtr2) {
            return FALSE;
        }
    }
    return FALSE;
}

/*
 *---------------------------------------------------------------------------
 *
 * TraceIdleEventProc --
 *
 *      Used to invoke event handler routines at some idle point.  This
 *      routine is called from the TCL event loop.  Errors generated by the
 *      event handler routines are backgrounded.
 *      
 * Results:
 *      None.
 *
 *---------------------------------------------------------------------------
 */
static void
TraceIdleEventProc(ClientData clientData)
{
    TraceIdleEvent *eventPtr = clientData;
    TraceHandler *tracePtr;
    Node *nodePtr;

    tracePtr = eventPtr->tracePtr;

    /* Get the node from the inode since the node may be been deleted in
     * the time between the idle proc was issued and invoked. */
    nodePtr = Blt_Tree_GetNodeFromIndex(tracePtr->treePtr, eventPtr->inode);
    if (nodePtr != NULL) {
        int result;

        Blt_DeleteHashEntry(&tracePtr->idleTable, eventPtr->hashPtr);
        result = (*tracePtr->proc) (tracePtr->clientData, eventPtr->interp, 
                nodePtr, eventPtr->uid, eventPtr->flags);
        if (result != TCL_OK) {
            Tcl_BackgroundError(eventPtr->interp);
        }
        nodePtr->flags &= ~TREE_TRACE_ACTIVE;
        Blt_Free(eventPtr);
    }
}

static void
CallTraces(
    Tcl_Interp *interp,
    Tree *sourcePtr,                    /* Client holding a reference to
                                         * the tree.  If NULL, indicates to
                                         * execute all handlers, including
                                         * those of the caller. */
    TreeObject *corePtr,                /* Tree that was changed. */
    Node *nodePtr,                      /* Node that received the event. */
    Blt_TreeUid uid,
    unsigned int flags)
{
    Tree *treePtr;

    for (treePtr = FirstClient(corePtr); treePtr != NULL; 
        treePtr = NextClient(treePtr)) {
        Blt_ChainLink link;
        Blt_Chain chain;

        if (flags & TREE_TRACE_READS) {
            chain = treePtr->readTraces;
        } else {
            chain = treePtr->writeTraces;
        }
        for (link = Blt_Chain_FirstLink(chain); link != NULL; 
             link = Blt_Chain_NextLink(link)) {
            TraceHandler *tracePtr;     
            
            tracePtr = Blt_Chain_GetValue(link);
            if ((tracePtr->keyPattern != NULL) && 
                (!Tcl_StringMatch(uid, tracePtr->keyPattern))) {
                continue;               /* Key pattern doesn't match. */
            }
            if ((tracePtr->withTag != NULL) && 
                (!Blt_Tree_HasTag(treePtr, nodePtr, tracePtr->withTag))) {
                continue;               /* Doesn't have the tag. */
            }
            if ((tracePtr->mask & flags) == 0) {
                continue;               /* Flags don't match. */
            }
            if ((treePtr == sourcePtr) && 
                (tracePtr->mask & TREE_TRACE_FOREIGN_ONLY)) {
                continue;               /* This client initiated the trace. */
            }
            if ((tracePtr->nodePtr != NULL) && (tracePtr->nodePtr != nodePtr)) {
                continue;               /* Nodes don't match. */
            }
            if (tracePtr->mask & TREE_TRACE_WHENIDLE) {
                TraceIdleEvent *eventPtr;
                int isNew;
                
                eventPtr = Blt_AssertCalloc(1, sizeof(TraceIdleEvent));
                eventPtr->interp = sourcePtr->interp;
                eventPtr->tracePtr = tracePtr;
                eventPtr->uid = uid;
                eventPtr->flags = flags;
                eventPtr->inode = nodePtr->inode;
                eventPtr->hashPtr = Blt_CreateHashEntry(&tracePtr->idleTable, 
                        (const char *)eventPtr, &isNew);
                if (isNew) {
                    Blt_SetHashValue(eventPtr->hashPtr, eventPtr);
                    Tcl_DoWhenIdle(TraceIdleEventProc, eventPtr);
                }
            } else {
                nodePtr->flags |= TREE_TRACE_ACTIVE;
                if ((*tracePtr->proc) (tracePtr->clientData, sourcePtr->interp, 
                                       nodePtr, uid, flags) != TCL_OK) {
                    if (interp != NULL) {
                        Tcl_BackgroundError(interp);
                    }
                }
                nodePtr->flags &= ~TREE_TRACE_ACTIVE;
            }
        }
    }
}

static Variable *
GetVariable(Tcl_Interp *interp, Tree *treePtr, Node *nodePtr, 
            Blt_TreeUid uid)
{
    Variable *varPtr;

    varPtr = FindVariable(nodePtr, uid); 
    if (varPtr == NULL) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "can't find a variable \"", uid, 
                "\" in tree \"", treePtr->name, "\" at node ", 
                Blt_Tree_NodeIdAscii(nodePtr), (char *)NULL);
        }
        return NULL;
    }   
    if ((varPtr->owner != NULL) && (varPtr->owner != treePtr)) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "can't access private variable \"", uid, 
                "\" in tree \"", treePtr->name, "\"", (char *)NULL);
        }
        return NULL;
    }
    return varPtr;
}

int
Blt_Tree_PrivateVariable(Tcl_Interp *interp, Tree *treePtr, Node *nodePtr,
                         Blt_TreeUid uid)
{
    Variable *varPtr;

    varPtr = FindVariable(nodePtr, uid); 
    if (varPtr == NULL) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "can't find variable \"", uid, "\"", 
                             (char *)NULL);
        }
        return TCL_ERROR;
    }
    varPtr->owner = treePtr;
    return TCL_OK;
}

int
Blt_Tree_PublicVariable(Tcl_Interp *interp, Tree *treePtr, Node *nodePtr,
                        Blt_TreeUid uid)
{
    Variable *varPtr;

    varPtr = FindVariable(nodePtr, uid); 
    if (varPtr == NULL) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "can't find variable \"", uid, "\"", 
                             (char *)NULL);
        }
        return TCL_ERROR;
    }
    if (varPtr->owner != treePtr) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "not the owner of \"", uid, "\"", 
                     (char *)NULL);
        }
        return TCL_ERROR;
    }
    varPtr->owner = NULL;
    return TCL_OK;
}

int
Blt_Tree_ScalarVariableExistsByUid(Tree *treePtr, Node *nodePtr, 
                                   Blt_TreeUid uid)
{
    Variable *varPtr;

    varPtr = GetVariable((Tcl_Interp *)NULL, treePtr, nodePtr, uid);
    if (varPtr == NULL) {
        return FALSE;
    }
    return TRUE;
}

int
Blt_Tree_GetScalarVariableByUid(Tcl_Interp *interp, Tree *treePtr, 
                                Node *nodePtr, Blt_TreeUid uid, 
                                Tcl_Obj **valueObjPtrPtr)
{
    Variable *varPtr;
    TreeObject *corePtr = nodePtr->corePtr;

    varPtr = GetVariable(interp, treePtr, nodePtr, uid);
    if (varPtr == NULL) {
        return TCL_ERROR;
    }
    *valueObjPtrPtr = varPtr->objPtr;
    if (!(nodePtr->flags & TREE_TRACE_ACTIVE)) {
        CallTraces(interp, treePtr, corePtr, nodePtr, uid, 
                   TREE_TRACE_READS);
    }
    return TCL_OK;
}

int
Blt_Tree_SetScalarVariableByUid(Tcl_Interp *interp, Tree *treePtr,
    Node *nodePtr,                      /* Node to be updated. */
    Blt_TreeUid uid,                    /* Identifies the variable key. */
    Tcl_Obj *valueObjPtr)               /* New value. */
{
    TreeObject *corePtr = nodePtr->corePtr;
    Variable *varPtr;
    int isNew;
    unsigned int flags;

    if (valueObjPtr == NULL) {
        return Blt_Tree_UnsetScalarVariableByUid(interp, treePtr, nodePtr, uid);
    }
    varPtr = CreateVariable(nodePtr, uid, &isNew);
    if ((varPtr->owner != NULL) && (varPtr->owner != treePtr)) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "can't set private variable \"", 
                             uid, "\"", (char *)NULL);
        }
        return TCL_ERROR;
    }
    /* Increment the new variable, before decrementing the old. */
    if (valueObjPtr != NULL) {
        Tcl_IncrRefCount(valueObjPtr);
    }
    if (varPtr->objPtr != NULL) {
        Tcl_DecrRefCount(varPtr->objPtr);
    }
    varPtr->objPtr = valueObjPtr;
    flags = TREE_TRACE_WRITES;
    if (isNew) {
        flags |= TREE_TRACE_CREATES;
    }
    if (!(nodePtr->flags & TREE_TRACE_ACTIVE)) {
        CallTraces(interp, treePtr, corePtr, nodePtr, varPtr->uid, flags);
    }
    return TCL_OK;
}

int
Blt_Tree_UnsetScalarVariableByUid(Tcl_Interp *interp, Tree *treePtr,
    Node *nodePtr,                      /* Node to be updated. */
    Blt_TreeUid uid)                    /* Name of variable in node. */
{
    TreeObject *corePtr = nodePtr->corePtr;
    Variable *varPtr;

    varPtr = FindVariable(nodePtr, uid);
    if (varPtr == NULL) {
        return TCL_OK;                  /* It's okay to unset variables
                                         * that don't exist in the node. */
    }
    if ((varPtr->owner != NULL) && (varPtr->owner != treePtr)) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "can't unset private variable \"", 
                             uid, "\"", (char *)NULL);
        }
        return TCL_ERROR;
    }
    DeleteVariable(nodePtr, varPtr);
    CallTraces(interp, treePtr, corePtr, nodePtr, uid, TREE_TRACE_UNSETS);
    return TCL_OK;
}

int
Blt_Tree_AppendScalarVariableByUid(Tcl_Interp *interp, Tree *treePtr,
    Node *nodePtr,                      /* Node to be updated. */
    Blt_TreeUid uid,                    /* Identifies the variable key. */
    Tcl_Obj *valueObjPtr)               /* New variable. */
{
    TreeObject *corePtr = nodePtr->corePtr;
    Variable *varPtr;
    int isNew;
    unsigned int flags;

    varPtr = CreateVariable(nodePtr, uid, &isNew);
    if ((varPtr->owner != NULL) && (varPtr->owner != treePtr)) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "can't set private variable \"", 
                             uid, "\"", (char *)NULL);
        }
        return TCL_ERROR;
    }
    if ((isNew) || (varPtr->objPtr == NULL)) {
        if (valueObjPtr != NULL) {
            Tcl_IncrRefCount(valueObjPtr);
        }
        varPtr->objPtr = valueObjPtr;
    } else {
        if (Tcl_IsShared(varPtr->objPtr)) {
            Tcl_DecrRefCount(varPtr->objPtr);
            varPtr->objPtr = Tcl_DuplicateObj(varPtr->objPtr);
            Tcl_IncrRefCount(varPtr->objPtr);
        } 
        if (valueObjPtr != NULL) {
            Tcl_AppendObjToObj(varPtr->objPtr, valueObjPtr);
        }
    }
    flags = TREE_TRACE_WRITES;
    if (isNew) {
        flags |= TREE_TRACE_CREATES;
    }
    if (!(nodePtr->flags & TREE_TRACE_ACTIVE)) {
        CallTraces(interp, treePtr, corePtr, nodePtr, varPtr->uid, flags);
    }
    return TCL_OK;
}

int
Blt_Tree_ListAppendScalarVariableByUid(Tcl_Interp *interp, Tree *treePtr,
    Node *nodePtr,                      /* Node to be updated. */
    Blt_TreeUid uid,                    /* Identifies the variable key. */
    Tcl_Obj *valueObjPtr)               /* Variable to be appended. */
{
    TreeObject *corePtr = nodePtr->corePtr;
    Variable *varPtr;
    int isNew;
    unsigned int flags;

    varPtr = CreateVariable(nodePtr, uid, &isNew);
    if ((varPtr->owner != NULL) && (varPtr->owner != treePtr)) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "can't set private variable \"", 
                             uid, "\"", (char *)NULL);
        }
        return TCL_ERROR;
    }
    flags = TREE_TRACE_WRITES;
    if ((isNew) || (varPtr->objPtr == NULL)) {
        varPtr->objPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
        Tcl_IncrRefCount(varPtr->objPtr);
        flags |= TREE_TRACE_CREATES;
    } else if (Tcl_IsShared(varPtr->objPtr)) {
        Tcl_Obj *newObjPtr;

        newObjPtr = Tcl_DuplicateObj(varPtr->objPtr);
        Tcl_DecrRefCount(varPtr->objPtr);
        Tcl_IncrRefCount(newObjPtr);
        varPtr->objPtr = newObjPtr;
    }
    if (valueObjPtr != NULL) {
        Tcl_ListObjAppendElement(interp, varPtr->objPtr, valueObjPtr);
    }
    if (!(nodePtr->flags & TREE_TRACE_ACTIVE)) {
        CallTraces(interp, treePtr, corePtr, nodePtr, varPtr->uid, flags);
    }
    return TCL_OK;
}

int
Blt_Tree_ListReplaceScalarVariableByUid(Tcl_Interp *interp, Tree *treePtr,
    Node *nodePtr,                      /* Node to be updated. */
    Blt_TreeUid uid,                    /* Identifies the variable key. */
    int firstIndex, int lastIndex,
    int objc,
    Tcl_Obj *const *objv)               /* List elements to be replaced. */
{
    TreeObject *corePtr = nodePtr->corePtr;
    Variable *varPtr;
    int isNew;
    unsigned int flags;
    int length, count;

    varPtr = CreateVariable(nodePtr, uid, &isNew);
    if ((varPtr->owner != NULL) && (varPtr->owner != treePtr)) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "can't set private variable \"", 
                             uid, "\"", (char *)NULL);
        }
        return TCL_ERROR;
    }
    flags = TREE_TRACE_WRITES;
    if ((isNew) || (varPtr->objPtr == NULL)) {
        varPtr->objPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
        Tcl_IncrRefCount(varPtr->objPtr);
        flags |= TREE_TRACE_CREATES;
    } else if (Tcl_IsShared(varPtr->objPtr)) {
        Tcl_DecrRefCount(varPtr->objPtr);
        varPtr->objPtr = Tcl_DuplicateObj(varPtr->objPtr);
        Tcl_IncrRefCount(varPtr->objPtr);
    }

    if (Tcl_ListObjLength(interp, varPtr->objPtr, &length) != TCL_OK) {
        return TCL_ERROR;
    }
    count = 0;
    if ((firstIndex == -1) && (length > 0)) {
        firstIndex += length;
    }
    if ((lastIndex == -1) && (length > 0))  {
        lastIndex += length;
    }
    if ((firstIndex >= 0) && (lastIndex >= 0) && (lastIndex >= firstIndex)) {
        count = lastIndex - firstIndex + 1;
    }
    if (Tcl_ListObjReplace(interp, varPtr->objPtr, firstIndex, count,
        objc, objv) != TCL_OK) {
        return TCL_ERROR;
    }
    if (!(nodePtr->flags & TREE_TRACE_ACTIVE)) {
        CallTraces(interp, treePtr, corePtr, nodePtr, varPtr->uid, flags);
    }
    return TCL_OK;
}

static int
ParseParentheses(Tcl_Interp *interp, const char *string, char **leftPtr, 
                 char **rightPtr)
{
    char *p;
    char *left, *right;

    left = right = NULL;
    *leftPtr = *rightPtr = NULL;
    for (p = (char *)string; *p != '\0'; p++) {
        if (*p == ' ') {
            return TCL_OK;
        }
        if (*p == '(') {
            left = p;
        } else if (*p == ')') {
            right = p;
        }
    }
    if (left != right) {
        if (((left != NULL) && (right == NULL)) ||
            ((left == NULL) && (right != NULL)) ||
            (left > right) || (right != (p - 1))) {
            if (interp != NULL) {
                Tcl_AppendResult(interp, "bad array specification \"", string,
                             "\"", (char *)NULL);
            }
            return TCL_ERROR;
        }
    }
    *leftPtr = left;
    *rightPtr = right;
    return TCL_OK;
}

static Tree *
NewTree(TreeInterpData *dataPtr, TreeObject *corePtr, const char *qualName)
{
    Tree *treePtr;
    int isNew;

    treePtr = Blt_Calloc(1, sizeof(Tree));
    if (treePtr == NULL) {
        return NULL;
    }

    treePtr->magic = TREE_MAGIC;
    treePtr->interp = dataPtr->interp;
    /* Add client to table object's list of clients. */
    treePtr->link = Blt_Chain_Append(corePtr->clients, treePtr);
    treePtr->corePtr = corePtr;
    treePtr->root = corePtr->root;

    /* By default, use own sets of tags. */
    Blt_Tree_NewTagTable(treePtr);

    treePtr->hPtr = Blt_CreateHashEntry(&dataPtr->treeTable, qualName, &isNew);
    assert(isNew);
    Blt_SetHashValue(treePtr->hPtr, treePtr);
    treePtr->name = Blt_GetHashKey(&dataPtr->treeTable, treePtr->hPtr);
    treePtr->events = Blt_Chain_Create();
    treePtr->readTraces = Blt_Chain_Create();
    treePtr->writeTraces = Blt_Chain_Create();
    return treePtr;
}

static const char *
MakeTreeName(TreeInterpData *dataPtr, char *string)
{
    /* Generate a unique tree name in the current namespace. */
    do  {
        Blt_FmtString(string, 200, "tree%d", dataPtr->nextId++);
    } while (GetTree(dataPtr, string, NS_SEARCH_CURRENT) != NULL);
    return string;
} 

/*
 *---------------------------------------------------------------------------
 *
 * ShareTagTable --
 *
 *---------------------------------------------------------------------------
 */
static void
ShareTagTable(Tree *sourcePtr, Tree *targetPtr)
{
    sourcePtr->tagTablePtr->refCount++;
    if (targetPtr->tagTablePtr != NULL) {
        ReleaseTagTable(targetPtr->tagTablePtr);
    }
    targetPtr->tagTablePtr = sourcePtr->tagTablePtr;
}

int
Blt_Tree_GetVariable(Tcl_Interp *interp, Tree *treePtr, Node *nodePtr,
                     const char *varName, Tcl_Obj **valueObjPtrPtr)
{
    char *left, *right;
    int result;

    if (ParseParentheses(interp, varName, &left, &right) != TCL_OK) {
        return TCL_ERROR;
    }
    if (left != NULL) {
        *left = *right = '\0';
        result = Blt_Tree_GetArrayVariable(interp, treePtr, nodePtr, varName, 
                left + 1, valueObjPtrPtr);
        *left = '(', *right = ')';
    } else {
        result = Blt_Tree_GetScalarVariableByUid(interp, treePtr, nodePtr, 
                Blt_Tree_GetUid(treePtr, varName), valueObjPtrPtr);
    }
    return result;
}

int
Blt_Tree_SetVariable(Tcl_Interp *interp, Tree *treePtr, Node *nodePtr,
                     const char *varName, Tcl_Obj *valueObjPtr)
{
    char *left, *right;
    int result;

    if (ParseParentheses(interp, varName, &left, &right) != TCL_OK) {
        return TCL_ERROR;
    }
    if (left != NULL) {
        *left = *right = '\0';
        result = Blt_Tree_SetArrayVariable(interp, treePtr, nodePtr, varName, 
                left + 1, valueObjPtr);
        *left = '(', *right = ')';
    } else {
        result = Blt_Tree_SetScalarVariableByUid(interp, treePtr, nodePtr, 
                Blt_Tree_GetUid(treePtr, varName), valueObjPtr);
    }
    return result;
}

int
Blt_Tree_UnsetVariable(Tcl_Interp *interp, Tree *treePtr, Node *nodePtr, 
                       const char *varName)
{
    char *left, *right;
    int result;

    if (ParseParentheses(interp, varName, &left, &right) != TCL_OK) {
        return TCL_ERROR;
    }
    if (left != NULL) {
        *left = *right = '\0';
        result = Blt_Tree_UnsetArrayVariable(interp, treePtr, nodePtr, varName, 
                left + 1);
        *left = '(', *right = ')';
    } else {
        result = Blt_Tree_UnsetScalarVariableByUid(interp, treePtr, nodePtr, 
                Blt_Tree_GetUid(treePtr, varName));
    }
    return result;
}

int
Blt_Tree_AppendVariable(Tcl_Interp *interp, Tree *treePtr, Node *nodePtr,
                        const char *varName, Tcl_Obj *valueObjPtr)      
{
    char *left, *right;
    int result;

    if (ParseParentheses(interp, varName, &left, &right) != TCL_OK) {
        return TCL_ERROR;
    }
    if (left != NULL) {
        *left = *right = '\0';
        result = Blt_Tree_AppendArrayVariable(interp, treePtr, nodePtr,
                varName, left + 1, valueObjPtr);
        *left = '(', *right = ')';
    } else {
        result = Blt_Tree_AppendScalarVariableByUid(interp, treePtr, nodePtr, 
                        Blt_Tree_GetUid(treePtr, varName), valueObjPtr);
    }
    return result;
}

int
Blt_Tree_ListAppendVariable(Tcl_Interp *interp, Tree *treePtr, Node *nodePtr, 
                            const char *varName, Tcl_Obj *valueObjPtr)
{
    char *left, *right;
    int result;

    if (ParseParentheses(interp, varName, &left, &right) != TCL_OK) {
        return TCL_ERROR;
    }
    if (left != NULL) {
        *left = *right = '\0';
        result = Blt_Tree_ListAppendArrayVariable(interp, treePtr, nodePtr,
                varName, left + 1, valueObjPtr);
        *left = '(', *right = ')';
    } else {
        result = Blt_Tree_ListAppendScalarVariableByUid(interp, treePtr, 
                nodePtr, Blt_Tree_GetUid(treePtr, varName), valueObjPtr);
    }
    return result;
}

int
Blt_Tree_ListReplaceVariable(Tcl_Interp *interp, Tree *treePtr, Node *nodePtr, 
                             const char *varName, int firstIndex, 
                             int lastIndex, int objc, Tcl_Obj *const *objv)
{
    char *left, *right;
    int result;

    if (ParseParentheses(interp, varName, &left, &right) != TCL_OK) {
        return TCL_ERROR;
    }
    if (left != NULL) {
        *left = *right = '\0';
        result = Blt_Tree_ListReplaceArrayVariable(interp, treePtr, nodePtr,
            varName, left + 1, firstIndex, lastIndex, objc, objv);
        *left = '(', *right = ')';
    } else {
        result = Blt_Tree_ListReplaceScalarVariableByUid(interp, treePtr,
            nodePtr, Blt_Tree_GetUid(treePtr, varName), firstIndex, lastIndex,
            objc, objv);
    }
    return result;
}

int
Blt_Tree_VariableExists(Tree *treePtr, Node *nodePtr, const char *varName)
{
    char *left, *right;
    int result;

    if (ParseParentheses(NULL, varName, &left, &right) != TCL_OK) {
        return FALSE;
    }
    if (left != NULL) {
        *left = *right = '\0';
        result = Blt_Tree_ArrayVariableExists(treePtr, nodePtr, varName, 
                                              left + 1);
        *left = '(', *right = ')';
    } else {
        result = Blt_Tree_ScalarVariableExistsByUid(treePtr, nodePtr, 
                Blt_Tree_GetUid(treePtr, varName));
    }
    return result;
}

Blt_TreeUid
Blt_Tree_FirstVariable(Tree *treePtr, Node *nodePtr, 
                       Blt_TreeVariableIterator *iterPtr)
{
    Variable *varPtr;
    
    varPtr = FirstVariable(nodePtr, iterPtr);
    if (varPtr == NULL) {
        return NULL;
    }
    while ((varPtr->owner != NULL) && (varPtr->owner != treePtr)) {
        varPtr = NextVariable(iterPtr);
        if (varPtr == NULL) {
            return NULL;
        }
    }
    return varPtr->uid;
}

Blt_TreeUid
Blt_Tree_NextVariable(Tree *treePtr, Blt_TreeVariableIterator *iterPtr)
{
    Variable *varPtr;

    varPtr = NextVariable(iterPtr);
    if (varPtr == NULL) {
        return NULL;
    }
    while ((varPtr->owner != NULL) && (varPtr->owner != treePtr)) {
        varPtr = NextVariable(iterPtr);
        if (varPtr == NULL) {
            return NULL;
        }
    }
    return varPtr->uid;
}

int
Blt_Tree_IsAncestor(Node *nodePtr1, Node *nodePtr2)
{
    if (nodePtr2 != NULL) {
        nodePtr2 = nodePtr2->parentPtr;
        while (nodePtr2 != NULL) {
            if (nodePtr2 == nodePtr1) {
                return TRUE;
            }
            nodePtr2 = nodePtr2->parentPtr;
        }
    }
    return FALSE;
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_SortNode --
 *
 *      Sorts the subnodes at a given node.
 *
 * Results:
 *      Always returns TCL_OK.
 *
 *---------------------------------------------------------------------------
 */
int
Blt_Tree_SortNode(Tree *treePtr, Node *parentPtr, 
                  Blt_TreeCompareNodesProc *proc)
{
    Node **nodeArr, *childPtr;
    long numNodes, i;

    numNodes = parentPtr->numChildren;
    if (numNodes < 2) {
        return TCL_OK;
    }
    nodeArr = Blt_Malloc(numNodes * sizeof(Node *));
    if (nodeArr == NULL) {
        Tcl_AppendResult(treePtr->interp, "can't allocate sorting array", 
        (char *)NULL);
        return TCL_ERROR;               /* Out of memory. */
    }
    for (i = 0, childPtr = parentPtr->firstChildPtr; childPtr != NULL; 
         childPtr = childPtr->nextPtr, i++) {
        nodeArr[i] = childPtr;
    }
    qsort(nodeArr, numNodes, sizeof(Node *), (QSortCompareProc *)proc);
    ReorderNodes(parentPtr, numNodes, nodeArr);
    Blt_Free(nodeArr);
    NotifyClients(treePtr, parentPtr->corePtr, parentPtr, TREE_NOTIFY_SORT);
    return TCL_OK;
}

#define TEST_RESULT(result) \
        switch (result) { \
        case TCL_CONTINUE: \
            return TCL_OK; \
        case TCL_OK: \
            break; \
        default: \
            return (result); \
        }

int
Blt_Tree_Apply(Node *rootPtr, Blt_TreeApplyProc *proc, ClientData clientData)
{
    Node *nodePtr, *nextPtr;

    for (nodePtr = rootPtr->firstChildPtr; nodePtr != NULL; 
         nodePtr = nextPtr) {
        int result;

        /* 
         * Get the next link in the chain before calling Blt_TreeApply
         * recursively.  This is because the apply callback may delete the
         * node and its link.
         */
        nextPtr = nodePtr->nextPtr;

        result = Blt_Tree_Apply(nodePtr, proc, clientData);
        TEST_RESULT(result);
    }
    return (*proc) (rootPtr, clientData, TREE_POSTORDER);
}

int
Blt_Tree_ApplyDFS(
    Node *rootPtr,                   /* Root node of subtree. */
    Blt_TreeApplyProc *proc,         /* Procedure to call for each node. */
    ClientData clientData,           /* One-word of data passed when
                                      * calling proc. */
    int order)                       /* Order of traversal. */
{
    Node *nodePtr, *nextPtr;
    int result;

    if (order & TREE_PREORDER) {
        result = (*proc) (rootPtr, clientData, TREE_PREORDER);
        TEST_RESULT(result);
    }
    nodePtr = rootPtr->firstChildPtr;
    if (order & TREE_INORDER) {
        if (nodePtr != NULL) {
            result = Blt_Tree_ApplyDFS(nodePtr, proc, clientData, order);
            TEST_RESULT(result);
            nodePtr = nodePtr->nextPtr;
        }
        result = (*proc) (rootPtr, clientData, TREE_INORDER);
        TEST_RESULT(result);
    }
    for (/* empty */; nodePtr != NULL; nodePtr = nextPtr) {
        /* 
         * Get the next link in the chain before calling Blt_Tree_Apply
         * recursively.  This is because the apply callback may delete the
         * node and its link.
         */
        nextPtr = nodePtr->nextPtr;
        result = Blt_Tree_ApplyDFS(nodePtr, proc, clientData, order);
        TEST_RESULT(result);
    }
    if (order & TREE_POSTORDER) {
        return (*proc) (rootPtr, clientData, TREE_POSTORDER);
    }
    return TCL_OK;
}

int
Blt_Tree_ApplyBFS(
    Node *rootPtr,                    /* Root node of subtree. */
    Blt_TreeApplyProc *proc,            /* Procedure to call for each node. */
    ClientData clientData)              /* One-word of data passed when
                                         * calling proc. */
{
    Blt_Chain queue;
    Blt_ChainLink link, next;

    queue = Blt_Chain_Create();
    link = Blt_Chain_Append(queue, rootPtr);
    while (link != NULL) {
        Node *parentPtr, *childPtr;
        int result;

        parentPtr = Blt_Chain_GetValue(link);
        /* Add the children to the queue. */
        for (childPtr = parentPtr->firstChildPtr; childPtr != NULL; 
             childPtr = childPtr->nextPtr) {
            Blt_Chain_Append(queue, childPtr);
        }
        /* Process the node. */
        result = (*proc) (parentPtr, clientData, TREE_BREADTHFIRST);
        switch (result) { 
        case TCL_CONTINUE: 
            Blt_Chain_Destroy(queue);
            return TCL_OK; 
        case TCL_OK: 
            break; 
        default: 
            Blt_Chain_Destroy(queue);
            return result; 
        }
        /* Remove the node from the queue. */
        next = Blt_Chain_NextLink(link);
        Blt_Chain_DeleteLink(queue, link);
        link = next;
    }
    Blt_Chain_Destroy(queue);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_Attach --
 *
 *      Attaches the tree object of the named tree to the current one.
 *      This lets a tree client change its core tree object.  If the name
 *      of the tree (used to get the tree object) is the empty string (""),
 *      then create a new tree object.
 *
 * Results:
 *      Returns a standard TCL result.  If an error occurs, TCL_ERROR is
 *      returned and an error message if left in the interpreter result.
 *
 * Side Effects:
 *      The tree's traces and notifiers are deactivated and removed.  The
 *      tree's old core tree object is released.  This may destroy the old
 *      tree object is no client is still using it.  The tag table is also
 *      reset or shared with the named client.
 *
 *---------------------------------------------------------------------------
 */
int
Blt_Tree_Attach(Tcl_Interp *interp, Tree *treePtr, const char *name)
{
    TreeObject *corePtr;
    Blt_ChainLink link;
    TreeInterpData *dataPtr;

    dataPtr = treePtr->corePtr->dataPtr;
    if ((name == NULL) || (name[0] == '\0')) {
        /* Create a new tree object. */
        corePtr = NewTreeObject(dataPtr);
        if (corePtr == NULL) {
            Tcl_AppendResult(interp, "can't allocate a new tree object", 
                (char *)NULL);
            return TCL_ERROR;
        }
    } else {
        Tree *newPtr;

        newPtr = GetTree(dataPtr, name, NS_SEARCH_BOTH);
        if ((newPtr == NULL) || (newPtr->corePtr == NULL)) {
            Tcl_AppendResult(interp, "can't find a tree named \"", name, "\"", 
                (char *)NULL);
            return TCL_ERROR;
        }
        corePtr = newPtr->corePtr;
        ShareTagTable(newPtr, treePtr);
    }
    /* 
     * Be sure to add the client to new tree object, before releasing the
     * old tree object. This is so that reattaching to the same tree object
     * doesn't delete the tree object.
     */
    link = Blt_Chain_Append(corePtr->clients, treePtr);
    ReleaseTreeObject(treePtr);
    ResetTree(treePtr);                 /* Reset the client's traces and
                                         * notifiers. */
    /* Update pointers in the client. */
    treePtr->link = link;
    treePtr->corePtr = corePtr;
    treePtr->root = corePtr->root;
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_Open --
 *
 *      Creates a tree using an existing or new tree object.  The returned
 *      tree must be freed by the caller using Blt_Tree_Close.  If the name
 *      of the tree (used to get the tree object) is the empty string (""),
 *      then create a new tree object.  The following flags may be used:
 *
 *      TREE_CREATE     Create a tree named "name". A new tree object is 
 *                      automatically created. If the "name" is NULL, then 
 *                      automatically generate a new name.
 *      
 *      TREE_NEWTAGS    By default the tag table is shared.  This flag 
 *                      indicates to not share tags and to start with an 
 *                      empty tag table.
 *
 * Results:
 *      Returns a standard TCL result.  If an error occurs, TCL_ERROR
 *      is returned and an error message if left in the interpreter result.
 *
 * Side Effects:
 *      The tree's traces and notifiers are deactivated and removed.  The
 *      tree's old core tree object is release.  This may destroy the tree
 *      object is no client is still using it.  The tag table is also reset
 *      or shared with the named client.
 *
 *---------------------------------------------------------------------------
 */
Blt_Tree
Blt_Tree_Open(
    Tcl_Interp *interp,                 /* Interpreter to report errors back
                                         * to. */
    const char *name,                   /* Name of tree in namespace. */
    int flags)                          /* TREE_CREATE|TREE_NEWTAGS */
{
    Blt_ObjectName objName;
    Tree *srcPtr, *destPtr;
    TreeObject *corePtr;
    Tcl_DString ds;
    TreeInterpData *dataPtr;
    const char *qualName;
    char string[200];

    dataPtr = Blt_Tree_GetInterpData(interp);
    srcPtr = NULL;
    /* If there's a name available, see it's a tree. */
    if (name != NULL) {
        srcPtr = GetTree(dataPtr, name, NS_SEARCH_BOTH);
    } else if ((flags & TREE_CREATE) == 0) {
        Tcl_AppendResult(interp, "no tree name given to attach", (char *)NULL);
        return NULL;
    }   
    /* Either create a core tree object or use the one from the named tree.*/
    if (flags & TREE_CREATE) {
        if (srcPtr != NULL) {
            Tcl_AppendResult(interp, "tree \"", name, "\" already exists", 
                             (char *)NULL);
            return NULL;
        }
        corePtr = NewTreeObject(dataPtr);
        if (corePtr == NULL) {
            Tcl_AppendResult(interp, "can't allocate tree object", 
                             (char *)NULL);
            return NULL;
        }
    } else {
        if ((srcPtr == NULL) || (srcPtr->corePtr == NULL)) {    
            Tcl_AppendResult(interp, "can't find a tree named \"", name, "\"", 
                             (char *)NULL);
            return NULL;
        }
        corePtr = srcPtr->corePtr;
    }    
    /* Generate a new tree name if one wasn't already provided. */
    if (name == NULL) {
        name = MakeTreeName(dataPtr, string);
    }
    /* 
     * Tear apart and put back together the namespace-qualified name of the
     * tree. This is to ensure that naming is consistent.
     */ 
    if (!Blt_ParseObjectName(interp, name, &objName, 0)) {
        return NULL;
    }
    qualName = Blt_MakeQualifiedName(&objName, &ds);
    destPtr = NewTree(dataPtr, corePtr, qualName);
    Tcl_DStringFree(&ds);
    if (destPtr == NULL) {
        Tcl_AppendResult(interp, "can't allocate tree token", (char *)NULL);
        return NULL;
    }
    if (((flags & TREE_NEWTAGS) == 0) && (srcPtr != NULL)) {
        ShareTagTable(srcPtr, destPtr);
    }
    return destPtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_GetFromObj --
 *
 *      Returns a pointer to the tree object.  This should be used
 *      carefully in special circumstances when you want to manipulate a
 *      specific tree without the overhead of attaching to it.
 *
 * Results:
 *      Returns a standard TCL result.  If an error occurs, TCL_ERROR
 *      is returned and an error message if left in the interpreter result.
 *
 *---------------------------------------------------------------------------
 */
Blt_Tree
Blt_Tree_GetFromObj(
    Tcl_Interp *interp,                 /* Interpreter to report errors back
                                         * to. */
    Tcl_Obj *objPtr)                    /* Name of tree in namespace. */
{
    Tree *srcPtr;
    TreeInterpData *dataPtr;
    const char *name;

    dataPtr = Blt_Tree_GetInterpData(interp);
    name = Tcl_GetString(objPtr);
    /* If there's a name available, see it's a tree. */
    srcPtr = GetTree(dataPtr, name, NS_SEARCH_BOTH);
    if ((srcPtr == NULL) || (srcPtr->corePtr == NULL)) {        
        Tcl_AppendResult(interp, "can't find a tree named \"", name, "\"", 
                         (char *)NULL);
        return NULL;
    }
    return srcPtr;
}

void
Blt_Tree_Close(Tree *treePtr)
{
    if (treePtr->magic != TREE_MAGIC) {
        Blt_Warn("invalid tree object token 0x%llx\n", (size_t)treePtr);
        return;
    }
    DestroyTree(treePtr);
}

int
Blt_Tree_Exists(Tcl_Interp *interp, const char *name)
{
    TreeInterpData *dataPtr;

    dataPtr = Blt_Tree_GetInterpData(interp);
    return (GetTree(dataPtr, name, NS_SEARCH_BOTH) != NULL);
}

/*ARGSUSED*/
static int
SizeApplyProc(Node *nodePtr, ClientData clientData, int order)
{
    int *sumPtr = clientData;
    *sumPtr = *sumPtr + 1;
    return TCL_OK;
}

int
Blt_Tree_Size(Node *nodePtr)
{
    int sum;

    sum = 0;
    Blt_Tree_Apply(nodePtr, SizeApplyProc, &sum);
    return sum;
}


void
Blt_Tree_CreateEventHandler(Tree *treePtr, unsigned int mask, 
                            Blt_TreeNotifyEventProc *proc, 
                            ClientData clientData)
{
    Blt_ChainLink link;
    NotifyEventHandler *notifyPtr;

    notifyPtr = NULL;                   /* Suppress compiler warning. */

    /* Check if the event is already handled. */
    for (link = Blt_Chain_FirstLink(treePtr->events); 
        link != NULL; link = Blt_Chain_NextLink(link)) {
        notifyPtr = Blt_Chain_GetValue(link);
        if ((notifyPtr->proc == proc) && 
            (notifyPtr->mask == mask) &&
            (notifyPtr->clientData == clientData)) {
            break;
        }
    }
    if (link == NULL) {
        notifyPtr = Blt_AssertMalloc(sizeof (NotifyEventHandler));
        link = Blt_Chain_Append(treePtr->events, notifyPtr);
    }
    if (proc == NULL) {
        Blt_Chain_DeleteLink(treePtr->events, link);
        Blt_Free(notifyPtr);
    } else {
        notifyPtr->proc = proc;
        notifyPtr->clientData = clientData;
        notifyPtr->mask = mask;
        notifyPtr->notifyPending = FALSE;
        notifyPtr->interp = treePtr->interp;
    }
}

void
Blt_Tree_DeleteEventHandler(Tree *treePtr, unsigned int mask, 
                            Blt_TreeNotifyEventProc *proc, 
                            ClientData clientData)
{
    Blt_ChainLink link;

    for (link = Blt_Chain_FirstLink(treePtr->events); link != NULL; 
        link = Blt_Chain_NextLink(link)) {
        NotifyEventHandler *notifyPtr;

        notifyPtr = Blt_Chain_GetValue(link);
        if ((notifyPtr->proc == proc) && (notifyPtr->mask == mask) &&
            (notifyPtr->clientData == clientData)) {
            if (notifyPtr->notifyPending) {
                Tcl_CancelIdleCall(NotifyIdleEventProc, notifyPtr);
            }
            Blt_Chain_DeleteLink(treePtr->events, link);
            Blt_Free(notifyPtr);
            return;
        }
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_GetPathSeparator --
 *
 *---------------------------------------------------------------------------
 */
Tcl_Obj *
Blt_Tree_GetPathSeparator(Tree *treePtr)
{
    if (treePtr->corePtr->sepObjPtr != NULL) {
        Tcl_IncrRefCount(treePtr->corePtr->sepObjPtr);
    }
    return treePtr->corePtr->sepObjPtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_SetPathSeparator --
 *
 *---------------------------------------------------------------------------
 */
void
Blt_Tree_SetPathSeparator(Tree *treePtr, Tcl_Obj *sepObjPtr)
{
    if (sepObjPtr != NULL) {
        Tcl_IncrRefCount(sepObjPtr);
    }
    if (treePtr->corePtr->sepObjPtr != NULL) {
        Tcl_DecrRefCount(treePtr->corePtr->sepObjPtr);
        treePtr->corePtr->sepObjPtr = sepObjPtr;
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_PathFromNode --
 *
 *---------------------------------------------------------------------------
 */
const char *
Blt_Tree_NodeRelativePath(
    Node *rootPtr,                      /* Root of subtree. */
    Node *nodePtr,                      /* Node whose path is to be
                                         * returned. */
    Tcl_Obj *sepObjPtr,                 /* If non-NULL is character string
                                         * to separator elements. */
    unsigned int flags,                 /* Indicates how to print the path. */
    Tcl_Obj *resultPtr)                 /* (out) Contains the path of the
                                         * node. */
{
    const char **names;                 /* Used to stack the component
                                         * names. */
    const char *staticSpace[64];
    long i;
    long numLevels;

    Tcl_SetObjLength(resultPtr, 0);
    if (rootPtr == NULL) {
        rootPtr = nodePtr->corePtr->root;
    }
    numLevels = Blt_Tree_NodeDepth(nodePtr) - Blt_Tree_NodeDepth(rootPtr);
    if (flags & TREE_INCLUDE_ROOT) {
        numLevels++;
    }
    if (numLevels > 64) {
        names = Blt_AssertMalloc(numLevels * sizeof(const char *));
    } else {
        names = staticSpace;
    }
    for (i = numLevels; i > 0; i--) {
        /* Save the name of each ancestor in the name array.  Note that we
         * ignore the root. */
        names[i - 1] = nodePtr->label;
        nodePtr = nodePtr->parentPtr;
    }
    /* Append each the names in the array. */
    if ((numLevels > 0) && (sepObjPtr != NULL)) {
        const char *separator;

        separator = Tcl_GetString(sepObjPtr);
        Tcl_AppendToObj(resultPtr, names[0], -1);
        for (i = 1; i < numLevels; i++) {
            Tcl_AppendToObj(resultPtr, separator, -1);
            Tcl_AppendToObj(resultPtr, names[i], -1);
        }
    } else {
        for (i = 0; i < numLevels; i++) {
            Tcl_Obj *objPtr;

            objPtr = Tcl_NewStringObj(names[i], -1);
            Tcl_ListObjAppendElement(NULL, resultPtr, objPtr);
        }
    }
    if (names != staticSpace) {
        Blt_Free(names);
    }
    return Tcl_GetString(resultPtr);
}
    
/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_NodePathObj --
 *
 *---------------------------------------------------------------------------
 */
Tcl_Obj *
Blt_Tree_NodePathObj(Node *nodePtr, Blt_TreePathOptions *pathPtr)
{
    Tcl_Obj *resultPtr;

    resultPtr = Tcl_NewStringObj("", -1);
    Blt_Tree_NodeRelativePath(pathPtr->root, nodePtr, pathPtr->sepObjPtr, 
         pathPtr->flags, resultPtr);
    return resultPtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_NodePath --
 *
 *---------------------------------------------------------------------------
 */
const char *
Blt_Tree_NodePath(Node *nodePtr, Blt_TreePathOptions *pathPtr)
{
    return Blt_Tree_NodeRelativePath(pathPtr->root, nodePtr, 
        pathPtr->sepObjPtr, pathPtr->flags, pathPtr->objPtr);
}

int
Blt_Tree_ArrayVariableExists(Tree *treePtr, Node *nodePtr, 
                             const char *arrayName, const char *elemName)
{
    Blt_TreeUid uid;
    Blt_HashEntry *hPtr;
    Blt_HashTable *tablePtr;
    Variable *varPtr;

    uid = Blt_Tree_GetUid(treePtr, arrayName);
    varPtr = GetVariable((Tcl_Interp *)NULL, treePtr, nodePtr, uid);
    if ((varPtr == NULL) || (varPtr->objPtr == NULL)) {
        return FALSE;
    }
    if (Blt_GetArrayFromObj((Tcl_Interp *)NULL, varPtr->objPtr, &tablePtr) 
        != TCL_OK) {
        return FALSE;
    }
    hPtr = Blt_FindHashEntry(tablePtr, elemName);
    return (hPtr != NULL);
}

int
Blt_Tree_GetArrayVariable(Tcl_Interp *interp, Tree *treePtr, Node *nodePtr,
                          const char *arrayName, const char *elemName,
                          Tcl_Obj **valueObjPtrPtr)
{
    Blt_TreeUid uid;
    Blt_HashEntry *hPtr;
    Blt_HashTable *tablePtr;
    Variable *varPtr;

    uid = Blt_Tree_GetUid(treePtr, arrayName);
    varPtr = GetVariable(interp, treePtr, nodePtr, uid);
    if (varPtr == NULL) {
        return TCL_ERROR;
    }
    if (varPtr->objPtr == NULL) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "can't find a variable \"", arrayName, 
                 "\" in tree \"", treePtr->name, "\" at node ", 
                Blt_Tree_NodeIdAscii(nodePtr), (char *)NULL);
        }
        return TCL_ERROR;
    }
    if (Blt_GetArrayFromObj(interp, varPtr->objPtr, &tablePtr) != TCL_OK) {
        return TCL_ERROR;
    }
    hPtr = Blt_FindHashEntry(tablePtr, elemName);
    if (hPtr == NULL) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "can't find an element \"", elemName, 
                "\" in array \"", arrayName, "\" in tree \"", treePtr->name, 
                "\"", (char *)NULL);
        }
        return TCL_ERROR;
    }
    *valueObjPtrPtr = Blt_GetHashValue(hPtr);

    /* Reading any element of the array can cause a trace to fire. */
    if (!(nodePtr->flags & TREE_TRACE_ACTIVE)) {
        CallTraces(interp, treePtr, nodePtr->corePtr, nodePtr, uid, 
                   TREE_TRACE_READS);
    }
    return TCL_OK;
}

int
Blt_Tree_SetArrayVariable(Tcl_Interp *interp, Tree *treePtr, Node *nodePtr,
                          const char *arrayName, const char *elemName,
                          Tcl_Obj *valueObjPtr)
{
    Blt_TreeUid uid;
    Blt_HashEntry *hPtr;
    Blt_HashTable *tablePtr;
    Variable *varPtr;
    unsigned int flags;
    int isNew;

    assert(valueObjPtr != NULL);

    /* 
     * Search for the array in the list of data values.  If one doesn't
     * exist, create it.
     */
    uid = Blt_Tree_GetUid(treePtr, arrayName);
    varPtr = CreateVariable(nodePtr, uid, &isNew);
    if ((varPtr->owner != NULL) && (varPtr->owner != treePtr)) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "can't set private variable \"", 
                             uid, "\"", (char *)NULL);
        }
        return TCL_ERROR;
    }
    flags = TREE_TRACE_WRITES;
    if ((isNew) || (varPtr->objPtr == NULL)) {
        varPtr->objPtr = Blt_NewArrayObj(0, (Tcl_Obj **)NULL);
        Tcl_IncrRefCount(varPtr->objPtr);
        flags |= TREE_TRACE_CREATES;
    } else if (Tcl_IsShared(varPtr->objPtr)) {
        Tcl_DecrRefCount(varPtr->objPtr);
        varPtr->objPtr = Tcl_DuplicateObj(varPtr->objPtr);
        Tcl_IncrRefCount(varPtr->objPtr);
    }
    if (Blt_GetArrayFromObj(interp, varPtr->objPtr, &tablePtr) != TCL_OK) {
        return TCL_ERROR;
    }
    Tcl_InvalidateStringRep(varPtr->objPtr);
    hPtr = Blt_CreateHashEntry(tablePtr, elemName, &isNew);
    if (valueObjPtr != NULL) {
        Tcl_IncrRefCount(valueObjPtr);
    }
    if (!isNew) {
        Tcl_Obj *oldValueObjPtr;

        /* An element by the same name already exists. Decrement the reference
         * count of the old value. */

        oldValueObjPtr = Blt_GetHashValue(hPtr);
        if (oldValueObjPtr != NULL) {
            Tcl_DecrRefCount(oldValueObjPtr);
        }
    }
    Blt_SetHashValue(hPtr, valueObjPtr);

    /*
     * We don't handle traces on a per array element basis.  Setting any
     * element can fire traces for the variable.
     */
    if (!(nodePtr->flags & TREE_TRACE_ACTIVE)) {
        CallTraces(interp, treePtr, nodePtr->corePtr, nodePtr, 
                varPtr->uid, flags);
    }
    return TCL_OK;
}

int
Blt_Tree_UnsetArrayVariable(Tcl_Interp *interp, Tree *treePtr, Node *nodePtr,
                            const char *arrayName, const char *elemName)
{
    Blt_TreeUid uid;                    /* Name of variable in node. */
    Blt_HashEntry *hPtr;
    Blt_HashTable *tablePtr;
    Tcl_Obj *valueObjPtr;
    Variable *varPtr;

    uid = Blt_Tree_GetUid(treePtr, arrayName);
    varPtr = FindVariable(nodePtr, uid);
    if ((varPtr == NULL) || (varPtr->objPtr == NULL)) {
        return TCL_OK;
    }
    if ((varPtr->owner != NULL) && (varPtr->owner != treePtr)) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "can't unset private variable \"", 
                             uid, "\"", (char *)NULL);
        }
        return TCL_ERROR;
    }
    if (Tcl_IsShared(varPtr->objPtr)) {
        Tcl_Obj *newValueObjPtr;

        newValueObjPtr = Tcl_DuplicateObj(varPtr->objPtr);
        Tcl_IncrRefCount(newValueObjPtr);
        Tcl_DecrRefCount(varPtr->objPtr);
        varPtr->objPtr = newValueObjPtr;
    }
    if (Blt_GetArrayFromObj(interp, varPtr->objPtr, &tablePtr) != TCL_OK) {
        return TCL_ERROR;
    }
    hPtr = Blt_FindHashEntry(tablePtr, elemName);
    if (hPtr == NULL) {
        if (interp != NULL) {
           Tcl_AppendResult(interp, "can't find array element \"", 
                  elemName, "\" in variable \"", uid, "\"", (char *)NULL);
        }
        return TCL_ERROR;
    }
    valueObjPtr = Blt_GetHashValue(hPtr);
    if (valueObjPtr != NULL) {
        Tcl_DecrRefCount(valueObjPtr);
    }
    Blt_DeleteHashEntry(tablePtr, hPtr);

    /*
     * Un-setting any element in the array can cause the trace on the
     * variable to fire.
     */
    if (!(nodePtr->flags & TREE_TRACE_ACTIVE)) {
        CallTraces(interp, treePtr, nodePtr->corePtr, nodePtr, 
                varPtr->uid, TREE_TRACE_WRITES);
    }
    return TCL_OK;
}

int
Blt_Tree_AppendArrayVariable(Tcl_Interp *interp, Tree *treePtr, Node *nodePtr,
                             const char *arrayName, const char *elemName,
                             Tcl_Obj *valueObjPtr)
{
    Blt_TreeUid uid;
    Blt_HashEntry *hPtr;
    Blt_HashTable *tablePtr;
    Variable *varPtr;
    unsigned int flags;
    int isNew;

    uid = Blt_Tree_GetUid(treePtr, arrayName);
    varPtr = CreateVariable(nodePtr, uid, &isNew);
    if ((varPtr->owner != NULL) && (varPtr->owner != treePtr)) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "can't set private variable \"", 
                             uid, "\"", (char *)NULL);
        }
        return TCL_ERROR;
    }
    flags = TREE_TRACE_WRITES;
    if ((isNew) || (varPtr->objPtr == NULL)) {
        varPtr->objPtr = Blt_NewArrayObj(0, (Tcl_Obj **)NULL);
        Tcl_IncrRefCount(varPtr->objPtr);
        flags |= TREE_TRACE_CREATES;
    } else if (Tcl_IsShared(varPtr->objPtr)) {
        Tcl_DecrRefCount(varPtr->objPtr);
        varPtr->objPtr = Tcl_DuplicateObj(varPtr->objPtr);
        Tcl_IncrRefCount(varPtr->objPtr);
    }
    if (Blt_GetArrayFromObj(interp, varPtr->objPtr, &tablePtr) != TCL_OK) {
        return TCL_ERROR;
    }
    Tcl_InvalidateStringRep(varPtr->objPtr);
    hPtr = Blt_CreateHashEntry(tablePtr, elemName, &isNew);
    if (isNew) {
        if (valueObjPtr != NULL) {
            Tcl_IncrRefCount(valueObjPtr);
        }
        Blt_SetHashValue(hPtr, valueObjPtr);
    } else {
        Tcl_Obj *oldValueObjPtr;
        
        /* An element by the same name already exists. Decrement the
         * reference count of the old value. */

        oldValueObjPtr = Blt_GetHashValue(hPtr);
        if (oldValueObjPtr == NULL) {
            if (valueObjPtr != NULL) {
                Tcl_IncrRefCount(valueObjPtr);
            }
            Blt_SetHashValue(hPtr, valueObjPtr);
        } else {
            if (Tcl_IsShared(oldValueObjPtr)) {
                Tcl_Obj *newValueObjPtr;
            
                newValueObjPtr = Tcl_DuplicateObj(oldValueObjPtr);
                Tcl_DecrRefCount(oldValueObjPtr);
                Tcl_IncrRefCount(newValueObjPtr);
                Blt_SetHashValue(hPtr, newValueObjPtr);
                oldValueObjPtr = newValueObjPtr;
            }
            if (valueObjPtr != NULL) {
                Tcl_AppendObjToObj(oldValueObjPtr, valueObjPtr);
            }
        }
    }

    /*
     * We don't handle traces on a per array element basis.  Setting any
     * element can fire traces for the variable.
     */
    if (!(nodePtr->flags & TREE_TRACE_ACTIVE)) {
        CallTraces(interp, treePtr, nodePtr->corePtr, nodePtr, varPtr->uid,
                   flags);
    }
    return TCL_OK;
}

int
Blt_Tree_ListAppendArrayVariable(Tcl_Interp *interp, Tree *treePtr,
                                 Node *nodePtr, const char *arrayName,
                                 const char *elemName, Tcl_Obj *valueObjPtr)
{
    Blt_TreeUid uid;
    Blt_HashEntry *hPtr;
    Blt_HashTable *tablePtr;
    Variable *varPtr;
    unsigned int flags;
    int isNew;

    uid = Blt_Tree_GetUid(treePtr, arrayName);
    varPtr = CreateVariable(nodePtr, uid, &isNew);
    if ((varPtr->owner != NULL) && (varPtr->owner != treePtr)) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "can't set private variable \"", 
                             uid, "\"", (char *)NULL);
        }
        return TCL_ERROR;
    }
    flags = TREE_TRACE_WRITES;
    if ((isNew) || (varPtr->objPtr == NULL)) {
        varPtr->objPtr = Blt_NewArrayObj(0, (Tcl_Obj **)NULL);
        Tcl_IncrRefCount(varPtr->objPtr);
        flags |= TREE_TRACE_CREATES;
    } else if (Tcl_IsShared(varPtr->objPtr)) {
        Tcl_Obj *newArrayObjPtr;
        
        newArrayObjPtr = Tcl_DuplicateObj(varPtr->objPtr);
        Tcl_IncrRefCount(newArrayObjPtr);
        Tcl_DecrRefCount(varPtr->objPtr);
        varPtr->objPtr = newArrayObjPtr;
    }
    if (Blt_GetArrayFromObj(interp, varPtr->objPtr, &tablePtr) != TCL_OK) {
        return TCL_ERROR;
    }
    Tcl_InvalidateStringRep(varPtr->objPtr);
    hPtr = Blt_CreateHashEntry(tablePtr, elemName, &isNew);
    if (isNew) {
        if (valueObjPtr != NULL) {
            Tcl_IncrRefCount(valueObjPtr);
        }
        Blt_SetHashValue(hPtr, valueObjPtr);
    } else {
        Tcl_Obj *oldValueObjPtr;

        /* An element by the same name already exists. Decrement the
         * reference count of the old value. */

        oldValueObjPtr = Blt_GetHashValue(hPtr);
        if (oldValueObjPtr == NULL) {
            if (valueObjPtr != NULL) {
                Tcl_IncrRefCount(valueObjPtr);
            }
            Blt_SetHashValue(hPtr, valueObjPtr);
        } else {
            if (Tcl_IsShared(oldValueObjPtr)) {
                Tcl_Obj *newValueObjPtr;
            
                newValueObjPtr = Tcl_DuplicateObj(oldValueObjPtr);
                Tcl_DecrRefCount(oldValueObjPtr);
                Tcl_IncrRefCount(newValueObjPtr);
                Blt_SetHashValue(hPtr, newValueObjPtr);
                oldValueObjPtr = newValueObjPtr;
            }
            if (valueObjPtr != NULL) {
                Tcl_ListObjAppendElement(interp, oldValueObjPtr, valueObjPtr);
            }
        }
    }

    /*
     * We don't handle traces on a per array element basis.  Setting any
     * element can fire traces for the value.
     */
    if (!(nodePtr->flags & TREE_TRACE_ACTIVE)) {
        CallTraces(interp, treePtr, nodePtr->corePtr, nodePtr, 
                varPtr->uid, flags);
    }
    return TCL_OK;
}

int
Blt_Tree_ListReplaceArrayVariable(Tcl_Interp *interp, Tree *treePtr,
                                   Node *nodePtr, const char *arrayName,
                                   const char *elemName, int firstIndex,
                                   int lastIndex, int objc, 
                                   Tcl_Obj *const *objv)
{
    Blt_TreeUid uid;
    Blt_HashEntry *hPtr;
    Blt_HashTable *tablePtr;
    Variable *varPtr;
    unsigned int flags;
    int isNew;

    uid = Blt_Tree_GetUid(treePtr, arrayName);
    varPtr = CreateVariable(nodePtr, uid, &isNew);
    if ((varPtr->owner != NULL) && (varPtr->owner != treePtr)) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "can't set private variable \"", 
                             uid, "\"", (char *)NULL);
        }
        return TCL_ERROR;
    }
    flags = TREE_TRACE_WRITES;
    if ((isNew) || (varPtr->objPtr == NULL)) {
        varPtr->objPtr = Blt_NewArrayObj(0, (Tcl_Obj **)NULL);
        Tcl_IncrRefCount(varPtr->objPtr);
        flags |= TREE_TRACE_CREATES;
    } else if (Tcl_IsShared(varPtr->objPtr)) {
        Tcl_Obj *newArrayObjPtr;
        
        newArrayObjPtr = Tcl_DuplicateObj(varPtr->objPtr);
        Tcl_IncrRefCount(newArrayObjPtr);
        Tcl_DecrRefCount(varPtr->objPtr);
        varPtr->objPtr = newArrayObjPtr;
    }
    if (Blt_GetArrayFromObj(interp, varPtr->objPtr, &tablePtr) != TCL_OK) {
        return TCL_ERROR;
    }
    Tcl_InvalidateStringRep(varPtr->objPtr);
    hPtr = Blt_CreateHashEntry(tablePtr, elemName, &isNew);
    if (isNew) {
        Tcl_Obj *valueObjPtr;
        
        /* Make list of remaining elements on command line. */
        valueObjPtr = Tcl_NewListObj(objc, objv);
        if (valueObjPtr != NULL) {
            Tcl_IncrRefCount(valueObjPtr);
        }
        Blt_SetHashValue(hPtr, valueObjPtr);
    } else {
        Tcl_Obj *oldValueObjPtr;
        Tcl_Obj *valueObjPtr;

        /* An element by the same name already exists. Decrement the reference
         * count of the old value. */

        oldValueObjPtr = Blt_GetHashValue(hPtr);
        if (oldValueObjPtr == NULL) {
            valueObjPtr = Tcl_NewListObj(objc, objv);
            if (valueObjPtr != NULL) {
                Tcl_IncrRefCount(valueObjPtr);
            }
            Blt_SetHashValue(hPtr, valueObjPtr);
        } else {
            int length, count;
            
            if (Tcl_IsShared(oldValueObjPtr)) {
                Tcl_Obj *newValueObjPtr;
            
                newValueObjPtr = Tcl_DuplicateObj(oldValueObjPtr);
                Tcl_DecrRefCount(oldValueObjPtr);
                Tcl_IncrRefCount(newValueObjPtr);
                Blt_SetHashValue(hPtr, newValueObjPtr);
                oldValueObjPtr = newValueObjPtr;
            }
            if (Tcl_ListObjLength(interp, oldValueObjPtr, &length) != TCL_OK) {
                return TCL_ERROR;
            }
            count = 0;
            if ((firstIndex == -1) && (length > 0)) {
                firstIndex += length;
            }
            if ((lastIndex  == -1) && (length > 0))  {
                lastIndex += length;
            }
            if ((firstIndex >= 0) && (lastIndex >= 0) &&
                (lastIndex >= firstIndex)) {
                count = lastIndex - firstIndex + 1;
            }
            if (Tcl_ListObjReplace(interp, oldValueObjPtr, firstIndex, count,
                objc, objv) != TCL_OK) {
                return TCL_ERROR;
            }
        }
    }

    /*
     * We don't handle traces on a per array element basis.  Setting any
     * element can fire traces for the variable.
     */
    if (!(nodePtr->flags & TREE_TRACE_ACTIVE)) {
        CallTraces(interp, treePtr, nodePtr->corePtr, nodePtr, 
                varPtr->uid, flags);
    }
    return TCL_OK;
}

int
Blt_Tree_ArrayNames(Tcl_Interp *interp, Tree *treePtr, Node *nodePtr,
                    const char *arrayName, Tcl_Obj *listObjPtr)
{
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    Blt_HashTable *tablePtr;
    Variable *varPtr;
    Blt_TreeUid uid;

    uid = Blt_Tree_GetUid(treePtr, arrayName);
    varPtr = GetVariable(interp, treePtr, nodePtr, uid);
    if (varPtr == NULL) {
        return TCL_ERROR;
    }
    if (varPtr->objPtr == NULL) {
        return TCL_OK;
    }
    if (Tcl_IsShared(varPtr->objPtr)) {
        Tcl_DecrRefCount(varPtr->objPtr);
        varPtr->objPtr = Tcl_DuplicateObj(varPtr->objPtr);
        Tcl_IncrRefCount(varPtr->objPtr);
    }
    if (Blt_GetArrayFromObj(interp, varPtr->objPtr, &tablePtr) != TCL_OK) {
        return TCL_ERROR;
    }
    for (hPtr = Blt_FirstHashEntry(tablePtr, &cursor); hPtr != NULL; 
         hPtr = Blt_NextHashEntry(&cursor)) {
        Tcl_Obj *objPtr;

        objPtr = Tcl_NewStringObj(Blt_GetHashKey(tablePtr, hPtr), -1);
        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
    }
    return TCL_OK;
}

void
Blt_Tree_NewTagTable(Tree *treePtr)
{
    Blt_TreeTagTable *tagTablePtr;

    if (treePtr->tagTablePtr != NULL) {
        ReleaseTagTable(treePtr->tagTablePtr);
    }
    /* By default, use own sets of tags. */
    tagTablePtr = Blt_AssertMalloc(sizeof(Blt_TreeTagTable));
    tagTablePtr->refCount = 1;
    Blt_InitHashTable(&tagTablePtr->tagTable, BLT_STRING_KEYS);
    treePtr->tagTablePtr = tagTablePtr;
}

int
Blt_Tree_TagTableIsShared(Tree *treePtr)
{
    return (treePtr->tagTablePtr->refCount > 1);
}   

void
Blt_Tree_ClearTags(Tree *treePtr, Node *nodePtr)
{
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    
    for (hPtr = Blt_FirstHashEntry(&treePtr->tagTablePtr->tagTable, &cursor); 
        hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
        Blt_TreeTagEntry *tePtr;
        Blt_HashEntry *hPtr2;

        tePtr = Blt_GetHashValue(hPtr);
        hPtr2 = Blt_FindHashEntry(&tePtr->nodeTable, (const char *)nodePtr);
        if (hPtr2 != NULL) {
            Blt_DeleteHashEntry(&tePtr->nodeTable, hPtr2);
        }
    }
}

Blt_TreeTagEntry *
Blt_Tree_RememberTag(Tree *treePtr, const char *tagName)
{
    int isNew;
    Blt_HashEntry *hPtr;
    Blt_TreeTagEntry *tePtr;
    Blt_HashTable *tablePtr;

    tablePtr = &treePtr->tagTablePtr->tagTable;
    hPtr = Blt_CreateHashEntry(tablePtr, tagName, &isNew);
    if (isNew) {
        tePtr = Blt_AssertMalloc(sizeof(Blt_TreeTagEntry));
        Blt_InitHashTable(&tePtr->nodeTable, BLT_ONE_WORD_KEYS);
        Blt_SetHashValue(hPtr, tePtr);
        tePtr->hashPtr = hPtr;
        tePtr->tagName = Blt_GetHashKey(tablePtr, hPtr);
    } else {
        tePtr = Blt_GetHashValue(hPtr);
    }
    return tePtr;
}

void
Blt_Tree_ForgetTag(Tree *treePtr, const char *tagName)
{
    Blt_HashEntry *hPtr;
    Blt_TreeTagEntry *tePtr;

    if ((strcmp(tagName, "all") == 0) || (strcmp(tagName, "root") == 0)) {
        return;
    }
        
    hPtr = Blt_FindHashEntry(&treePtr->tagTablePtr->tagTable, tagName);
    if (hPtr == NULL) {
        return;
    }
    tePtr = Blt_GetHashValue(hPtr);
    Blt_DeleteHashTable(&tePtr->nodeTable);
    Blt_Free(tePtr);
    Blt_DeleteHashEntry(&treePtr->tagTablePtr->tagTable, hPtr);
}

int
Blt_Tree_HasTag(Tree *treePtr, Node *nodePtr, const char *tagName)
{
    Blt_HashEntry *hPtr;
    Blt_TreeTagEntry *tePtr;

    if (strcmp(tagName, "all") == 0) {
        return TRUE;
    }
    if ((strcmp(tagName, "root") == 0) && 
        (nodePtr == Blt_Tree_RootNode(treePtr))) {
        return TRUE;
    }
    hPtr = Blt_FindHashEntry(&treePtr->tagTablePtr->tagTable, tagName);
    if (hPtr == NULL) {
        return FALSE;
    }
    tePtr = Blt_GetHashValue(hPtr);
    hPtr = Blt_FindHashEntry(&tePtr->nodeTable, (const char *)nodePtr);
    if (hPtr == NULL) {
        return FALSE;
    }
    return TRUE;
}

void
Blt_Tree_AddTag(Tree *treePtr, Node *nodePtr, const char *tagName)
{
    Blt_TreeTagEntry *tePtr;

    if ((strcmp(tagName, "all") == 0) || (strcmp(tagName, "root") == 0)) {
        return;
    }
    tePtr = Blt_Tree_RememberTag(treePtr, tagName);
    if (nodePtr != NULL) {
        Blt_HashEntry *hPtr;
        int isNew;

        hPtr = Blt_CreateHashEntry(&tePtr->nodeTable, (const char *)nodePtr,
                                   &isNew);
        if (isNew) {
            Blt_SetHashValue(hPtr, nodePtr);
        }
    }
}

void
Blt_Tree_RemoveTag(Tree *treePtr, Node *nodePtr, const char *tagName)
{
    Blt_HashEntry *hPtr;
    Blt_TreeTagEntry *tePtr;
    
    if (strcmp(tagName, "all") == 0) {
        return;                         /* Can't remove tag "all". */
    }
    if ((strcmp(tagName, "root") == 0) && 
        (nodePtr == Blt_Tree_RootNode(treePtr))) {
        return;                         /* Can't remove tag "root" from root
                                         * node. */
    }
    hPtr = Blt_FindHashEntry(&treePtr->tagTablePtr->tagTable, tagName);
    if (hPtr == NULL) {
        return;                         /* No such tag. */
    }
    tePtr = Blt_GetHashValue(hPtr);
    hPtr = Blt_FindHashEntry(&tePtr->nodeTable, (const char *)nodePtr);
    if (hPtr == NULL) {
        return;                         /* Node isn't tagged. */
    }
    Blt_DeleteHashEntry(&tePtr->nodeTable, hPtr);
    return;
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_TagHashTable --
 *
 *---------------------------------------------------------------------------
 */
Blt_HashTable *
Blt_Tree_TagHashTable(Tree *treePtr, const char *tagName)
{
    Blt_HashEntry *hPtr;
   
    hPtr = Blt_FindHashEntry(&treePtr->tagTablePtr->tagTable, tagName);
    if (hPtr != NULL) {
        Blt_TreeTagEntry *tePtr;
        
        tePtr = Blt_GetHashValue(hPtr);
        return &tePtr->nodeTable;
    }
    return NULL;
}

Blt_HashEntry *
Blt_Tree_FirstTag(Tree *treePtr, Blt_HashSearch *cursorPtr)
{
    return Blt_FirstHashEntry(&treePtr->tagTablePtr->tagTable, cursorPtr);
}


long 
Blt_Tree_Depth(Tree *treePtr) 
{
    Node *nodePtr;
    long depth = -1;

    for (nodePtr = treePtr->root; nodePtr != NULL; 
         nodePtr = Blt_Tree_NextNode(treePtr->root, nodePtr)) {
        if (nodePtr->depth > depth) {
            depth = nodePtr->depth;
        }
    }
    return depth;
}


static int
IsNodeId(const char *string)
{
    long value;

    return (Blt_GetCount(NULL, string, COUNT_NNEG, &value) == TCL_OK);
}

static Blt_TreeNode 
ParseModifiers(Tcl_Interp *interp, Blt_Tree tree, Blt_TreeNode node,
               char *modifiers)
{
    char *p, *token;

    p = modifiers;
    do {
        p += 2;                         /* Skip the initial "->" */
        token = strstr(p, "->");
        if (token != NULL) {
            *token = '\0';
        }
        if (IsNodeId(p)) {
            long inode;
            
            if (Blt_GetCount(interp, p, COUNT_NNEG, &inode) != TCL_OK) {
                node = NULL;
            } else {
                node = Blt_Tree_GetNodeFromIndex(tree, inode);
            }
        } else if ((*p == 'p') && (strcmp(p, "parent") == 0)) {
            node = Blt_Tree_ParentNode(node);
        } else if ((*p == 'f') && (strcmp(p, "firstchild") == 0)) {
            node = Blt_Tree_FirstChild(node);
        } else if ((*p == 'l') && (strcmp(p, "lastchild") == 0)) {
            node = Blt_Tree_LastChild(node);
        } else if ((*p == 'n') && (strcmp(p, "next") == 0)) {
            node = Blt_Tree_NextNode(NULL, node);
        } else if ((*p == 'n') && (strcmp(p, "nextsibling") == 0)) {
            node = Blt_Tree_NextSibling(node);
        } else if ((*p == 'p') && (strcmp(p, "previous") == 0)) {
            node = Blt_Tree_PrevNode(NULL, node);
        } else if ((*p == 'p') && (strcmp(p, "prevsibling") == 0)) {
            node = Blt_Tree_PrevSibling(node);
        } else {
            int length;

            length = strlen(p);
            if (length > 0) {
                char *endp;

                endp = p + length - 1;
                if ((*p == '"') && (*endp == '"')) {
                    *endp = '\0';
                    node = Blt_Tree_FindChild(node, p + 1);
                    *endp = '"';
                } else {
                    node = Blt_Tree_FindChild(node, p);
                }               
            }
        }
        if (node == NULL) {
            goto error;
        }
        if (token != NULL) {
            *token = '-';               /* Repair the string */
        }
        p = token;
    } while (token != NULL);
    return node;
 error:
    if (token != NULL) {
        *token = '-';                   /* Repair the string */
    }
    return NULL;
}

/*
 *---------------------------------------------------------------------------
 *
 * GetNodeFromObj --
 *
 *---------------------------------------------------------------------------
 */
static int 
GetNodeFromObj(Tcl_Interp *interp, Blt_Tree tree, Tcl_Obj *objPtr, 
               Blt_TreeNode *nodePtr)
{
    Blt_TreeNode node;
    char *string;
    char *p;
    char save;

    node = NULL;                        /* Suppress compiler warnings. */
    save = '\0';

    string = Tcl_GetString(objPtr);

    /* Check if modifiers are present. */
    p = strstr(string, "->");
    if (p != NULL) {
        save = *p;
        *p = '\0';
    }
    if (IsNodeId(string)) {
        long inode;

        if (p != NULL) {
            if (Blt_GetCount(interp, string, COUNT_NNEG, &inode) != TCL_OK) {
                goto error;
            }
        } else {
            if (Blt_GetCountFromObj(interp, objPtr, COUNT_NNEG, &inode) != TCL_OK) {
                goto error;
            }
        }
        node = Blt_Tree_GetNodeFromIndex(tree, inode);
    }  else if (tree != NULL) {
        char c;

        c = string[0];
        if ((c == 'a') && (strcmp(string, "all") == 0)) {
            if (Blt_Tree_Size(Blt_Tree_RootNode(tree)) > 1) {
                if (interp != NULL) {
                    Tcl_AppendResult(interp, "more than one node tagged as \"", 
                                     string, "\"", (char *)NULL);
                }
                goto error;
            }
            node = Blt_Tree_RootNode(tree);
        } else if ((c == 'r') && (strcmp(string, "root") == 0)) {
            node = Blt_Tree_RootNode(tree);
        } else {
            Blt_HashTable *tablePtr;

            node = NULL;

            /* Lookup the string as a tag. */
            tablePtr = Blt_Tree_TagHashTable(tree, string);
            if (tablePtr == NULL) {
                if (interp != NULL) {
                    Tcl_AppendResult(interp, "can't find tag or id \"", string, 
                        "\" in ", Blt_Tree_Name(tree), (char *)NULL);
                }
                goto error;
            } 
            if (tablePtr->numEntries > 1) {
                if (interp != NULL) {
                    Tcl_AppendResult(interp, "more than one node tagged as \"", 
                                     string, "\"", (char *)NULL);
                }
                goto error;
            }
            if (tablePtr->numEntries == 1) {
                Blt_HashSearch iter;
                Blt_HashEntry *hPtr;
                
                hPtr = Blt_FirstHashEntry(tablePtr, &iter);
                node = Blt_GetHashValue(hPtr);
                if (p != NULL) {
                    *p = save;
                }
            }
        }
    }
    if (node != NULL) {
        if (p != NULL) {
            node = ParseModifiers(interp, tree, node, p);
            if (node == NULL) {
                if (interp != NULL) {
                    *p = save;          /* Need entire string. */
                    Tcl_AppendResult(interp, "can't find tag or id \"", string, 
                             "\" in ", Blt_Tree_Name(tree), (char *)NULL);
                }
                goto error;
            }
        }
        if (p != NULL) {
            *p = save;
        }
        *nodePtr = node;
        return TCL_OK;
    } 
    if (interp != NULL) {
        Tcl_AppendResult(interp, "can't find tag or id \"", string, "\" in ", 
                         Blt_Tree_Name(tree), (char *)NULL);
    }
 error:
    if (p != NULL) {
        *p = save;
    }
    return TCL_ERROR;
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_GetNodeIterator --
 *
 *      Returns the id of the first node tagged by the given tag in objPtr.
 *      It basically hides much of the cumbersome special case details.
 *      For example, the special tags "root" and "all" always exist, so
 *      they don't have entries in the tag hashtable.  If it's a hashed tag
 *      (not "root" or "all"), we have to save the place of where we are in
 *      the table for the next call to NextTaggedNode.
 *
 *---------------------------------------------------------------------------
 */
int
Blt_Tree_GetNodeIterator(Tcl_Interp *interp, Blt_Tree tree, Tcl_Obj *objPtr,
                         Blt_TreeNodeIterator *iterPtr)
{
    Blt_TreeNode node;
    char c;
    const char *string;
    
    iterPtr->type = ITER_TYPE_SINGLE;
    iterPtr->root = Blt_Tree_RootNode(tree);

    /* Process strings with modifiers or digits as simple ids, not tags. */
    if (GetNodeFromObj(NULL, tree, objPtr, &node) == TCL_OK) {
        iterPtr->current = node;
        return TCL_OK;
    }
    string = Tcl_GetString(objPtr);
    c = string[0];
    if ((c == 'a') && (strcmp(string, "all") == 0)) {
        iterPtr->type = ITER_TYPE_ALL;
        iterPtr->current = iterPtr->root;
        return TCL_OK;
    } else if ((c == 'r') && (strcmp(string, "root") == 0))  {
        iterPtr->current = iterPtr->root;
        return TCL_OK;
    } else {
        Blt_HashTable *tablePtr;

        tablePtr = Blt_Tree_TagHashTable(tree, string);
        if (tablePtr != NULL) {
            Blt_HashEntry *hPtr;
            
            iterPtr->type = ITER_TYPE_TAG;
            hPtr = Blt_FirstHashEntry(tablePtr, &iterPtr->cursor); 
            if (hPtr == NULL) {
                iterPtr->current = NULL;
                return TCL_OK;
            }
            iterPtr->current = Blt_GetHashValue(hPtr);
            return TCL_OK;
        }
    }
    if (interp != NULL) {
        Tcl_AppendResult(interp, "can't find tag or id \"", string, "\" in ", 
                         Blt_Tree_Name(tree), (char *)NULL);
    }
    return TCL_ERROR;
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_FirstTaggedNode --
 *
 *---------------------------------------------------------------------------
 */
Blt_TreeNode 
Blt_Tree_FirstTaggedNode(Blt_TreeNodeIterator *iterPtr)
{
    return iterPtr->current;
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_FirstTaggedNode --
 *
 *---------------------------------------------------------------------------
 */
Blt_TreeNode 
Blt_Tree_NextTaggedNode(Blt_TreeNodeIterator *iterPtr)
{
    switch (iterPtr->type) {
    case ITER_TYPE_SINGLE:
        return NULL;
    case ITER_TYPE_TAG:
        {
            Blt_HashEntry *hPtr;

            hPtr = Blt_NextHashEntry(&iterPtr->cursor);
            if (hPtr == NULL) {
                return NULL;
            }
            return Blt_GetHashValue(hPtr);
        }
    case ITER_TYPE_ALL:
        iterPtr->current = Blt_Tree_NextNode(NULL, iterPtr->current);
        return iterPtr->current;
    }
    return NULL;
}


/*
 *---------------------------------------------------------------------------
 *
 * Blt_Tree_GetNodeFromObj --
 *
 *---------------------------------------------------------------------------
 */
int 
Blt_Tree_GetNodeFromObj(Tcl_Interp *interp, Blt_Tree tree, Tcl_Obj *objPtr, 
                        Blt_TreeNode *nodePtr)
{
    Blt_TreeNodeIterator iter;

    if (Blt_Tree_GetNodeIterator(interp, tree, objPtr, &iter) != TCL_OK) {
        return TCL_ERROR;
    }
    *nodePtr = Blt_Tree_FirstTaggedNode(&iter);
    if (Blt_Tree_NextTaggedNode(&iter) != NULL) {
        if (interp != NULL) {
            Tcl_AppendResult(interp, "tag \"", Tcl_GetString(objPtr),
                             "\" refers to more than one node", (char *)NULL);
        }
        return TCL_ERROR;
    }
    return TCL_OK;                      /* Singleton tag. */
}
