/*
**	URL DATA TREE STRUCTURE
**
**	(c) COPYRIGHT MIT 1995.
**	Please first read the full copyright statement in the file COPYRIGH.
**	@(#) $Id: HTUTree.c,v 2.3 1996/08/24 18:10:22 frystyk Exp $
**
** AUTHORS:
**	HFN	Henrik Frystyk Nielsen <frystyk@w3.org>
**
**	An infobase is a tree structure where we can store all the information
**	we know about a remote server. Typically, each remote server will
**	have its own infobase which gets richer as we get to know more about
**	the remote web site.
**
**	This module maintains an URL information database
**	which can contain information stored by filters. This can for
**	example be used to store challenges received from remote server.
**	A infobase has the advantage that it can be searched using URLs _or_
**	using realms. The letter is most useful to "guess" information
**	about a remote URL that we haven't seen before
*/

/* Library include files */
#include "sysdep.h"
#include "WWWUtil.h"
#include "HTUTree.h"					 /* Implemented here */

#define TREE_TIMEOUT		43200L	     /* Default tree timeout is 12 h */
#define HASH_SIZE 		101			  /* Arbitrary prime */

struct _HTUTree {			  /* Server URL info base */
    char *		name;
    char *		host;
    int			port;

    HTList *		templates;	  /* List of templates for this tres */
    HTList * 		realms;		     /* List of realms for this tree */

    time_t		created;	     /* Creation time of this object */
    HTUTree_gc * 	gc;			/* Contect garbage collector */
};

struct _HTURealm {			  		  /* Realm specifics */
    char *		realm;
    void *		context;
    HTUTemplate *	tm_ptr;
};

struct _HTUTemplate {				 /* Hierarchical information */
    char *		tmplate;
    HTURealm *	       	rm_ptr;
};

PRIVATE HTList ** InfoTable = NULL;    		/* List of information bases */
PRIVATE time_t UTreeTimeout = TREE_TIMEOUT;

/* ------------------------------------------------------------------------- */

/*
**	Create a new realm
**	Returns new object or NULL if error
*/
PRIVATE HTURealm * HTUTree_newRealm (HTUTree * tree, const char * realm,
				     void * context)
{
    if (tree) {
	HTURealm * me;
	if ((me = (HTURealm *) HT_CALLOC(1, sizeof(HTURealm))) == NULL)
	    HT_OUTOFMEM("HTURealm_new");
	if (realm) StrAllocCopy(me->realm, realm);
	me->context = context;
	HTList_addObject(tree->realms, (void *) me);
	return me;
    }
    return NULL;
}

/*
**	Delete a realm. We call the scheme gc callback to free the opaque
**	context object.
*/
PRIVATE BOOL HTUTree_deleteRealm (HTUTree * tree, HTURealm * me)
{
    if (tree && me) {
	if (tree->gc && me->context) (*tree->gc)(me->context);
	HTList_removeObject(tree->realms, (void *) me);
	HT_FREE(me->realm);
	HT_FREE(me);
	return YES;
    }
    return NO;
}

/*
**	Find a realm
*/
PRIVATE HTURealm * HTUTree_findRealm (HTUTree * tree, const char * realm)
{
    if (tree && tree->realms && realm) {
	HTList * cur = tree->realms;
	HTURealm * pres;
	while ((pres = (HTURealm *) HTList_nextObject(cur))) {
	    if (!strcmp(pres->realm, realm)) {
		if (CORE_TRACE)
		    HTTrace("URL Node.... Realm `%s\' found\n", realm);
		return pres;
	    }
	}
    }
    return NULL;
}


/*
**	Create a new template and add to URL tree
**	Returns new object or NULL if error
*/
PRIVATE HTUTemplate * HTUTree_newTemplate (HTUTree * tree,const char * tmplate)
{
    if (tree && tmplate) {
	HTUTemplate * me;
	if ((me = (HTUTemplate *) HT_CALLOC(1, sizeof(HTUTemplate))) == NULL)
	    HT_OUTOFMEM("HTUTemplate_new");
	StrAllocCopy(me->tmplate, tmplate);
	HTList_addObject(tree->templates, (void *) me);
	return me;
    }
    return NULL;
}

/*
**	Delete a template
*/
PRIVATE BOOL HTUTree_deleteTemplate (HTUTree * tree, HTUTemplate * me)
{
    if (tree && me) {
	HTList_removeObject(tree->templates, (void *) me);
	HT_FREE(me->tmplate);
	HT_FREE(me);
	return YES;
    }
    return NO;
}

/*
**	Find a template
*/
PRIVATE HTUTemplate * HTUTree_findTemplate (HTUTree * tree, const char * path)
{
    if (tree && tree->templates && path) {
	HTUTemplate * pres;
	HTList * cur = tree->templates;
	while ((pres = (HTUTemplate *) HTList_nextObject(cur))) {
	    if (HTStrMatch(pres->tmplate, path)) {
		if (CORE_TRACE)
		    HTTrace("URL Node.... Found template `%s\' for for `%s\'\n",
			    pres->tmplate, path);
		return pres;
	    }
	}
    }
    return NULL;
}

/*
**	Search a URL Tree for a matching template or realm
**	Return the opaque context object found or NULL if none
**	Please regard this as a first simple attempt - it can be done
**	much more efficient!
*/
PUBLIC void * HTUTree_findNode (HTUTree * tree,
				const char * realm, const char * path)
{
    HTURealm * rm = HTUTree_findRealm(tree, realm);
    if (rm)
	return rm->context;
    else {
	HTUTemplate * tm = HTUTree_findTemplate(tree, path);
	if (tm) return tm->rm_ptr ? tm->rm_ptr->context : NULL;
    }
    if (CORE_TRACE) HTTrace("URL Node.... Not found\n");
    return NULL;
}

/*
**	Add a new node (a template and a realm) to the tree
*/
PUBLIC BOOL HTUTree_addNode (HTUTree * tree,
			     const char * realm, const char * path,
			     void * context)
{
    if (tree) {
	if (realm && path) { 		       /* If both a path and a realm */
	    HTUTemplate * new_template = HTUTree_newTemplate(tree, path);
	    HTURealm * new_realm = HTUTree_newRealm(tree, realm, context);
	    new_realm->tm_ptr = new_template;
	    new_template->rm_ptr = new_realm;
	    return YES;
	} else if (realm) {				  /* If only a realm */
	    HTUTree_newRealm(tree, realm, context);
	    return YES;
	}
	if (CORE_TRACE)
	    HTTrace("URL Node.... At least realm must be present\n");
    }
    return NO;
}

/*
**	Replace node (insert a new context at the same node)
*/
PUBLIC BOOL HTUTree_replaceNode (HTUTree * tree,
				 const char * realm, const char * path,
				 void * context)
{
    HTURealm * rm = HTUTree_findRealm(tree, realm);
    if (!rm) {
	HTUTemplate * tm = HTUTree_findTemplate(tree, path);
	if (tm) rm = tm->rm_ptr;
    }
    if (rm) {
	if (tree->gc && rm->context) (*tree->gc)(rm->context);
	rm->context = context;
	return YES;
    }
    if (CORE_TRACE) HTTrace("URL Node.... Not found\n");
    return NO;
}

/*
**	Remove a node (a template and a realm) from the tree
*/
PUBLIC BOOL HTUTree_deleteNode (HTUTree * tree,
				const char * realm, const char * path)
{
    if (tree) {
	HTURealm * rm = HTUTree_findRealm(tree, realm);
	HTUTemplate * tm = rm ? rm->tm_ptr : HTUTree_findTemplate(tree, path);
	if (!rm) rm = tm ? tm->rm_ptr : NULL;
	HTUTree_deleteRealm(tree, rm);
	HTUTree_deleteTemplate(tree, tm);
	return YES;
    }
    return NO; 
}


PRIVATE BOOL delete_tree (HTUTree * tree)
{
    if (tree) {
	HTList * cur;

	/* Free all templates */
	if ((cur = tree->templates)) {
	    HTUTemplate * pres;
	    while ((pres = (HTUTemplate *) HTList_lastObject(cur)))
		HTUTree_deleteTemplate(tree, pres);
	    HTList_delete(tree->templates);
	}

	/* Free all nodes */
	if ((cur = tree->realms)) {
	    HTURealm * pres;
	    while ((pres = (HTURealm *) HTList_lastObject(cur)))
		HTUTree_deleteRealm(tree, pres);
	    HTList_delete(tree->realms);	    
	}

	HT_FREE(tree->name);
	HT_FREE(tree->host);
	HT_FREE(tree);
	return YES;
    }
    return NO;
}

/*
**	Find an existing URL Tree
**	Returns tree or NULL if error
*/
PRIVATE HTUTree * find_tree (const char * 	name,
			     const char *	host,
			     int		port,
			     HTList **		hashlist)
{
    HTUTree * pres = NULL;
    *hashlist = NULL;
    if (!name || !host) {
	if (CORE_TRACE) HTTrace("URL Tree.... Bad argument\n");
	return NULL;
    }

    /* Find a hash for this host */
    {
	int hash = 0;
	const char * ptr;
	for (ptr=host; *ptr; ptr++)
	    hash = (int) ((hash * 3 + (*(unsigned char *) ptr)) % HASH_SIZE);
	if (!InfoTable) {
	    if ((InfoTable = (HTList **) HT_CALLOC(HASH_SIZE,
						   sizeof(HTList *))) == NULL)
	        HT_OUTOFMEM("HTUTree_find");
	}
	if (!InfoTable[hash])
	    InfoTable[hash] = *hashlist = HTList_new();
	else
	    *hashlist = InfoTable[hash];
    }

    /* Search the existing list to see if we already have this entry */
    {
	HTList * cur = *hashlist;
	while ((pres = (HTUTree *) HTList_nextObject(cur))) {
	    if (!strcmp(pres->name, name) && !strcmp(pres->host, host) &&
		pres->port==port) {
		if (time(NULL) > pres->created + UTreeTimeout) {
		    if (CORE_TRACE)
			HTTrace("URL Tree.... Collecting URL Tree %p\n", pres);
		    HTList_removeObject(*hashlist, pres);
		    delete_tree(pres);
		    pres = NULL;
		}
		return pres;
	    }
	}
    }
    return NULL;
}

/*
**	Create a new URL Tree (or return an aready existing one)
**	Returns new object (or the one found) or NULL if error
*/
PUBLIC HTUTree * HTUTree_new (const char * 		name,
			      const char * 		host,
			      int 			port,
			      HTUTree_gc *	 	gc)
{
    if (name && host) {
	HTList * hashlist = NULL;
	HTUTree * pres = find_tree(name, host, port, &hashlist);

	/* If not found (or gc'ed) then create a new URL tree */
	if (!pres) {
	    if ((pres = (HTUTree *) HT_CALLOC(1, sizeof(HTUTree))) == NULL)
		HT_OUTOFMEM("HTUTree_new");
	    StrAllocCopy(pres->name, name);
	    StrAllocCopy(pres->host, host);
	    pres->port = (port > 0 ? port : 80);
	    pres->templates = HTList_new();
	    pres->realms = HTList_new();
	    pres->created = time(NULL);
	    pres->gc = gc;

	    /* Add the new URL tree to the hash table */
	    HTList_addObject(hashlist, (void *) pres);
	    if (CORE_TRACE)HTTrace("URL Tree.... Created %p with name `%s\'\n",
				   pres, pres->name);
	} else {
	    if (CORE_TRACE) HTTrace("URL Tree.... Found %p with name `%s\'\n",
				    pres, pres->name);
	}
	return pres;
    } else {
	if (CORE_TRACE) HTTrace("URL Tree.... Bad argument\n");
	return NULL;
    }
}

/*
**	Find a URL tree
*/
PUBLIC HTUTree * HTUTree_find (const char *	name,
			       const char * 	host,
			       int		port)
{
    if (name && host) {
	HTList * hashlist = NULL;
	HTUTree * pres = find_tree(name, host, port, &hashlist);
	if (CORE_TRACE) HTTrace("URL Tree.... did %sfind `%s\'\n",
				pres ? "" : "NOT ", name);
	return pres;
    } else {
	if (CORE_TRACE) HTTrace("URL Tree.... Bad augument\n");
    }
    return NULL;
}


/*
**	Delete a complete server tree and everything within it.
*/
PUBLIC BOOL HTUTree_delete (const char * 	name,
			    const char * 	host,
			    int			port)
{
    if (name && host) {
	HTList * hashlist = NULL;
	HTUTree * pres = find_tree(name, host, port, &hashlist);
	if (pres) {
	    HTList_removeObject(hashlist, pres);
	    delete_tree(pres);
	    if (CORE_TRACE) HTTrace("URL Tree.... deleted %p\n", pres);
	    return YES;
	}
    }
    return NO;
}

/*
**	Delete all URL Trees
*/
PUBLIC BOOL HTUTree_deleteAll (void)
{
    if (InfoTable) {
	int cnt;
	HTList * cur;
	for (cnt=0; cnt<HASH_SIZE; cnt++) {
	    if ((cur = InfoTable[cnt])) { 
		HTUTree * pres;
		while ((pres = (HTUTree *) HTList_nextObject(cur)))
		    delete_tree(pres);
	    }
	    HTList_delete(InfoTable[cnt]);
	}
	HT_FREE(InfoTable);
	return YES;
    }
    return NO;
}
