/*
 * db.c : Database routines for xarchie
 *
 * George Ferguson, ferguson@cs.rochester.edu, 2 Nov 1991.
 * Version 2.0: 23 Apr 1993.
 */

#include <stdio.h>
#include "db.h"
#include "weight.h"
#include "stringdefs.h"
#include "debug.h"

#ifdef XARCHIE
# include <X11/Intrinsic.h>
#else /*!XARCHIE*/
# include "xtypes.h"
#endif

/*
 * Functions defined here:
 */
DbEntry *newEntry();
void clearEntries();
DbEntry *addEntry();
DbEntry *lastEntry();
DbEntry *findEntryFromString();
DbEntry *findEntryFromIndex();
int findIndexFromEntry();
void setEntryData(), freeEntryData();
void sortEntriesRecursively(), sortEntries();
int cmpEntryNames(), cmpEntryDates(), cmpEntryWeights();

static void clearEntriesInternal();
static char *entryDate(),*hostDate(),*locationDate();

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

DbEntry *
newEntry()
{
    DbEntry *new;

    new = XtNew(DbEntry);
    new->name = NULL;
    new->type = DB_NOTYPE;
    new->size = -1;
    new->modes = "";
    new->date = "";
    new->gt_date = "";
    new->vlink = NULL;
    new->selected = -1;
    new->entries = NULL;
    new->next = new->prev = new->parent = NULL;
    return(new);
}

void
clearEntries(dbp)
DbEntry *dbp;
{
    if (dbp == NULL) {
	fprintf(stderr,"DB Error: Attempt to clear NULL list\n");
	return;
    } else {
	clearEntriesInternal(dbp->entries);
	dbp->entries = NULL;
    }
}

static void
clearEntriesInternal(dbp)
DbEntry *dbp;
{
    DbEntry *t;

    while (dbp != NULL) {
	clearEntriesInternal(dbp->entries);
	freeEntryData(dbp);
	t = dbp;
	dbp = dbp->next;
	XtFree((char *)t);
    }
}

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

DbEntry *
addEntry(parent,before)
DbEntry *parent;
DbEntry *before;
{
    DbEntry *new,*last;

    DEBUG2("adding entry to 0x%x before 0x%x\n",parent,before);
    if (parent == NULL) {
	fprintf(stderr,"DB error: attempt to add entry to NULL list\n");
	return(NULL);
    } else if (parent->entries == NULL) {
	/* No items already in list */
	DEBUG0("  inserting as new list\n");
	new = XtNew(DbEntry);
	DEBUG1("  new entry is 0x%x\n",new);
	parent->entries = new;
	new->prev = NULL;
	new->next = NULL;
    } else if (before == NULL) {
	/* Insert at end of list */
	DEBUG0("  inserting at end of list\n");
	last = lastEntry(parent);
	new = XtNew(DbEntry);
	DEBUG1("  new entry is 0x%x\n",new);
	last->next = new;
	new->prev = last;
	new->next = NULL;
    } else if (before->prev == NULL) {
	/* Insert at head of list */
	DEBUG0("  inserting at head of list\n");
	new = XtNew(DbEntry);
	DEBUG1("  new entry is 0x%x\n",new);
	parent->entries = new;
	new->prev = NULL;
	new->next = before;
	before->prev = new;
    } else {
	/* Insert somewhere in the middle of list */
	DEBUG0("  inserting in middle of list\n");
	new = XtNew(DbEntry);
	DEBUG1("  new entry is 0x%x\n",new);
	before->prev->next = new;
	new->prev = before->prev;
	new->next = before;
	before->prev = new;
    }
    new->parent = parent;
    new->entries = NULL;
    new->name = NULL;
    new->type = DB_NOTYPE;
    new->size = -1;
    new->modes = "";
    new->date = "";
    new->gt_date = "";
    new->vlink = NULL;
    DEBUG0("  done adding new entry\n");
    return(new);
}

DbEntry *
lastEntry(parent)
DbEntry *parent;
{
    DbEntry *dbp;

    DEBUG1("finding last entry in entries for 0x%x\n",parent);
    if (parent == NULL) {
	fprintf(stderr,"DB error: attempt to find last entry of NULL list\n");
	return(NULL);
    } else if (parent->entries == NULL) {
	DEBUG1("no entries for 0x%x, returning NULL\n",parent);
	return(NULL);
    } else {
	for (dbp=parent->entries; dbp->next != NULL; dbp=dbp->next)
	    /* EMPTY */ ;
	DEBUG2("last entry for 0x%x is 0x%x\n",parent,dbp);
	return(dbp);
    }
}

DbEntry *
findEntryFromString(parent,string)
DbEntry *parent;
char *string;
{
    DbEntry *dbp;

    DEBUG2("finding entry \"%s\" in entries for 0x%x\n",string,parent);
    if (parent == NULL) {
	fprintf(stderr,"DB error: attempt to find \"%s\" in NULL list",string);
	return(NULL);
    } else {
	for (dbp=parent->entries; dbp != NULL &&
				  strcmp(dbp->name,string) != 0; dbp=dbp->next)
	    /* EMPTY */ ;
	DEBUG1("  find returning 0x%x\n",dbp);
	return(dbp);
    }
}

DbEntry *
findEntryFromIndex(parent,index)
DbEntry *parent;
int index;
{
    DbEntry *dbp;

    DEBUG2("finding entry with index %d in entries for 0x%x\n",index,parent);
    if (parent == NULL) {
	fprintf(stderr,"DB error: attempt to find index %d in NULL list",index);
	return(NULL);
    } else {
	for (dbp=parent->entries; dbp != NULL && index--; dbp=dbp->next)
	    /* EMPTY */ ;
	DEBUG1("  find returning 0x%x\n",dbp);
	return(dbp);
    }
}

int
findIndexFromEntry(parent,entry)
DbEntry *parent,*entry;
{
    DbEntry *dbp;
    int index;

    DEBUG2("finding index for entry 0x%x in entries for 0x%x\n",entry,parent);
    index = 0;
    if (parent == NULL) {
	fprintf(stderr,"DB error: attempt to find entry 0x%x in NULL list",entry);
	return(0);
    } else {
	for (dbp=parent->entries; dbp != NULL; dbp=dbp->next) {
	    if (strcmp(entry->name,dbp->name) == 0) {
		DEBUG1("  return index %d\n",index);
		return(index);
	    }
	    index += 1;
	}
    }
    return(-1);
}

/*	-	-	-	-	-	-	-	-	*/
/*
 * Allocate and set the data for the given DbEntry.
 */
void
setEntryData(dbp,name,type,size,modes,date,gt_date,vlink)
DbEntry *dbp;
char *name;
int type;
int size;
char *modes,*date,*gt_date;
VLINK vlink;
{
    XtFree(dbp->name);
    dbp->name = XtNewString(name);
    dbp->type = type;
    dbp->size = size;
    dbp->modes = XtNewString(modes);
    dbp->date = XtNewString(date);
    dbp->gt_date = XtNewString(gt_date);
    dbp->vlink = vlink;
    DEBUG2("set data for entry 0x%x, name=\"%s\"\n",dbp,dbp->name);
}

/*
 * Free up anything associated with the given DbEntry.
 */
void
freeEntryData(dbp)
DbEntry *dbp;
{
    XtFree((char *)(dbp->name));
    if (dbp->vlink)
	vlfree(dbp->vlink);
}

/*	-	-	-	-	-	-	-	-	*/
/*
 * Sorts entries of parent and then calls itself on each of them.
 */
void
sortEntriesRecursively(parent,cmp_proc)
DbEntry *parent;
int (*cmp_proc)();
{
    DbEntry *dbp;

    if (parent == NULL || cmp_proc == NULL)
	return;
    DEBUG1(" sorting entries recursively for 0x%x\n",parent);
    sortEntries(parent,cmp_proc);
    for (dbp=parent->entries; dbp != NULL; dbp = dbp->next)
	sortEntriesRecursively(dbp->entries,cmp_proc);
    DEBUG1(" done sorting entries recursively for 0x%x\n",parent);
}

/*
 * Sorts entries of parent using a selection sort and the given cmp_proc.
 */
void
sortEntries(parent,cmp_proc)
DbEntry *parent;
int (*cmp_proc)();
{
    DbEntry *p,*nextp,*lowest,*q,*pnext,*pprev;

    DEBUG1("  sorting entries for 0x%x\n",parent);
    for (p = parent->entries; p != NULL; p = nextp) {
	nextp = p->next;
	lowest = p;
	for (q = p->next; q != NULL; q = q->next)
	    if ((*cmp_proc)(q,lowest) < 0)
		lowest = q;
	if (p != lowest) {
	    /* swap the two links */
	    pnext = p->next;
	    pprev = p->prev;
	    if (lowest->next != NULL)
		lowest->next->prev = p;
	    p->next = lowest->next;
	    if (nextp == lowest) {
		p->prev = lowest;
	    } else {
		lowest->prev->next = p;
		p->prev = lowest->prev;
	    }
	    if (nextp == lowest) {
		lowest->next = p;
	    } else {
		pnext->prev = lowest;
		lowest->next = pnext;
	    }
	    if (pprev != NULL)
		pprev->next = lowest;
	    lowest->prev = pprev;
	    /* keep the head of the list in the right place */
	    if (parent->entries == p)
		parent->entries = lowest;
	}
    }
    DEBUG1("  done sorting entries for 0x%x\n",parent);
}

/*
 * cmpEntryNames(p,q) : Compare entries by name. Returns < 0 if p belongs
 *	before q, > 0 if q belongs before p, and == 0 if names are identical.
 */
int
cmpEntryNames(p,q)
DbEntry *p,*q;
{
    return(strcmp(p->name,q->name));
}

/*
 * cmpEntryDates(p,q) : Compare entries by date. If the dates are the same
 *	or both entries don't have a date, then compare by name.
 *
 * To make this useful, I've made it sort hosts and locations by the
 * newest thing in them. To make it faster, it caches the computed date
 * in the otherwise unused date field.
 */
int
cmpEntryDates(p,q)
DbEntry *p,*q;
{
    int result;

    if (!*(p->gt_date))
	p->gt_date = entryDate(p);
    if (!*(q->gt_date))
	q->gt_date = entryDate(q);
    if ((result=strcmp(q->gt_date,p->gt_date)) != 0) /* note order! */
	return(result);
    else
	return(cmpEntryNames(p,q));
}

static char *
entryDate(dbp)
DbEntry *dbp;
{
    if (dbp->type == DB_HOST)
	return(hostDate(dbp));
    else if (dbp->type == DB_LOCATION)
	return(locationDate(dbp));
    else
	return(dbp->gt_date);
}

/*
 * Returns the date of the newest (biggest date) location in the host.
 */
static char *
hostDate(dbp)
DbEntry *dbp;
{
    DbEntry *loc;
    char *date = "";
    char *locdate;

    for (loc=dbp->entries; loc != NULL; loc=loc->next) {
	locdate = locationDate(loc);
	if (!*date || strcmp(date,locdate) < 0)
	    date = locdate;
    }
    return(date);
}

/*
 * Returns the date of the newest (biggest date) entry in the location.
 */
static char *
locationDate(dbp)
DbEntry *dbp;
{
    DbEntry *file;
    char *date = "";

    for (file=dbp->entries; file != NULL; file=file->next) {
	if (!*date || strcmp(date,file->gt_date) < 0)
	    date = file->gt_date;
    }
    return(date);
}

/*
 * cmpEntryWeights(p,q) : Compare entries by user-defined "weights".
 */
int
cmpEntryWeights(p,q)
DbEntry *p,*q;
{
    int result;

    if ((result=hostWeight(p->name)-hostWeight(q->name)) != 0)
	return(result);
    else
	return(cmpEntryNames(p,q));
}
