/* itemList.c
   routines to manage the data structures for gopher items. */

     /*---------------------------------------------------------------*/
     /* Xgopher        version 1.2     20 November 1991               */
     /*                version 1.1     20 April 1991                  */
     /*                version 1.0     04 March 1991                  */
     /* X window system client for the University of Minnesota        */
     /*                                Internet Gopher System.        */
     /* Allan Tuchman, University of Illinois at Urbana-Champaign     */
     /*                Computing and Communications Services Office   */
     /* Copyright 1992 by                                             */
     /*           the Board of Trustees of the University of Illinois */
     /* Permission is granted to freely copy and redistribute this    */
     /* software with the copyright notice intact.                    */
     /*---------------------------------------------------------------*/


#include <stdio.h>

#include "gopher.h"
#include "itemList.h"
#include "dir.h"
#include "listP.h"

static	gopherItemList	unusedItems = {NULL, NULL};
static	int		firstItemAlloc = TRUE;

#define FREE_ITEM_LIST_LENGTH	itemListLength(&unusedItems)


/* Item list management routines:
	initItemList(gopherItemListP)	set list pointers to indicate empty
	appendItem(gopherItemListP, gopherItemP) add item to end of list
	getItemN(gopherItemListP, int)	return pointer to Nth item in list
	nextItem(gopherItemP)		return pointer to next item in list
	removeItemN(gopherItemListP, int) delete Nth item from a list
	removeItem(gopherItemListP, gopherItemP) delete given item from a list
	itemListLength(gopherItemListP)	return length of item list

   for internal use only:
	pushItem(gopherItemListP, gopherItemP) push item to front of list
	printItemList(gopherItemListP, char *) Only defined if DEBUG is

   freeList management routines:
	acquireItem()			return ptr to a gopherItem structure
	releaseItem(gopherItemP)	Return a gopher item to the free list
	freeItemList(gopherItemP)	return list of items to the free list

   freeList management routines (internal only):
	allocGopherItem(int)		allocate N new items
	releaseItems(gopherItemP)	Return items to the free list (internal)
*/


/* acquireItem 
	Allocate a gopher item from the free list */

gopherItemP
acquireItem()
{
	gopherItemP	gi;


	if (unusedItems.first == NULL) {

		/* out of gopher items.  First reclaim those that
		   are out of date, then allocate more if necessary */

		checkDirStack();

		if (unusedItems.first == NULL  ||
				FREE_ITEM_LIST_LENGTH < itemIncrement) {
			allocGopherItem(firstItemAlloc ?
				itemStart : itemIncrement);
			firstItemAlloc = FALSE;
		}
	}
	gi = unusedItems.first;
	unusedItems.first = gi->next;
	if (unusedItems.first == NULL) unusedItems.last = NULL;

	gi->next = NULL;

	return gi;
}


/* initItemList 
	Set the list pointers to indicate that the list is empty */

void
initItemList(list)
gopherItemListP	list;
{
	list->first = list->last = NULL;

	return;
}


/* releaseItem 
	Return a cleared out gopher item to the free list */

void
releaseItem(gi)
gopherItemP	gi;
{
	if (gi != NULL)
		pushItem(&unusedItems, gi);

	return;
}


/* releaseItems
	Return a linked list of cleared out gopher items to the free list */

static void
releaseItems(gi)
gopherItemP	gi;
{
	gopherItemP	p;
	int	n=0;

	while (gi != NULL) {
		p = gi->next;
		releaseItem(gi);
		n++;
		gi = p;
	}

#ifdef NOT_USED
	/* this code is a more efficient way of doing the above */

	if (gi == NULL) return;

	/* set p to point to the end of the items */

	for (p=gi; p->next != NULL; p=p->next) ;

	if (unusedItems.last == NULL) unusedItems.last = p;
	p->next = unusedItems.first;
	unusedItems.first = gi;
#endif /* NOT_USED */

	return;
}


/* freeItemList
	Return a list of gopher items to the free list and clear the
	list header */

void
freeItemList(list)
gopherItemListP	list;
{
	gopherItemP	p;
	int		n=0;

	if (list != NULL) {
		/* if we know that they are already cleared out, we could use:
			releaseItems(list->first);
		   but we don't, so free them one at a time. */

		gopherItemP	gi=list->first;

		while (gi != NULL) {
			p = gi->next;
			freeItem(gi);
			n++;
			gi = p;
		}

		initItemList(list);
	}

	return;
}


/* itemListLength
	return the number of items in an item list */

int
itemListLength(list)
gopherItemListP	list;
{
	int	n;
	gopherItemP	p;

	if (list == NULL) return 0;

	for (n=0, p=list->first; p != NULL; n++, p=p->next) ;

	return n;
}


/* appendItem 
	append an item to the end of an item list */

void
appendItem(list, gi)
gopherItemListP	list;
gopherItemP	gi;
{
	if (list == NULL || gi == NULL) return;

	if (list->last == NULL) {
		list->first = list->last = gi;
	} else {
		list->last->next = gi;
		list->last = gi;
	}

	gi->next = NULL;

	return;
}


/* removeItemN 
	delete the Nth item from a list.  The first item is numbered 0 */

void
removeItemN(list, n)
gopherItemListP	list;
int		n;
{
	gopherItemP	p, prev;

	if (list == NULL) return;
	if (n < 0) return;
	if ((p = list->first) == NULL) return;

	if (n == 0) {
		list->first = p->next;
		if (list->last == p) list->last = NULL;
		freeItem(p);
	} else {
		for (prev=p, p=p->next; p!= NULL; prev=p, p=p->next) {
			if (--n == 0) {
				prev->next = p->next;
				if (p == list->last) list->last = prev;
				freeItem(p);
				break;
			}
		}
	}

	return;
}


/* removeItem
	delete the a given item from a list. */

void
removeItem(list, gi)
gopherItemListP	list;
gopherItemP	gi;
{
	gopherItemP	p, prev;

	if (list == NULL) return;
	if ((p = list->first) == NULL) return;

	if (p == gi) {
		list->first = p->next;
		if (list->last == p) list->last = NULL;
		freeItem(p);
	} else {
		for (prev=p, p=p->next; p!= NULL; prev=p, p=p->next) {
			if (p == gi) {
				prev->next = p->next;
				if (p == list->last) list->last = prev;
				freeItem(p);
				break;
			}
		}
	}

	return;
}


/* getItemN 
	return a pointer to the Nth item of the specified item list,
	starting with number 0 */

gopherItemP
getItemN(list, n)
gopherItemListP	list;
int		n;
{
	gopherItemP	p;

	if (n < 0) return NULL;  
	if (list == NULL) return NULL;  

	p = list->first;
	for ( ; p != NULL; p=p->next) {
		if (n-- == 0) break;
	}

	return p;
}


/* nextItem 
	return a pointer to the next item in a list, given an existing item. */

gopherItemP
nextItem(gi)
gopherItemP	gi;
{
	if (gi == NULL) return NULL;
	return gi->next;
}


/* pushItem 
	push an item onto the front of a item list */

static void
pushItem(list, gi)
gopherItemListP	list;
gopherItemP	gi;
{
	gi->next = list->first;
	list->first = gi;
	if (list->last == NULL) list->last = gi;

	return;
}


/* allocGopherItem
	Allocate n new gopher items, adding them to the free list */

static void
allocGopherItem(n)
int	n;
{
	gopherItemP	gil, p;
	int		i;

	if (n <= 0) return;

	if ((gil = (gopherItemP) malloc(n * sizeof(gopherItem))) == NULL) {
		/* out of memory */
		fprintf (stderr, "There is not enough memory to continue.\n");
		exit(3);
	}

	for (i=0, p=gil; i<n-1; i++, p++) {
		initItemFields(p);
		p->next = p+1;
	}
	initItemFields(p);
	p->next = NULL;
	releaseItems(gil);

	return;
}


/* itemInList
        see if a gopher item is in an item list */

BOOLEAN
itemInList(list, item)
gopherItemListP	list;
gopherItemP	item;
{
	gopherItemP	p;
	BOOLEAN		same;

	if (list == NULL  ||  item == NULL) return FALSE;

	for (p = list->first; (p != item  &&  p != NULL); p=p->next) {
		same = compareItem(item, p);
		if (same) break;
	}

	return (p != NULL);
}


#ifdef DEBUG_LIST


/* printItemList
   Print a list of gopher items for checking the list contents */

void
printItemList(list, label)
gopherItemListP list;
char            *label;
{
        gopherItemP     gi;
	int		i;

        if (list == NULL) fprintf(stdout, "NULL list %s\n", label);
        else {
                gi = list->first;
                fprintf (stdout, "list %s:\n", label);
                while (gi != NULL) {
			fprintf (stdout, "\t(");
			for (i=0; i<PREFIX_LEN; i++) 
				fprintf (stdout, " %d ",
					(int)((USER_STRING_PREFIX(gi))[i]));
			fprintf (stdout, ") ");
                        fprintf (stdout, "(%s)\n", USER_STRING(gi));
                        gi = gi->next;
                }
                fprintf (stdout, "    end of list\n");
        }
}
#endif /* DEBUG_LIST */
