/*
 * db.c : Database routines for xarchie
 *
 * The functions in this file provide two distinct but related services.
 * First, they implement a hierarchical host/location/file database that
 * is used to store the results of the Prospero query. This database is
 * implemented as a hierarchical set of doubly-linked lists, with each
 * record containing information about the host/location/file and a
 * pointer to its list of sub-records. See "db.h" for details. Secondly,
 * these routines implement the mapping from the database to the browser
 * lists. That is, there are functions that, given a host/location/file
 * record, display its contents and the appropriate sub-records' contents
 * in the browser panes.
 *
 * George Ferguson, ferguson@cs.rochester.edu, 4 Sep 1991.
 *
 */
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Cardinals.h>
#include <X11/Xaw/List.h>
#include "xarchie.h"
#include "db.h"
#include "alert.h"

/*
 * Functions defined here:
 */
Database *newDb();
void clearDb();
HostEntry *addHost(), *lastHostEntry(), *findHostEntryFromString();
HostEntry *findHostEntryFromIndex();
LocEntry *addLoc(), *lastLocEntry(), *findLocEntryFromString();
LocEntry *findLocEntryFromIndex();
FileEntry *addFile(), *lastFileEntry(), *findFileEntryFromString();
FileEntry *findFileEntryFromIndex();
int findHostIndexFromEntry();
int findLocIndexFromEntry();
int findFileIndexFromEntry();
void displayHosts(), displayLocs(), displayFiles();
void setHostString(), setLocString(), setFileString();
void setHostListFromStrings();
void setLocListFromStrings();
void setFileListFromStrings();
void clearList();

/*
 * Data defined here:
 */
/*
 * These correspond to the highlighted items in the List widgets
 */
HostEntry *selectedHostEntry;
LocEntry *selectedLocEntry;
FileEntry *selectedFileEntry;
/*
 * These string arrays are needed since the only way to set a List
 * widget in X is to pass it an array of strings. That is, there's
 * no way to add items incrementally. Blech. Still, the arrays are
 * grown as needed, so these values can be way off, as they are.
 */
#define INIT_NUM_HOST_STRINGS 1
#define INIT_NUM_LOC_STRINGS  1
#define INIT_NUM_FILE_STRINGS 1
/* The arrays double in size as they grow */
#define REALLOC_INCR(num) (2*(num))

static char **hostStrings,**locStrings,**fileStrings;
static int numHostStrings,numLocStrings,numFileStrings;

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

Database *
newDb()
{
    Database *db;

    db = XtNew(Database);
    db->hostEntries = NULL;
    return(db);
}

void
clearDb(dbp)
Database *dbp;
{
    HostEntry *hostp,*nexthostp;
    LocEntry *locp,*nextlocp;
    FileEntry *filep,*nextfilep;

    if (dbp == DB_NULL) {
	alert0("DB error: attempt to clear NULL database\n");
	return;
    }
    for (hostp=dbp->hostEntries; hostp != HOST_NULL; hostp=nexthostp) {
	nexthostp = hostp->next;
        for (locp=hostp->locEntries; locp != LOC_NULL; locp=nextlocp) {
	    nextlocp = locp->next;
	    for (filep=locp->fileEntries; filep != FILE_NULL;
							filep=nextfilep) {
		nextfilep = filep->next;
		XtFree(filep->name);
		XtFree(filep);
	    }
	    XtFree(locp->linkpath);
	    XtFree(locp);
	}
	XtFree(hostp->hostname);
	XtFree(hostp);
    }
    dbp->hostEntries = HOST_NULL;
}
	
/*	-	-	-	-	-	-	-	-	*/

HostEntry *
addHostEntry(hostname,dbp,before)
char *hostname;
Database *dbp;
HostEntry *before;
{
    HostEntry *hp,*last;

    hp = XtNew(HostEntry);
    hp->hostname = (char *)NULL;
    hp->locEntries = LOC_NULL;
    hp->next = hp->prev = HOST_NULL;
    if (dbp == DB_NULL) {
	alert1("DB error: attempt to add host \"%.180s\" to NULL database",
	       hostname);
	return(HOST_NULL);
    } else if (dbp->hostEntries == HOST_NULL) {
	dbp->hostEntries = hp;
	hp->prev = hp->next = HOST_NULL;
    } else if (before == HOST_NULL) {
	last = lastHostEntry(dbp);
	last->next = hp;
	hp->prev = last;
	hp->next = HOST_NULL;
    } else {
	if (before->prev == HOST_NULL)
	    dbp->hostEntries = hp;
	else
	    before->prev->next = hp;
	hp->prev = before->prev;
	hp->next = before;
	before->prev = hp;
    }
    hp->hostname = XtNewString(hostname);
    return(hp);
}

HostEntry *
lastHostEntry(dbp)
Database *dbp;
{
    HostEntry *hp;

    if (dbp == DB_NULL) {
	alert0("DB error: attempt to find last host of NULL database\n");
	return(HOST_NULL);
    } else if (dbp->hostEntries == HOST_NULL)
	return(HOST_NULL);
    for (hp=dbp->hostEntries; hp->next != HOST_NULL; hp=hp->next)
	/* EMPTY */ ;
    return(hp);
}

HostEntry *
findHostEntryFromString(hostname,dbp)
char *hostname;
Database *dbp;
{
    HostEntry *hp;

    if (dbp == DB_NULL) {
	alert1("DB error: attempt to find host \"%.180s\" in NULL database",
	       hostname);
	return(HOST_NULL);
    }
    for (hp=dbp->hostEntries; hp != HOST_NULL &&
	 		      strcmp(hp->hostname,hostname) != 0; hp=hp->next)
	/* EMPTY */ ;
    return(hp);
}

HostEntry *
findHostEntryFromIndex(index,dbp)
int index;
Database *dbp;
{
    HostEntry *hp;

    if (dbp == DB_NULL) {
	alert1("DB error: attempt to find host index %d in NULL database",
	       index);
	return(HOST_NULL);
    }
    for (hp=dbp->hostEntries; hp != HOST_NULL && index--; hp=hp->next)
	/* EMPTY */ ;
    return(hp);
}

int
findHostIndexFromEntry(hp)
HostEntry *hp;
{
    int i;

    for (i=0; i < numHostStrings && *(hostStrings+i) != (char*)NULL; i++)
        if (strcmp(hp->hostname,*(hostStrings+i)) == 0)
            return(i);
    return(-1);
}


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

LocEntry *
addLocEntry(linkpath,hostp,before)
char *linkpath;
HostEntry *hostp;
LocEntry *before;
{
    LocEntry *lp,*last;

    lp = XtNew(LocEntry);
    lp->linkpath = (char *)NULL;
    lp->fileEntries = FILE_NULL;
    lp->next = lp->prev = LOC_NULL;
    if (hostp == HOST_NULL) {
	alert1("DB error: attempt to add location \"%.180s\" to NULL host",
	       linkpath);
	return(LOC_NULL);
    } else if (hostp->locEntries == LOC_NULL) {
	hostp->locEntries = lp;
	lp->prev = lp->next = LOC_NULL;
    } else if (before == LOC_NULL) {
	last = lastLocEntry(hostp);
	last->next = lp;
	lp->prev = last;
	lp->next = LOC_NULL;
    } else {
	if (before->prev == LOC_NULL)
	    hostp->locEntries = lp;
	else
	    before->prev->next = lp;
	lp->prev = before->prev;
	lp->next = before;
	before->prev = lp;
    }
    lp->linkpath = XtNewString(linkpath);
    return(lp);
}

LocEntry *
lastLocEntry(locp)
HostEntry *locp;
{
    LocEntry *lp;

    if (locp == HOST_NULL) {
	alert0("DB error: attempt to find last location of NULL host\n");
	return LOC_NULL;
    } else if (locp->locEntries == LOC_NULL)
	return(LOC_NULL);
    for (lp=locp->locEntries; lp->next != LOC_NULL; lp=lp->next)
	/* EMPTY */ ;
    return(lp);
}

LocEntry *
findLocEntryFromString(linkpath,hostp)
char *linkpath;
HostEntry *hostp;
{
    LocEntry *lp;

    if (hostp == HOST_NULL) {
	alert1("DB error: attempt to find loc \"%.180s\" in NULL host",
	       linkpath);
	return(LOC_NULL);
    }
    for (lp=hostp->locEntries; lp != LOC_NULL &&
	 		      strcmp(lp->linkpath,linkpath) != 0; lp=lp->next)
	/* EMPTY */ ;
    return(lp);
}

LocEntry *
findLocEntryFromIndex(index,hostp)
int index;
HostEntry *hostp;
{
    LocEntry *lp;

    if (hostp == HOST_NULL) {
	alert1("DB error: attempt to find loc index %d in NULL host",
	       index);
	return(LOC_NULL);
    }
    for (lp=hostp->locEntries; lp != LOC_NULL && index--; lp=lp->next)
	/* EMPTY */ ;
    return(lp);
}

int
findLocIndexFromEntry(lp)
LocEntry *lp;
{
    int i;

    for (i=0; i < numLocStrings && *(locStrings+i) != (char *)NULL; i++)
        if (strcmp(lp->linkpath,*(locStrings+i)) == 0)
            return(i);
    return(-1);
}

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

FileEntry *
addFileEntry(name,size,modes,date,locp,before)
char *name;
int size;
char *modes,*date;
LocEntry *locp;
FileEntry *before;
{
    FileEntry *fp,*last;

    fp = XtNew(FileEntry);
    fp->name = (char *)NULL;
    fp->next = fp->prev = FILE_NULL;
    if (locp == LOC_NULL) {
	alert1("DB error: attempt to add file \"%.180s\" to NULL location",
	       name);
	return(FILE_NULL);
    } else if (locp->fileEntries == FILE_NULL) {
	locp->fileEntries = fp;
	fp->prev = fp->next = FILE_NULL;
    } else if (before == FILE_NULL) {
	last = lastFileEntry(locp);
	last->next = fp;
	fp->prev = last;
	fp->next = FILE_NULL;
    } else {
	if (before->prev == FILE_NULL)
	    locp->fileEntries = fp;
	else
	    before->prev->next = fp;
	fp->prev = before->prev;
	fp->next = before;
	before->prev = fp;
    }
    fp->name = XtNewString(name);
    fp->size = size;
    strncpy(fp->modes,modes,15);
    strncpy(fp->date,date,15);
    return(fp);
}

FileEntry *
lastFileEntry(locp)
LocEntry *locp;
{
    FileEntry *fp;

    if (locp == LOC_NULL) {
	alert0("DB error: attempt to find last file of NULL location\n");
	return FILE_NULL;
    } else if (locp->fileEntries == FILE_NULL)
	return(FILE_NULL);
    for (fp=locp->fileEntries; fp->next != FILE_NULL; fp=fp->next)
	/* EMPTY */ ;
    return(fp);
}

FileEntry *
findFileEntryFromString(name,locp)
char *name;
LocEntry *locp;
{
    FileEntry *fp;

    if (locp == LOC_NULL) {
	alert1("DB error: attempt to find file \"%.180s\" in NULL location",
	       name);
	return(FILE_NULL);
    }
    for (fp=locp->fileEntries; fp != FILE_NULL &&
	 		      strcmp(fp->name,name) != 0; fp=fp->next)
	/* EMPTY */ ;
    return(fp);
}

FileEntry *
findFileEntryFromIndex(index,locp)
int index;
LocEntry *locp;
{
    FileEntry *fp;

    if (locp == LOC_NULL) {
	alert1("DB error: attempt to find file index %d in NULL location",
	       index);
	return(FILE_NULL);
    }
    for (fp=locp->fileEntries; fp != FILE_NULL && index--; fp=fp->next)
	/* EMPTY */ ;
    return(fp);
}

int
findFileIndexFromEntry(fp)
FileEntry *fp;
{
    int i;

    for (i=0; i < numFileStrings && *(fileStrings+i) != (char *)NULL; i++)
        if (strcmp(fp->name,*(fileStrings+i)) == 0)
            return(i);
    return(-1);
}

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

void
displayHosts(dbp)
Database *dbp;
{
    HostEntry *hp;
    int i;

    if (dbp == DB_NULL) {
	alert0("DB error: attempt to display hosts from NULL database\n");
	return;
    }
    clearList(hostList);
    clearList(locationList);
    clearList(fileList);
    i = 0;
    for (hp=dbp->hostEntries; hp != NULL; hp=hp->next)
	setHostString(i++,hp->hostname);
    setHostString(i,(char *)NULL);
    if (i > 0)
	setHostListFromStrings();
    if (i == 1) {
	XawListHighlight(hostList,0);
	selectedHostEntry = dbp->hostEntries;
	displayHostInfo(selectedHostEntry);
	displayLocs(selectedHostEntry);
    } else {
	selectedHostEntry = HOST_NULL;
	clearHostInfo();
	selectedLocEntry = LOC_NULL;
	clearLocationInfo();
	selectedFileEntry = FILE_NULL;
	clearFileInfo();
    }
}

void
displayLocs(hostp)
HostEntry *hostp;
{
    LocEntry *lp;
    int i;

    if (hostp == HOST_NULL) {
	alert0("DB error: attempt to display locations from NULL host\n");
	return;
    }
    clearList(locationList);
    clearList(fileList);
    i = 0;
    for (lp=hostp->locEntries; lp != LOC_NULL; lp=lp->next)
	setLocString(i++,lp->linkpath);
    setLocString(i,(char *)NULL);
    if (i > 0)
	setLocListFromStrings();
    if (i == 1) {
	XawListHighlight(locationList,0);
	selectedLocEntry = hostp->locEntries;
	displayLocationInfo(selectedLocEntry);
	displayFiles(selectedLocEntry);
    } else {
	selectedLocEntry = LOC_NULL;
	clearLocationInfo();
	selectedFileEntry = FILE_NULL;
	clearFileInfo();
    }
}

void
displayFiles(locp)
LocEntry *locp;
{
    FileEntry *fp;
    int i;

    if (locp == LOC_NULL) {
	alert0("DB error: attempt to display files from NULL location\n");
	return;
    }
    clearList(fileList);
    i = 0;
    for (fp=locp->fileEntries; fp != FILE_NULL; fp=fp->next)
	setFileString(i++,fp->name);
    setFileString(i,(char *)NULL);
    if (i > 0)
	setFileListFromStrings();
    if (i == 1) {
	XawListHighlight(fileList,0);
	selectedFileEntry = locp->fileEntries;
	displayFileInfo(selectedFileEntry);
    } else {
	selectedFileEntry = FILE_NULL;
	clearFileInfo();
    }
}

/*	-	-	-	-	-	-	-	-	*/
/* These routines provide access to the dynamically-grown lists
 * of strings. I bzero() the allocated space even though I shouldn't
 * to.
 */

void
setHostString(i,s)
int i;
char *s;
{
    char **oldStr;
    int oldNum;

    /* Free old string if there was one */
    if (i < numHostStrings)
	XtFree(*(hostStrings+i));
    /* If this is the first call, get the initial string array */
    if (numHostStrings == 0) {
	numHostStrings = INIT_NUM_HOST_STRINGS;
	hostStrings = (char **)XtCalloc(numHostStrings,sizeof(char *));
	bzero(hostStrings,numHostStrings*sizeof(char *));
    }
    /* Grow the array until it's big enough for this string */
    while (i >= numHostStrings) {
	oldStr = hostStrings;
	oldNum = numHostStrings;
	numHostStrings = REALLOC_INCR(numHostStrings);
	hostStrings = (char **)XtCalloc(numHostStrings,sizeof(char *));
	bzero(hostStrings,numHostStrings*sizeof(char *));
	bcopy(oldStr,hostStrings,oldNum*sizeof(char *));
	XtFree(oldStr);
    }
    /* Finally, set this value */
    if (s == (char *)NULL)
	*(hostStrings+i) = (char *)NULL;
    else
	*(hostStrings+i) = XtNewString(s);
}

void
setLocString(i,s)
int i;
char *s;
{ 
    char **oldStr;
    int oldNum;

    /* Free old string if there was one */
    if (i < numLocStrings)
	XtFree(*(locStrings+i));
    /* If this is the first call, get the initial string array */
    if (numLocStrings == 0) {
	numLocStrings = INIT_NUM_LOC_STRINGS;
	locStrings = (char **)XtCalloc(numLocStrings,sizeof(char *));
	bzero(locStrings,numLocStrings*sizeof(char *));
    }
    /* Grow the array until it's big enough for this string */
    while (i >= numLocStrings) {
	oldStr = locStrings;
	oldNum = numLocStrings;
	numLocStrings = REALLOC_INCR(numLocStrings);
	locStrings = (char **)XtCalloc(numLocStrings,sizeof(char *));
	bzero(locStrings,numLocStrings*sizeof(char *));
	bcopy(oldStr,locStrings,oldNum*sizeof(char *));
	XtFree(oldStr);
    }
    /* Finally, set this value */
    if (s == (char *)NULL)
	*(locStrings+i) = (char *)NULL;
    else
	*(locStrings+i) = XtNewString(s);
}

void
setFileString(i,s)
int i;
char *s;
{
    char **oldStr;
    int oldNum;

    /* Free old string if there was one */
    if (i < numFileStrings)
	XtFree(*(fileStrings+i));
    /* If this is the first call, get the initial string array */
    if (numFileStrings == 0) {
	numFileStrings = INIT_NUM_FILE_STRINGS;
	fileStrings = (char **)XtCalloc(numFileStrings,sizeof(char *));
	bzero(fileStrings,numFileStrings*sizeof(char *));
    }
    /* Grow the array until it's big enough for this string */
    while (i >= numFileStrings) {
	oldStr = fileStrings;
	oldNum = numFileStrings;
	numFileStrings = REALLOC_INCR(numFileStrings);
	fileStrings = (char **)XtCalloc(numFileStrings,sizeof(char *));
	bzero(fileStrings,numFileStrings*sizeof(char *));
	bcopy(oldStr,fileStrings,oldNum*sizeof(char *));
	XtFree(oldStr);
    }
    /* Finally, set this value */
    if (s == (char *)NULL)
	*(fileStrings+i) = (char *)NULL;
    else
	*(fileStrings+i) = XtNewString(s);
}

/*	-	-	-	-	-	-	-	-	*/
/* These routines connect the arrays of strings to the list widgets. */

void
setHostListFromStrings()
{
    XawListChange(hostList,hostStrings,0,0,True);
}

void
setLocListFromStrings()
{
    XawListChange(locationList,locStrings,0,0,True);
}

void
setFileListFromStrings()
{
    XawListChange(fileList,fileStrings,0,0,True);
}

static char *emptyNames[] = { NULL };

void clearList(w)
Widget w;
{
    XawListChange(w,emptyNames,0,0,False);
}
