/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.com/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */


/* 
 * urlspace.c --
 *
 *	This file implements a Trie data structure. It is used
 *	for "UrlSpecificData"; for example, when one registers
 *	a handler for all GET /foo/bar/ *.html requests, the data
 *	structure that holds that information is implemented herein.
 *	For full details see the file doc/urlspace.txt.
 */

static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/urlspace.c,v 1.5 2001/01/16 18:14:27 jgdavidson Exp $, compiled: " __DATE__ " " __TIME__;

#include "nsd.h"

/*
 * This optimization, when turned on, prevents the server from doing a
 * whole lot of calls to Tcl_StringMatch on every lookup in urlspace.
 * Instead, a strcmp is done. This hasn't been thoroughly tested, so
 * it is off by default.
 *
 *  #define __URLSPACE_OPTIMIZE__
 */

/*
 * This structure defines a Node. It is the lowest-level structure in
 * urlspace and contains the data the the user puts in. It holds data
 * whose scope is a set of URLs, such as /foo/bar/ *.html.
 * Data/cleanup functions are kept seperately for inheriting and non-
 * inheriting URLs, as there could be overlap.
 */

typedef struct {
    int    id;                          /* Handle from Ns_UrlSpecificAlloc */
    void  *dataInherit;                 /* User's data */
    void  *dataNoInherit;               /* User's data */
    void   (*deletefuncInherit) (void *);    /* Cleanup function */
    void   (*deletefuncNoInherit) (void *);  /* Cleanup function */
} Node;

/*
 * This structure defines a trie. A trie is a tree whose nodes are
 * branches and channels. It is an inherently recursive data structure,
 * and each node is itself a trie. Each node represents one "part" of
 * a URL; in this case, a "part" is server name, method, directory, or
 * wildcard.
 */

typedef struct {
    Ns_Index   branches;
    Ns_Index  *indexnode;
} Trie;

/*
 * A branch is a typical node in a Trie. The "word" is the part of the
 * URL that the branch represents, and "node" is the sub-trie.
 */

typedef struct {
    char  *word;
    Trie   node;
} Branch;

/*
 * A channel is much like a branch. It exists only at the second level
 * (Channels come out of Junctions, which are top-level structures).
 * The filter is a copy of the very last part of the URLs matched by
 * branches coming out of this channel (only branches come out of channels).
 * When looking for a URL, the filename part of the target URL is compared
 * with the filter in each channel, and the channel is traversed only if
 * there is a match
 */

typedef struct {
    char  *filter;
    Trie   trie;
} Channel;

/*
 * A Junction is the top-level structure. Channels come out of a junction.
 * Currently, only one junction is defined--the static global urlspace.
 */

typedef struct {
    Ns_Index byname;
    /* 
     * We've experimented with getting rid of this index because
     * it is like byname but in semi-reverse lexicographical
     * order.  This optimization seems to work in all cases, but
     * we need a thorough way of testing all cases.
     */
#ifndef __URLSPACE_OPTIMIZE__
    Ns_Index byuse;
#endif
} Junction;

/*
 * Local functions defined in this file
 */

static void  TrieDestroy(Trie *trie);
static void  NodeDestroy(Node *nodePtr);
static int   CmpNodes(Node **leftPtrPtr, Node **rightPtrPtr);
static int   CmpIdWithNode(int id, Node **nodePtrPtr);
static Ns_Index * IndexNodeCreate(void);
static void  IndexNodeDestroy(Ns_Index *indexPtr);
static int   CmpBranches(Branch **leftPtrPtr, Branch **rightPtrPtr);
static int   CmpKeyWithBranch(char *key, Branch **branchPtrPtr);
static void  BranchDestroy(Branch *branchPtr);

/*
 * Utility functions
 */

static void MkSeq(Ns_DString *dsPtr, char *server, char *method, char *url);
#ifdef DEBUG
static void indentspace(int n);
static void PrintTrie(Trie *triePtr, int indent);
static void PrintJunction(Junction *junctionPtr);
static void PrintSeq(char *seq);
#endif

/*
 * Trie functions
 */

static void  TrieInit(Trie *triePtr);
static void  TrieAdd(Trie *triePtr, char *seq, int id, void *data, int flags, 
                     void (*deletefunc) (void *));
static void  TrieTrunc(Trie *triePtr, int id);
static void  TrieDestroy(Trie *triePtr);
static int   TrieBranchTrunc(Trie *triePtr, char *seq, int id);
static void *TrieFind(Trie *triePtr, char *seq, int id, int *depthPtr);
static void *TrieFindExact(Trie *triePtr, char *seq, int id, int flags);
static void *TrieDelete(Trie *triePtr, char *seq, int id, int flags);

/*
 * Channel functions
 */

#ifndef __URLSPACE_OPTIMIZE__
static int CmpChannels(Channel **leftPtrPtr, Channel **rightPtrPtr);
static int CmpKeyWithChannel(char *key, Channel **channelPtrPtr);
#endif

static int CmpChannelsAsStrings(Channel **leftPtrPtr, Channel **rightPtrPtr);
static int CmpKeyWithChannelAsStrings(char *key, Channel **channelPtrPtr);

/*
 * Juntion functions
 */

static void JunctionInit(Junction *juncPtr);
static void JunctionAdd(Junction *juncPtr, char *seq, int id, void *data,
			int flags, void (*deletefunc) (void *));
static void JunctionBranchTrunc(Junction *juncPtr, char *seq, int id);
static void *JunctionFind(Junction *juncPtr, char *seq, int id, int fast);
static void *JunctionFindExact(Junction *juncPtr, char *seq, int id, int flags,
			       int fast);
static void *JunctionDelete(Junction *juncPtr, char *seq, int id, int flags);

/*
 * Static variables defined in this file
 */

static Junction urlspace;    /* All URL-specific data stored here */
static Ns_Mutex lock;


/*
 *----------------------------------------------------------------------
 *
 * Ns_UrlSpecificAlloc --
 *
 *	Allocate a unique ID to create a seperate virtual URL-space. 
 *
 * Results:
 *	An integer handle 
 *
 * Side effects:
 *	nextid will be incremented; don't call after server startup.
 *
 *----------------------------------------------------------------------
 */

int
Ns_UrlSpecificAlloc(void)
{
    int        id;
    static int nextid = 0;
    static int initialized = 0;

    Ns_MutexLock(&lock);
    if (!initialized) {
	Ns_MutexSetName2(&lock, "ns", "urlspace");
        JunctionInit(&urlspace);
	initialized = 1;
    }
    id = nextid++;
    Ns_MutexUnlock(&lock);
    return id;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_UrlSpecificSet --
 *
 *	Associate data with a set of URLs matching a wildcard, or 
 *	that are simply sub-URLs.
 *
 *	Flags can be NS_OP_NOINHERIT or NS_OP_NODELETE.
 *
 * Results:
 *	None 
 *
 * Side effects:
 *	Will set data in the urlspace trie. 
 *
 *----------------------------------------------------------------------
 */

void
Ns_UrlSpecificSet(char *server, char *method, char *url, int id, void *data,
		  int flags, void (*deletefunc) (void *))
{
    Ns_DString ds;

    Ns_DStringInit(&ds);
    MkSeq(&ds, server, method, url);
    Ns_MutexLock(&lock);
    JunctionAdd(&urlspace, ds.string, id, data, flags, deletefunc);
    Ns_MutexUnlock(&lock);
    Ns_DStringFree(&ds);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_UrlSpecificGet --
 *
 *	Find URL-specific data in the subspace identified by id that 
 *	the passed-in URL matches 
 *
 * Results:
 *	A pointer to user data, set with Ns_UrlSpecificSet 
 *
 * Side effects:
 *	None 
 *
 *----------------------------------------------------------------------
 */

void *
Ns_UrlSpecificGet(char *server, char *method, char *url, int id)
{
    Ns_DString  ds;
    void       *data;

    Ns_DStringInit(&ds);
    MkSeq(&ds, server, method, url);
    Ns_MutexLock(&lock);
    data = JunctionFind(&urlspace, ds.string, id, 0);
    Ns_MutexUnlock(&lock);
    Ns_DStringFree(&ds);

    return data;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_UrlSpecificGetFast --
 *
 *	Similar to Ns_UrlSpecificGet, but doesn't support wildcards; 
 *	on the other hand, it's a lot faster. 
 *
 * Results:
 *	See Ns_UrlSpecificGet 
 *
 * Side effects:
 *	None 
 *
 *----------------------------------------------------------------------
 */

void *
Ns_UrlSpecificGetFast(char *server, char *method, char *url, int id)
{
    Ns_DString  ds;
    void       *data;

    Ns_DStringInit(&ds);
    MkSeq(&ds, server, method, url);
    Ns_MutexLock(&lock);
    data = JunctionFind(&urlspace, ds.string, id, 1);
    Ns_MutexUnlock(&lock);
    Ns_DStringFree(&ds);
    
    return data;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_UrlSpecificGetExact --
 *	Similar to Ns_UrlSpecificGet, but does not support URL
 *	inheritance
 *
 * Results:
 *	See Ns_UrlSpecificGet 
 *
 * Side effects:
 *	None 
 *
 *----------------------------------------------------------------------
 */

void *
Ns_UrlSpecificGetExact(char *server, char *method, char *url, int id,
		       int flags)
{
    Ns_DString  ds;
    void       *data;

    Ns_DStringInit(&ds);
    MkSeq(&ds, server, method, url);
    Ns_MutexLock(&lock);
    data = JunctionFindExact(&urlspace, ds.string, id, flags, 0);
    Ns_MutexUnlock(&lock);
    Ns_DStringFree(&ds);
    
    return data;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_UrlSpecificDestroy --
 *
 *	Delete some urlspecific data.
 *
 *	flags can be NS_OP_NODELETE, NS_OP_NOINHERIT and/or NS_OP_RECURSE
 *
 * Results:
 *	None 
 *
 * Side effects:
 *	Will remove data from urlspace; don't call this after server 
 *	startup. 
 *
 *----------------------------------------------------------------------
 */

void *
Ns_UrlSpecificDestroy(char *server, char *method, char *url, int id, int flags)
{
    Ns_DString  ds;
    void       *data = NULL;

    Ns_DStringInit(&ds);
    MkSeq(&ds, server, method, url);
    Ns_MutexLock(&lock);
    if (flags & NS_OP_RECURSE) {
	JunctionBranchTrunc(&urlspace, ds.string, id);
	data = NULL;
    } else {
	data = JunctionDelete(&urlspace, ds.string, id, flags);
    }
    Ns_MutexUnlock(&lock);
    Ns_DStringFree(&ds);
    
    return data;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ServerSpecificAlloc --
 *
 *	Allocate a unique integer to be used with Ns_ServerSpecific* 
 *	calls 
 *
 * Results:
 *	An integer handle 
 *
 * Side effects:
 *	None 
 *
 *----------------------------------------------------------------------
 */

int
Ns_ServerSpecificAlloc(void)
{
    return Ns_UrlSpecificAlloc();
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ServerSpecificSet --
 *
 *	Set server-specific data 
 *
 * Results:
 *	None 
 *
 * Side effects:
 *	See Ns_UrlSpecificSet 
 *
 *----------------------------------------------------------------------
 */

void
Ns_ServerSpecificSet(char *handle, int id, void *data, int flags,
		     void (*deletefunc) (void *))
{
    Ns_UrlSpecificSet(handle, NULL, NULL, id, data, flags, deletefunc);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ServerSpecificGet --
 *
 *	Get server-specific data.
 *
 * Results:
 *	User server-specific data.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void *
Ns_ServerSpecificGet(char *handle, int id)
{
    return Ns_UrlSpecificGet(handle, NULL, NULL, id);
}



/*
 *----------------------------------------------------------------------
 *
 * Ns_ServerSpecificDestroy --
 *
 *	Destroy server-specific data. 
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Will remove data from urlspace.
 *
 *----------------------------------------------------------------------
 */

void *
Ns_ServerSpecificDestroy(char *handle, int id, int flags)
{
    return Ns_UrlSpecificDestroy(handle, NULL, NULL, id, flags);
}


/*
 *----------------------------------------------------------------------
 *
 * NodeDestroy --
 *
 *	Free a node and its data 
 *
 * Results:
 *	None 
 *
 * Side effects:
 *	The delete function is called and the node is freed 
 *
 *----------------------------------------------------------------------
 */

static void
NodeDestroy(Node *nodePtr)
{
    if (nodePtr == NULL) {
        goto done;
    }
    if (nodePtr->deletefuncNoInherit != NULL) {
        (*nodePtr->deletefuncNoInherit) (nodePtr->dataNoInherit);
    }
    if (nodePtr->deletefuncInherit != NULL) {
        (*nodePtr->deletefuncInherit) (nodePtr->dataInherit);
    }
    ns_free(nodePtr);

 done:
    return;
}


/*
 *----------------------------------------------------------------------
 *
 * CmpNodes --
 *
 *	Compare two Nodes by id. Ns_Index calls use this as a callback.
 *
 * Results:
 *	0 if equal, 1 if left is greater, -1 if left is less.
 *
 * Side effects:
 *	None 
 *
 *----------------------------------------------------------------------
 */

static int
CmpNodes(Node **leftPtrPtr, Node **rightPtrPtr)
{
    if ((*leftPtrPtr)->id != (*rightPtrPtr)->id) {
        return ((*leftPtrPtr)->id > (*rightPtrPtr)->id) ? 1 : -1;
    } else {
        return 0;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * CmpIdWithNode --
 *
 *	Compare a node's ID to a passed-in ID; called by Ns_Index*
 *
 * Results:
 *	0 if equal; 1 if id is too high; -1 if id is too low 
 *
 * Side effects:
 *	None 
 *
 *----------------------------------------------------------------------
 */

static int
CmpIdWithNode(int id, Node **nodePtrPtr)
{
    if (id != (*nodePtrPtr)->id) {
        return (id > (*nodePtrPtr)->id) ? 1 : -1;
    } else {
        return 0;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * IndexNodeCreate --
 *
 *	Initialize a trie->indexnode structure 
 *
 * Results:
 *	A pointer to an appropriately initialized index 
 *
 * Side effects:
 *	Memory alloacted. 
 *
 *----------------------------------------------------------------------
 */

static Ns_Index *
IndexNodeCreate(void)
{
    Ns_Index *indexPtr;

    indexPtr = ns_malloc(sizeof(Ns_Index));
    Ns_IndexInit(indexPtr, 5, (int (*) (const void *, const void *)) CmpNodes,
        (int (*) (const void *, const void *)) CmpIdWithNode);
    
    return indexPtr;
}


/*
 *----------------------------------------------------------------------
 *
 * IndexNodeDestroy --
 *
 *	Wipe out a trie->indexnode structure.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory freed.
 *
 *----------------------------------------------------------------------
 */

static void
IndexNodeDestroy(Ns_Index *indexPtr)
{
    int i;

    i = Ns_IndexCount(indexPtr);
    while (i--) {
        NodeDestroy(Ns_IndexEl(indexPtr, i));
    }
    Ns_IndexDestroy(indexPtr);
    ns_free(indexPtr);
}


/*
 *----------------------------------------------------------------------
 *
 * CmpBranches --
 *
 *	Compare two branches' word members. Called by Ns_Index* 
 *
 * Results:
 *	0 if equal, -1 if left is greater; 1 if right is greater 
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
CmpBranches(Branch **leftPtrPtr, Branch **rightPtrPtr)
{
    return strcmp((*leftPtrPtr)->word, (*rightPtrPtr)->word);
}


/*
 *----------------------------------------------------------------------
 *
 * CmpBranches --
 *
 *	Compare a branch's word to a passed-in key; called by 
 *	Ns_Index*.
 *
 * Results:
 *	0 if equal, -1 if left is greater; 1 if right is greater.
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

static int
CmpKeyWithBranch(char *key, Branch ** branchPtrPtr)
{
    return strcmp(key, (*branchPtrPtr)->word);
}


/*
 *----------------------------------------------------------------------
 *
 * BranchDestroy --
 *
 *	Free a branch structure 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	Will free memory. 
 *
 *----------------------------------------------------------------------
 */

static void
BranchDestroy(Branch *branchPtr)
{
    ns_free(branchPtr->word);
    TrieDestroy(&branchPtr->node);
    ns_free(branchPtr);
}


/*
 *----------------------------------------------------------------------
 *
 * TrieInit --
 *
 *	Initialize a Trie data structure with 25 branches and set the 
 *	Cmp functions for Ns_Index*. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	The trie is initialized and memory is allocated; memory is 
 *	allocated. 
 *
 *----------------------------------------------------------------------
 */

static void
TrieInit(Trie *triePtr)
{
    Ns_IndexInit(&triePtr->branches, 25,
		 (int (*) (const void *, const void *)) CmpBranches,
		 (int (*) (const void *, const void *)) CmpKeyWithBranch);
    triePtr->indexnode = NULL;
}


/*
 *----------------------------------------------------------------------
 *
 * TrieAdd --
 *
 *	Add something to the Trie data structure, usually to the 
 *	variable urlspace. 
 *
 *	seq is a null-delimited string of words, terminated with
 *	two nulls.
 *	id is allocated with Ns_UrlSpecificAlloc.
 *	flags is a bitmask; optionally OR NS_OP_NODELETE,
 *	NS_OP_NOINHERIT for desired behavior.
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	Memory is allocated. If a node is found and the 
 *	NS_OP_NODELETE is not set, the current node's data is deleted. 
 *
 *----------------------------------------------------------------------
 */

static void
TrieAdd(Trie *triePtr, char *seq, int id, void *data, int flags,
	void (*deletefunc) (void *))
{
    if (*seq == '\0') {
        Node *nodePtr;

        /*
	 * The entire sequence has been traversed, creating a branch
	 * for each word. Now it is time to make a Node.
	 *
	 * First, allocate a new node or find a matching one in existance.
	 */
	
        if (triePtr->indexnode == NULL) {
            triePtr->indexnode = IndexNodeCreate();
            nodePtr = NULL;
        } else {
            nodePtr = Ns_IndexFind(triePtr->indexnode, (void *) id);
        }

        if (nodePtr == NULL) {

	    /*
	     * Create and initialize a new node.
	     */
	    
            nodePtr = ns_malloc(sizeof(Node));
            nodePtr->id = id;
            Ns_IndexAdd(triePtr->indexnode, nodePtr);
            nodePtr->dataInherit = NULL;
            nodePtr->dataNoInherit = NULL;
            nodePtr->deletefuncInherit = NULL;
            nodePtr->deletefuncNoInherit = NULL;
        } else {

	    /*
	     * If NS_OP_NODELETE is NOT set, then delete the current node
	     * because one already exists.
	     */
	    
            if ((flags & NS_OP_NODELETE) == 0) {
                if ((flags & NS_OP_NOINHERIT) != 0) {
                    if (nodePtr->deletefuncNoInherit != NULL) {
                        (*nodePtr->deletefuncNoInherit)
			    (nodePtr->dataNoInherit);
                    }
                } else {
                    if (nodePtr->deletefuncInherit != NULL) {
                        (*nodePtr->deletefuncInherit) (nodePtr->dataInherit);
                    }
                }
            }
        }

        if (flags & NS_OP_NOINHERIT) {
            nodePtr->dataNoInherit = data;
            nodePtr->deletefuncNoInherit = deletefunc;
        } else {
            nodePtr->dataInherit = data;
            nodePtr->deletefuncInherit = deletefunc;
        }
    } else {
        Branch *branchPtr;

	/*
	 * We are parsing the middle of a sequence, such as "foo" in:
	 * "server1\0GET\0foo\0*.html\0"
	 *
	 * Create a new branch and recurse to add the next word in the
	 * sequence.
	 */

	branchPtr = Ns_IndexFind(&triePtr->branches, seq);
        if (branchPtr == NULL) {
            branchPtr = ns_malloc(sizeof(Branch));
            branchPtr->word = ns_strdup(seq);
            TrieInit(&branchPtr->node);

            Ns_IndexAdd(&triePtr->branches, branchPtr);
        }
        TrieAdd(&branchPtr->node, seq + strlen(seq) + 1, id, data, flags,
		deletefunc);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * TrieTrunc --
 *
 *	Wipes out all references to id. If id==-1 then it wipes out 
 *	everything. 
 *
 * Results:
 *	None 
 *
 * Side effects:
 *	Nodes and branches may be destroyed/freed. 
 *
 *----------------------------------------------------------------------
 */

static void
TrieTrunc(Trie *triePtr, int id)
{
    int n;

    n = Ns_IndexCount(&triePtr->branches);

    if (n > 0) {
        /*
	 * Loop over each branch and recurse.
	 */
	
        int             i;

        for (i = 0; i < n; i++) {
            Branch *branchPtr;

	    branchPtr = Ns_IndexEl(&triePtr->branches, i);
            TrieTrunc(&branchPtr->node, id);
        }
    }
    if (triePtr->indexnode != NULL) {
        if (id != -1) {
            Node *nodePtr;

	    /*
	     * Destroy just the node for this ID
	     */
	    
	    nodePtr = Ns_IndexFind(triePtr->indexnode, (void *) id);
            if (nodePtr != NULL) {
                NodeDestroy(nodePtr);
                Ns_IndexDel(triePtr->indexnode, nodePtr);
            }
        } else {

	    /*
	     * Destroy the whole index of nodes.
	     */
	    
            IndexNodeDestroy(triePtr->indexnode);
            triePtr->indexnode = NULL;
        }
    }
}


/*
 *----------------------------------------------------------------------
 *
 * TrieDestroy --
 *
 *	Delete an entire Trie.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Will free all the elements of the trie.
 *
 *----------------------------------------------------------------------
 */

static void
TrieDestroy(Trie *triePtr)
{
    int n;

    n = Ns_IndexCount(&triePtr->branches);

    if (n > 0) {
        int i;

	/*
	 * Loop over each branch and delete it
	 */
	
        for (i = 0; i < n; i++) {
            Branch *branchPtr;

	    branchPtr = Ns_IndexEl(&triePtr->branches, i);
            BranchDestroy(branchPtr);
        }
        Ns_IndexDestroy(&triePtr->branches);
    }
    if (triePtr->indexnode != NULL) {
        IndexNodeDestroy(triePtr->indexnode);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * TrieBranchTrunc --
 *
 *	Cut off a branch from a trie 
 *
 * Results:
 *	0 on success, -1 on failure 
 *
 * Side effects:
 *	Will delete a branch. 
 *
 *----------------------------------------------------------------------
 */

static int
TrieBranchTrunc(Trie *triePtr, char *seq, int id)
{
    if (*seq != '\0') {
        Branch *branchPtr;

	branchPtr = Ns_IndexFind(&triePtr->branches, seq);

	/*
	 * If this sequence exists, recursively delete it; otherwise
	 * return an error.
	 */
	
        if (branchPtr != NULL) {
            return TrieBranchTrunc(&branchPtr->node, seq + strlen(seq) + 1,
				   id);
        } else {
            return -1;
        }
    } else {
	/*
	 * The end of the sequence has been reached. Finish up the job
	 * and return success.
	 */
	
        TrieTrunc(triePtr, id);
        return 0;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * TrieFind --
 *
 *	Find a node in a trie matching a sequence.
 *
 * Results:
 *	Return the appropriate node's data.
 *
 * Side effects:
 *	The depth variable will be set-by-reference to the depth of
 *	the returned node. If no node is set, it will not be changed.
 *
 *----------------------------------------------------------------------
 */

static void *
TrieFind(Trie *triePtr, char *seq, int id, int *depthPtr)
{
    void *data;
    int   ldepth;

    data = NULL;
    ldepth = *depthPtr;
    if (triePtr->indexnode != NULL) {
        Node *nodePtr;

	/*
	 * We've reached a trie with an indexnode, which means that our
	 * data may be here. (If node is null that means that there
	 * is data at this branch, but not with this particular ID).
	 */

	nodePtr = Ns_IndexFind(triePtr->indexnode, (void *) id);
	
        if (nodePtr != NULL) {
            if ((*seq == '\0') && (nodePtr->dataNoInherit != NULL)) {
                data = nodePtr->dataNoInherit;
            } else {
                data = nodePtr->dataInherit;
            }
        }
    }
    if (*seq != '\0') {
        Branch *branchPtr;

	/*
	 * We have not yet reached the end of the sequence, so
	 * recurse if there are any sub-branches
	 */

	branchPtr = Ns_IndexFind(&triePtr->branches, seq);
        ldepth += 1;
        if (branchPtr != NULL) {
            void *p = TrieFind(&branchPtr->node, seq + strlen(seq) + 1, id,
			       &ldepth);

            if (p != NULL) {
                data = p;
                *depthPtr = ldepth;
            }
        }
    }
    
    return data;
}


/*
 *----------------------------------------------------------------------
 *
 * TrieFindExact --
 *
 *	Similar to TrieFind, but will not do inheritance.
 *      if (flags & NS_OP_NOINHERIT) then data set with
 *	that flag will be returned; otherwise only data set without that
 *	flag will be returned.
 *
 * Results:
 *	See TrieFind.
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

static void *
TrieFindExact(Trie *triePtr, char *seq, int id, int flags)
{
    void *data;

    data = NULL;
    if (*seq != '\0') {
        Branch *branchPtr;

	/*
	 * We have not reached the end of the sequence yet, so
	 * we must recurse.
	 */

	branchPtr = Ns_IndexFind(&triePtr->branches, seq);
        if (branchPtr != NULL) {
            data = TrieFindExact(&branchPtr->node, seq + strlen(seq) + 1, id,
                                 flags);
        }
    } else if (triePtr->indexnode != NULL) {
        Node *nodePtr;

	/*
	 * We reached the end of the sequence. Grab the data from
	 * this node. If the flag specifies NOINHERIT, then return
	 * the non-inheriting data, otherwise return the inheriting
	 * data.
	 */

	nodePtr = Ns_IndexFind(triePtr->indexnode, (void *) id);
        if (nodePtr != NULL) {
            if (flags & NS_OP_NOINHERIT) {
                data = nodePtr->dataNoInherit;
            } else {
                data = nodePtr->dataInherit;
            }
        }
    }
    
    return data;
}


/*
 *----------------------------------------------------------------------
 *
 * TrieDelete --
 *
 *	Delete a url, defined by a sequence, from a trie.
 *
 *	The NS_OP_NOINHERIT bit may be set in flags to use
 *	noninheriting data; NS_OP_NODELETE may be set to
 *	skip calling the delete function.
 *
 * Results:
 *	A pointer to the now-deleted data. 
 *
 * Side effects:
 *	Data may be deleted. 
 *
 *----------------------------------------------------------------------
 */

static void *
TrieDelete(Trie *triePtr, char *seq, int id, int flags)
{
    void *data;

    data = NULL;
    if (*seq != '\0') {
        Branch *branchPtr;

	/*
	 * We have not yet reached the end of the sequence. So
	 * recurse.
	 */

	branchPtr = Ns_IndexFind(&triePtr->branches, seq);
        if (branchPtr != NULL) {
            data = TrieDelete(&branchPtr->node, seq + strlen(seq) + 1, id,
			      flags);
        }
    } else if (triePtr->indexnode != NULL) {
        Node *nodePtr;

	/*
	 * We've reached the end of the sequence; if a node exists for
	 * this ID then delete the inheriting/noninheriting data (as
	 * specified in flags) and call the delte func if requested.
	 * The data will be set to null either way.
	 */

	nodePtr = Ns_IndexFind(triePtr->indexnode, (void *) id);
        if (nodePtr != NULL) {
            if (flags & NS_OP_NOINHERIT) {
                data = nodePtr->dataNoInherit;
                nodePtr->dataNoInherit = NULL;
                if (nodePtr->deletefuncNoInherit != NULL) {
                    if (!(flags & NS_OP_NODELETE)) {
                        (*nodePtr->deletefuncNoInherit) (data);
                    }
                    nodePtr->deletefuncNoInherit = NULL;
                }
            } else {
                data = nodePtr->dataInherit;
                nodePtr->dataInherit = NULL;
                if (nodePtr->deletefuncInherit != NULL) {
                    if (!(flags & NS_OP_NODELETE)) {
                        (*nodePtr->deletefuncInherit) (data);
                    }
                    nodePtr->deletefuncInherit = NULL;
                }
            }
        }
    }
    
    return data;
}

#ifndef __URLSPACE_OPTIMIZE__

/*
 *----------------------------------------------------------------------
 *
 * CmpChannels --
 *
 *	Compare the filters of two channels. 
 *
 * Results:
 *	0: Not the case that one contains the other OR they both 
 *	contain each other; 1: left contains right; -1: right contans 
 *	left. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

static int
CmpChannels(Channel **leftPtrPtr, Channel **rightPtrPtr)
{
    int lcontainsr, rcontainsl;

    lcontainsr = Tcl_StringMatch((*rightPtrPtr)->filter,
				 (*leftPtrPtr)->filter);
    rcontainsl = Tcl_StringMatch((*leftPtrPtr)->filter,
				 (*rightPtrPtr)->filter);

    if (lcontainsr && rcontainsl) {
        return 0;
    } else if (lcontainsr) {
        return 1;
    } else if (rcontainsl) {
        return -1;
    } else {
        return 0;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * CmpKeyWithChannel --
 *
 *	Compare a key to a channel's filter 
 *
 * Results:
 *	0: Not the case that one contains the other OR they both 
 *	contain each other; 1: key contains filter; -1: filter 
 *	contains key 
 *
 * Side effects:
 *	None 
 *
 *----------------------------------------------------------------------
 */

static int
CmpKeyWithChannel(char *key, Channel **channelPtrPtr)
{
    int lcontainsr, rcontainsl;

    lcontainsr = Tcl_StringMatch((*channelPtrPtr)->filter, key);
    rcontainsl = Tcl_StringMatch(key, (*channelPtrPtr)->filter);
    if (lcontainsr && rcontainsl) {
        return 0;
    } else if (lcontainsr) {
        return 1;
    } else if (rcontainsl) {
        return -1;
    } else {
	/*
	 * Neither is the case
	 */

        return 0;
    }
}
#endif


/*
 *----------------------------------------------------------------------
 *
 * CmpChannelsAsStrings --
 *
 *	Compare the filters of two channels.
 *
 * Results:
 *	Same as strcmp.
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

static int
CmpChannelsAsStrings(Channel **leftPtrPtr, Channel **rightPtrPtr)
{
    return strcmp((*leftPtrPtr)->filter, (*rightPtrPtr)->filter);
}


/*
 *----------------------------------------------------------------------
 *
 * CmpKeyWithChannelAsStrings --
 *
 *	Compare a string key to a channel's filter 
 *
 * Results:
 *	Same as strcmp. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

static int
CmpKeyWithChannelAsStrings(char *key, Channel **channelPtrPtr)
{
    return strcmp(key, (*channelPtrPtr)->filter);
}


/*
 *----------------------------------------------------------------------
 *
 * JunctionInit --
 *
 *	Initialize a junction.
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	Will set up the index in a junction. 
 *
 *----------------------------------------------------------------------
 */

static void
JunctionInit(Junction *juncPtr)
{
#ifndef __URLSPACE_OPTIMIZE__
    Ns_IndexInit(&juncPtr->byuse, 5,
        (int (*) (const void *, const void *)) CmpChannels,
        (int (*) (const void *, const void *)) CmpKeyWithChannel);
#endif
    Ns_IndexInit(&juncPtr->byname, 5,
        (int (*) (const void *, const void *)) CmpChannelsAsStrings,
        (int (*) (const void *, const void *)) CmpKeyWithChannelAsStrings);
}


/*
 *----------------------------------------------------------------------
 *
 * JunctionBranchTrunc --
 *
 *	Truncate a branch, defined by a sequence and ID, in a 
 *	junction 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	See TrieBranchTrunc 
 *
 *----------------------------------------------------------------------
 */

static void
JunctionBranchTrunc(Junction *juncPtr, char *seq, int id)
{
    int i;
    int n;

    /*
     * Loop over every channel in a junction and truncate the sequence in
     * each.
     */
    
#ifndef __URLSPACE_OPTIMIZE__
    n = Ns_IndexCount(&juncPtr->byuse);
    for (i = 0; i < n; i++) {
        Channel *channelPtr = Ns_IndexEl(&juncPtr->byuse, i);
        TrieBranchTrunc(&channelPtr->trie, seq, id);
    }
#else
    n = Ns_IndexCount(&juncPtr->byname);
    for (i = (n - 1); i >= 0; i--) {
        Channel *channelPtr = Ns_IndexEl(&juncPtr->byname, i);
        TrieBranchTrunc(&channelPtr->trie, seq, id);
    }
#endif
}


/*
 *----------------------------------------------------------------------
 *
 * JunctionAdd --
 *
 *	This function is called from Ns_UrlSpecificSet which is 
 *	usually called from Ns_RegisterRequest, 
 *	Ns_RegisterProxyRequest, InitAliases for mapping aliases, and 
 *	the nsperm functions TribeAlloc and Ns_AddPermission for 
 *	adding permissions. It adds a sequence, terminating in a new 
 *	node, to a junction.
 *
 *	Flags may be a bit-combination of NS_OP_NOINHERIT, NS_OP_NODELETE.
 *	NOINHERIT sets the data as noninheriting, so only an exact sequence
 *	will match in the future; NODELETE means that if a node already
 *	exists with this sequence/ID it will not be deleted but replaced.
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	Modifies seq, assuming
 *	seq = "handle\0method\0urltoken\0urltoken\0..\0\0\"
 *
 *----------------------------------------------------------------------
 */

static void
JunctionAdd(Junction *juncPtr, char *seq, int id, void *data, int flags,
    void (*deletefunc) (void *))
{
    Channel    *channelPtr;
    Ns_DString  dsWord;
    char       *p;
    int         l;
    int         depth;

    depth = 0;

    /*
     * Find out how deep the sequence is, and position p at the
     * beginning of the last word in the sequence.
     */
    
    for (p = seq; p[l = strlen(p) + 1] != '\0'; p += l) {
        depth++;
    }

    Ns_DStringInit(&dsWord);

    /*
     * If it's a valid sequence that has a wildcard in its last element,
     * append the whole string to dsWord, then cut off the last word from
     * p.
     * Otherwise, set dsWord to "*" because there is an implicit * wildcard
     * at the end of URLs like /foo/bar
     *
     * dsWord will eventually be used to set or find&reuse a channel filter.
     */
    
    if ((p != NULL) && (depth > 1) && (strchr(p, '*') || strchr(p, '?'))) {
        Ns_DStringAppend(&dsWord, p);
        *p = '\0';
    } else {
        Ns_DStringAppend(&dsWord, "*");
    }

    /*
     * Find a channel whose filter matches what the filter on this URL
     * should be.
     */
    
    channelPtr = Ns_IndexFind(&juncPtr->byname, dsWord.string);

    /* 
     * If no channel is found, create a new channel and add it to the
     * list of channels in the junction.
     */

    if (channelPtr == NULL) {
        channelPtr = (Channel *) ns_malloc(sizeof(Channel));
        channelPtr->filter = ns_strdup(dsWord.string);
        TrieInit(&channelPtr->trie);

#ifndef __URLSPACE_OPTIMIZE__
        Ns_IndexAdd(&juncPtr->byuse, channelPtr);
#endif
        Ns_IndexAdd(&juncPtr->byname, channelPtr);
    }
    
    /* 
     * Now we need to create a sequence of branches in the trie (if no
     * appropriate sequence already exists) and a node at the end of it.
     * TrieAdd will do that.
     */
    
    TrieAdd(&channelPtr->trie, seq, id, data, flags, deletefunc);

    Ns_DStringFree(&dsWord);
}



/*
 *----------------------------------------------------------------------
 *
 * JunctionFind --
 *
 *	Locate a node for a given sequence in a junction.
 *	As usual sequence is "handle\0method\0urltoken\0...\0\0".
 *
 *	The "fast" boolean switch makes it do strcmp instead of
 *	Tcl string matches on the filters. Not useful for wildcard
 *	matching.
 *
 * Results:
 *	User data
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

static void *
JunctionFind(Junction *juncPtr, char *seq, int id, int fast)
{
    char *p;
    int   l;
    int   i, n;
    void *data;
    int   depth;

    n = 0;

    /*
     * After this loop, p will point at the last element in the sequence.
     * n will be the number of elements in the sequence.
     */
    
    for (p = seq; p[l = strlen(p) + 1] != '\0'; p += l) {
        n++;
    }

    if (n < 2) {
        /*
	 * If there are fewer than 2 elements then advance p to the
	 * end of the string.
	 */
        p += strlen(p) + 1;
    }

    /*
     * Check filters from most restrictive to least restrictive
     */
    
    data = NULL;
#ifndef __URLSPACE_OPTIMIZE__
    l = Ns_IndexCount(&juncPtr->byuse);
#else
    l = Ns_IndexCount(&juncPtr->byname);
#endif

#ifdef DEBUG
    if (n >= 2) {
        fprintf(stderr, "Checking Id=%d, Seq=", id);
        PrintSeq(seq);
        fputs("\n", stderr);
    }
#endif

    /* 
     * For __URLSPACE_OPTIMIZE__
     * Basically if we use the optimize, let's reverse the order
     * by which we search because the byname is in "almost" exact
     * reverse lexicographical order.
     *
     * Loop over all the channels in the index.
     */
    
#ifndef __URLSPACE_OPTIMIZE__
    for (i = 0; i < l; i++) {
        Channel *channelPtr = Ns_IndexEl(&juncPtr->byuse, i);
#else
    for (i = (l - 1); i >= 0; i--) {
        Channel *channelPtr = Ns_IndexEl(&juncPtr->byname, i);
#endif
        int doit;

	if (fast) {
	    doit = !strcmp(p, channelPtr->filter);
	} else {
	    doit = Tcl_StringMatch(p, channelPtr->filter);
	}
	if (doit) {
	    /*
	     * We got here because this url matches the filter
	     * (for example, it's *.adp).
	     */
	    
            if (data == NULL) {
		/*
		 * Nothing has been found so far. Traverse the channel
		 * and find the node; set data to that. Depth will be
		 * set to the level of the node.
		 */
		
                depth = 0;
                data = TrieFind(&channelPtr->trie, seq, id, &depth);
            } else {
                void *candidate;
                int   cdepth;

		/*
		 * Let's see if this channel has a node that also matches
		 * the sequence but is more specific (has a greater depth)
		 * that the previously found node.
		 */
		
                cdepth = 0;
                candidate = TrieFind(&channelPtr->trie, seq, id, &cdepth);
                if ((candidate != NULL) && (cdepth > depth)) {
                    data = candidate;
                    depth = cdepth;
                }
            }
        }

#ifdef DEBUG
        if (n >= 2) {
            if (data == NULL) {
                fprintf(stderr, "Channel %s: No match\n",
                    channelPtr->filter);
            } else {
                fprintf(stderr, "Channel %s: depth=%d, data=%p\n",
                    channelPtr->filter, depth, data);
            }
        }
#endif
    }

#ifdef DEBUG
    if (n >= 2) {
        fprintf(stderr, "Done.\n");
    }
#endif
    
    return data;
}


/*
 *----------------------------------------------------------------------
 *
 * JunctionFindExact --
 *
 *	Find a node in a junction that exactly matches a sequence.
 *
 * Results:
 *	User data.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void *
JunctionFindExact(Junction *juncPtr, char *seq, int id, int flags, int fast)
{
    char *p;
    int   l;
    int   i;
    int   depth;
    void *data;

    depth = 0;

    /*
     * Set p to the last element of the sequence, and
     * depth to the number of elements in the sequence.
     */
    
    for (p = seq; p[l = strlen(p) + 1] != '\0'; p += l) {
        depth++;
    }

    data = NULL;

    /*
     * First, loop through all the channels that have non-"*"
     * filters looking for an exact match
     */

#ifndef __URLSPACE_OPTIMIZE__
    l = Ns_IndexCount(&juncPtr->byuse);

    for (i = 0; i < l; i++) {
        Channel *channelPtr = Ns_IndexEl(&juncPtr->byuse, i);
#else
    l = Ns_IndexCount(&juncPtr->byname);

    for (i = (l - 1); i >= 0; i--) {
        Channel *channelPtr = Ns_IndexEl(&juncPtr->byname, i);
#endif
        if (strcmp(p, channelPtr->filter) == 0) {
	    /*
	     * The last element of the sequence exactly matches the
	     * filter, so this is the one. Wipe out the last word and
	     * return whatever node coems out of TrieFindExact.
	     */
	    
            *p = '\0';
            data = TrieFindExact(&channelPtr->trie, seq, id, flags);
            goto done;
        }
    }
    
    /*
     * Now go to the channel with the "*" filter and look there for 
     * an exact match:
     */
    
#ifndef __URLSPACE_OPTIMIZE__
    for (i = 0; i < l; i++) {
        Channel *channelPtr = Ns_IndexEl(&juncPtr->byuse, i);
#else
    for (i = (l - 1); i >= 0; i--) {
        Channel *channelPtr = Ns_IndexEl(&juncPtr->byname, i);
#endif
        if (strcmp("*", channelPtr->filter) == 0) {
            data = TrieFindExact(&channelPtr->trie, seq, id, flags);
            break;
        }
    }
    
  done:
    
    return data;
}


/*
 *----------------------------------------------------------------------
 *
 * JunctionDelete --
 *
 *	Delete a node from a junction matching a sequence 
 *
 * Results:
 *	A pointer to the deleted node 
 *
 * Side effects:
 *	The node will be deleted if NS_OP_NODELETE isn't set in flags 
 *
 *----------------------------------------------------------------------
 */

static void *
JunctionDelete(Junction *juncPtr, char *seq, int id, int flags)
{
    char *p;
    int   l;
    int   i;
    int   depth;
    void *data;

    /*
     * Set p to the last element of the sequence, and
     * depth to the number of elements in the sequence.
     */

    depth = 0;
    for (p = seq; p[l = strlen(p) + 1] != '\0'; p += l) {
        depth++;
    }

    data = NULL;

#ifndef __URLSPACE_OPTIMIZE__
    l = Ns_IndexCount(&juncPtr->byuse);
    for (i = 0; (i < l) && (data == NULL); i++) {
        Channel *channelPtr = Ns_IndexEl(&juncPtr->byuse, i);
#else
    l = Ns_IndexCount(&juncPtr->byname);
    for (i = (l - 1); (i >= 0) && (data == NULL); i--) {
        Channel *channelPtr = Ns_IndexEl(&juncPtr->byname, i);
#endif
        if ((depth == 2) && (strcmp(p, channelPtr->filter) == 0)) {
	    /*
	     * This filter exactly matches the last element of the
	     * sequence, so get the node and delete it. (This is
	     * server-specific data because depth is 2).
	     */
	    
            *p = '\0';
            data = TrieFindExact(&channelPtr->trie, seq, id, flags);
            if (data != NULL) {
                TrieDelete(&channelPtr->trie, seq, id, flags);
            }
        } else if (Tcl_StringMatch(p, channelPtr->filter)) {
	    /*
	     * The filter matches, so get the node and delete it.
	     */
	    
            data = TrieFindExact(&channelPtr->trie, seq, id, flags);
            if (data != NULL) {
                TrieDelete(&channelPtr->trie, seq, id, flags);
            }
        }
    }
    
    return data;
}


/*
 *----------------------------------------------------------------------
 *
 * MkSeq --
 *
 *	Build a "sequence" out of a server/method/url; turns it into 
 *	"server\0method\0urltoken\0...\0\0" 
 *
 * Results:
 *	None 
 *
 * Side effects:
 *	Sequence goes into ds 
 *
 *----------------------------------------------------------------------
 */

static void
MkSeq(Ns_DString *dsPtr, char *server, char *method, char *url)
{
    if (server == NULL) {
	server = nsServer;
    }
    if ((method != NULL) && (url != NULL)) {
        char *p;
        int   done;

	/*
	 * It is URLspecific data, not serverspecific data
	 * if we get here.
	 */
	
        Ns_DStringNAppend(dsPtr, server, strlen(server) + 1);
        Ns_DStringNAppend(dsPtr, method, strlen(method) + 1);

	/*
	 * Loop over each directory in the URL and turn the slashes
	 * into nulls.
	 */
	
        done = 0;
        while (!done && *url != '\0') {
            if (*url != '/') {
                int l;

                p = strchr(url, '/');
                if (p != NULL) {
                    l = p - url;
                } else {
                    l = strlen(url);
                    done = 1;
                }

                Ns_DStringNAppend(dsPtr, url, l++);
                Ns_DStringNAppend(dsPtr, "\0", 1);
                url += l;
            } else {
                url++;
            }
        }

	/*
	 * Put another null on the end to mark the end of the
	 * string.
	 */
	
        Ns_DStringNAppend(dsPtr, "\0", 1);
    } else {
        assert((method == NULL) && (url == NULL));

	/*
	 * This is Server-specific data, so there's only going to
	 * be one element.
	 */
	
        Ns_DStringNAppend(dsPtr, server, strlen(server) + 1);
        Ns_DStringNAppend(dsPtr, "\0", 1);
    }
}

#ifdef DEBUG

/*
 *----------------------------------------------------------------------
 *
 * indentspace --
 *
 *	Print n spaces.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Will print to stderr.
 *
 *----------------------------------------------------------------------
 */

static void
indentspace(int n)
{
    int i;

    fputc('\n', stderr);
    for (i = 0; i < n; i++) {
        fputc(' ', stderr);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * PrintTrie --
 *
 *	Output the trie to standard error.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Will write to stderr. 
 *
 *----------------------------------------------------------------------
 */

static void
PrintTrie(Trie *triePtr, int indent)
{
    int i;

    indentspace(indent);
    fprintf(stderr, "Branches:");
    for (i = 0; i < (&(triePtr->branches))->n; i++) {
        Branch *branch;

        branch = (Branch *) Ns_IndexEl(&(triePtr->branches), i);
        indentspace(indent + 2);
        fprintf(stderr, "(%s):", branch->word);
        PrintTrie(&(branch->node), indent + 4);
    }
    indentspace(indent);
    fprintf(stderr, "IndexNodes:");
    if (triePtr->indexnode != NULL) {
        for (i = 0; i < triePtr->indexnode->n; i++) {
            Node *nodePtr;

            nodePtr = (Node *) Ns_IndexEl(triePtr->indexnode, i);
            indentspace(indent + 2);
            if (nodePtr->dataInherit != NULL) {
                fprintf(stderr, "(Id: %d, inherit): %p", 
                        nodePtr->id, nodePtr->dataInherit);
            }
            if (nodePtr->dataNoInherit != NULL) {
                fprintf(stderr, "(Id: %d, noinherit): %p",
                        nodePtr->id, nodePtr->dataNoInherit);
            }
        }
    }
}


/*
 *----------------------------------------------------------------------
 *
 * PrintJunction --
 *
 *	Print a junction to std error. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	Will write to stderr. 
 *
 *----------------------------------------------------------------------
 */

static void
PrintJunction(Junction *junctionPtr)
{
    int i;

    fprintf(stderr, "Junction:");

#ifndef __URLSPACE_OPTIMIZE__
    for (i = 0; i < (&(junctionPtr->byuse))->n; i++) {
        Channel *channelPtr;
        channelPtr = (Channel *) Ns_IndexEl(&(junctionPtr->byuse), i);
#else
    for (i = ((&(junctionPtr->byname))->n - 1); i >= 0; i--) {
        Channel *channelPtr;
        channelPtr = (Channel *) Ns_IndexEl(&(junctionPtr->byname), i);
#endif
        fprintf(stderr, "\n  Channel[%d]:\n", i);
        fprintf(stderr, "    Filter: %s\n", channelPtr->filter);
        fprintf(stderr, "    Trie:");
        PrintTrie(&(channelPtr->trie), 4);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * PrintSeq --
 *
 *	Print a null-delimited sequence to stderr.
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	Will write to stderr. 
 *
 *----------------------------------------------------------------------
 */

static void
PrintSeq(char *seq)
{
    char *p;

    for (p = seq; *p != '\0'; p += strlen(p) + 1) {
        if (p != seq) {
            fputs(", ", stderr);
        }
        fputs(p, stderr);
    }
}

#endif
