/*
 * Copyright (c) 1993 David I. Bell
 * Permission is granted to use, distribute, or modify this source,
 * provided that this copyright notice remains intact.
 *
 * List handling routines.
 * Lists can be composed of any types of values, mixed if desired.
 * Lists are doubly linked so that elements can be inserted or
 * deleted efficiently at any point in the list.  A pointer is
 * kept to the most recently indexed element so that sequential
 * accesses are fast.
 */

#include "value.h"


static LISTELEM *elemalloc MATH_PROTO((void));
static LISTELEM *listelement MATH_PROTO((LIST *lp, long index));
static void elemfree MATH_PROTO((LISTELEM *ep));
static void removelistelement MATH_PROTO((LIST *lp, LISTELEM *ep));


/*
 * Insert an element before the first element of a list.
 */
void
insertlistfirst(lp, vp)
	LIST *lp;		/* list to put element onto */
	VALUE *vp;		/* value to be inserted */
{
	LISTELEM *ep;		/* list element */

	ep = elemalloc();
	copyvalue(vp, &ep->e_value);
	if (lp->l_count == 0)
		lp->l_last = ep;
	else {
		lp->l_cacheindex++;
		lp->l_first->e_prev = ep;
		ep->e_next = lp->l_first;
	}
	lp->l_first = ep;
	lp->l_count++;
}


/*
 * Insert an element after the last element of a list.
 */
void
insertlistlast(lp, vp)
	LIST *lp;		/* list to put element onto */
	VALUE *vp;		/* value to be inserted */
{
	LISTELEM *ep;		/* list element */

	ep = elemalloc();
	copyvalue(vp, &ep->e_value);
	if (lp->l_count == 0)
		lp->l_first = ep;
	else {
		lp->l_last->e_next = ep;
		ep->e_prev = lp->l_last;
	}
	lp->l_last = ep;
	lp->l_count++;
}


/*
 * Insert an element into the middle of list at the given index (zero based).
 * The specified index will select the new element, so existing elements
 * at or beyond the index will be shifted down one position.  It is legal
 * to specify an index which is right at the end of the list, in which
 * case the element is appended to the list.
 */
void
insertlistmiddle(lp, index, vp)
	LIST *lp;		/* list to put element onto */
	long index;		/* element number to insert in front of */
	VALUE *vp;		/* value to be inserted */
{
	LISTELEM *ep;		/* list element */
	LISTELEM *oldep;	/* old list element at desired index */

	if (index == 0) {
		insertlistfirst(lp, vp);
		return;
	}
	if (index == lp->l_count) {
		insertlistlast(lp, vp);
		return;
	}
	oldep = NULL;
	if ((index >= 0) && (index < lp->l_count))
		oldep = listelement(lp, index);
	if (oldep == NULL)
		math_error("Index out of bounds for list insertion");
	ep = elemalloc();
	copyvalue(vp, &ep->e_value);
	ep->e_next = oldep;
	ep->e_prev = oldep->e_prev;
	ep->e_prev->e_next = ep;
	oldep->e_prev = ep;
	lp->l_cache = ep;
	lp->l_cacheindex = index;
	lp->l_count++;
}


/*
 * Remove the first element from a list, returning its value.
 * Returns the null value if no more elements exist.
 */
void
removelistfirst(lp, vp)
	LIST *lp;		/* list to have element removed */
	VALUE *vp;		/* location of the value */
{
	if (lp->l_count == 0) {
		vp->v_type = V_NULL;
		return;
	}
	*vp = lp->l_first->e_value;
	lp->l_first->e_value.v_type = V_NULL;
	removelistelement(lp, lp->l_first);
}


/*
 * Remove the last element from a list, returning its value.
 * Returns the null value if no more elements exist.
 */
void
removelistlast(lp, vp)
	LIST *lp;		/* list to have element removed */
	VALUE *vp;		/* location of the value */
{
	if (lp->l_count == 0) {
		vp->v_type = V_NULL;
		return;
	}
	*vp = lp->l_last->e_value;
	lp->l_last->e_value.v_type = V_NULL;
	removelistelement(lp, lp->l_last);
}


/*
 * Remove the element with the given index from a list, returning its value.
 */
void
removelistmiddle(lp, index, vp)
	LIST *lp;		/* list to have element removed */
	long index;		/* list element to be removed */
	VALUE *vp;		/* location of the value */
{
	LISTELEM *ep;		/* element being removed */

	ep = NULL;
	if ((index >= 0) && (index < lp->l_count))
		ep = listelement(lp, index);
	if (ep == NULL)
		math_error("Index out of bounds for list deletion");
	*vp = ep->e_value;
	ep->e_value.v_type = V_NULL;
	removelistelement(lp, ep);
}


/*
 * Remove an arbitrary element from a list.
 * The value contained in the element is freed.
 */
static void
removelistelement(lp, ep)
	register LIST *lp;		/* list header */
	register LISTELEM *ep;		/* list element to remove */
{
	if ((ep == lp->l_cache) || ((ep != lp->l_first) && (ep != lp->l_last)))
		lp->l_cache = NULL;
	if (ep->e_next)
		ep->e_next->e_prev = ep->e_prev;
	if (ep->e_prev)
		ep->e_prev->e_next = ep->e_next;
	if (ep == lp->l_first) {
		lp->l_first = ep->e_next;
		lp->l_cacheindex--;
	}
	if (ep == lp->l_last)
		lp->l_last = ep->e_prev;
	lp->l_count--;
	elemfree(ep);
}


/*
 * Search a list for the specified value starting at the specified index.
 * Returns the element number (zero based) of the found value, or -1 if
 * the value was not found.
 */
long
listsearch(lp, vp, index)
	LIST *lp;
	VALUE *vp;
	long index;
{
	register LISTELEM *ep;

	if (index < 0)
		index = 0;
	ep = listelement(lp, index);
	while (ep) {
		if (!comparevalue(&ep->e_value, vp)) {
			lp->l_cache = ep;
			lp->l_cacheindex = index;
			return index;
		}
		ep = ep->e_next;
		index++;
	}
	return -1;
}


/*
 * Search a list backwards for the specified value starting at the
 * specified index.  Returns the element number (zero based) of the
 * found value, or -1 if the value was not found.
 */
long
listrsearch(lp, vp, index)
	LIST *lp;
	VALUE *vp;
	long index;
{
	register LISTELEM *ep;

	if (index >= lp->l_count)
		index = lp->l_count - 1;
	ep = listelement(lp, index);
	while (ep) {
		if (!comparevalue(&ep->e_value, vp)) {
			lp->l_cache = ep;
			lp->l_cacheindex = index;
			return index;
		}
		ep = ep->e_prev;
		index--;
	}
	return -1;
}


/*
 * Index into a list and return the address for the value corresponding
 * to that index.  Returns NULL if the element does not exist.
 */
VALUE *
listfindex(lp, index)
	LIST *lp;		/* list to index into */
	long index;		/* index of desired element */
{
	LISTELEM *ep;

	ep = listelement(lp, index);
	if (ep == NULL)
		return NULL;
	return &ep->e_value;
}


/*
 * Return the element at a specified index number of a list.
 * The list is indexed starting at zero, and negative indices
 * indicate to index from the end of the list.  This routine finds
 * the element by chaining through the list from the closest one
 * of the first, last, and cached elements.  Returns NULL if the
 * element does not exist.
 */
static LISTELEM *
listelement(lp, index)
	register LIST *lp;	/* list to index into */
	long index;		/* index of desired element */
{
	register LISTELEM *ep;	/* current list element */
	long dist;		/* distance to element */
	long temp;		/* temporary distance */
	BOOL forward;		/* TRUE if need to walk forwards */

	if (index < 0)
		index += lp->l_count;
	if ((index < 0) || (index >= lp->l_count))
		return NULL;
	/*
	 * Check quick special cases first.
	 */
	if (index == 0)
		return lp->l_first;
	if (index == 1)
		return lp->l_first->e_next;
	if (index == lp->l_count - 1)
		return lp->l_last;
	if ((index == lp->l_cacheindex) && lp->l_cache)
		return lp->l_cache;
	/*
	 * Calculate whether it is better to go forwards from
	 * the first element or backwards from the last element.
	 */
	forward = ((index * 2) <= lp->l_count);
	if (forward) {
		dist = index;
		ep = lp->l_first;
	} else {
		dist = (lp->l_count - 1) - index;
		ep = lp->l_last;
	}
	/*
	 * Now see if we have a cached element and if so, whether or
	 * not the distance from it is better than the above distance.
	 */
	if (lp->l_cache) {
		temp = index - lp->l_cacheindex;
		if ((temp >= 0) && (temp < dist)) {
			dist = temp;
			ep = lp->l_cache;
			forward = TRUE;
		}
		if ((temp < 0) && (-temp < dist)) {
			dist = -temp;
			ep = lp->l_cache;
			forward = FALSE;
		}
	}
	/*
	 * Now walk forwards or backwards from the selected element
	 * until we reach the correct element.  Cache the location of
	 * the found element for future use.
	 */
	if (forward) {
		while (dist-- > 0)
			ep = ep->e_next;
	} else {
		while (dist-- > 0)
			ep = ep->e_prev;
	}
	lp->l_cache = ep;
	lp->l_cacheindex = index;
	return ep;
}


/*
 * Compare two lists to see if they are identical.
 * Returns TRUE if they are different.
 */
BOOL
listcmp(lp1, lp2)
	LIST *lp1, *lp2;
{
	LISTELEM *e1, *e2;
	long count;

	if (lp1 == lp2)
		return FALSE;
	if (lp1->l_count != lp2->l_count)
		return TRUE;
	e1 = lp1->l_first;
	e2 = lp2->l_first;
	count = lp1->l_count;
	while (count-- > 0) {
		if (comparevalue(&e1->e_value, &e2->e_value))
			return TRUE;
		e1 = e1->e_next;
		e2 = e2->e_next;
	}
	return FALSE;
}


/*
 * Copy a list
 */
LIST *
listcopy(oldlp)
	LIST *oldlp;
{
	LIST *lp;
	LISTELEM *oldep;

	lp = listalloc();
	oldep = oldlp->l_first;
	while (oldep) {
		insertlistlast(lp, &oldep->e_value);
		oldep = oldep->e_next;
	}
	return lp;
}


/*
 * Allocate an element for a list.
 */
static LISTELEM *
elemalloc()
{
	LISTELEM *ep;

	ep = (LISTELEM *) malloc(sizeof(LISTELEM));
	if (ep == NULL)
		math_error("Cannot allocate list element");
	ep->e_next = NULL;
	ep->e_prev = NULL;
	ep->e_value.v_type = V_NULL;
	return ep;
}


/*
 * Free a list element, along with any contained value.
 */
static void
elemfree(ep)
	LISTELEM *ep;
{
	if (ep->e_value.v_type != V_NULL)
		freevalue(&ep->e_value);
	free(ep);
}


/*
 * Allocate a new list header.
 */
LIST *
listalloc()
{
	register LIST *lp;

	lp = (LIST *) malloc(sizeof(LIST));
	if (lp == NULL)
		math_error("Cannot allocate list header");
	lp->l_first = NULL;
	lp->l_last = NULL;
	lp->l_cache = NULL;
	lp->l_cacheindex = 0;
	lp->l_count = 0;
	return lp;
}


/*
 * Free a list header, along with all of its list elements.
 */
void
listfree(lp)
	register LIST *lp;
{
	register LISTELEM *ep;

	while (lp->l_count-- > 0) {
		ep = lp->l_first;
		lp->l_first = ep->e_next;
		elemfree(ep);
	}
	free(lp);
}


/*
 * Print out a list along with the specified number of its elements.
 * The elements are printed out in shortened form.
 */
void
listprint(lp, max_print)
	LIST *lp;
	long max_print;
{
	long count;
	long index;
	LISTELEM *ep;

	if (max_print > lp->l_count)
		max_print = lp->l_count;
	count = 0;
	ep = lp->l_first;
	index = lp->l_count;
	while (index-- > 0) {
		if ((ep->e_value.v_type != V_NUM) ||
			(!qiszero(ep->e_value.v_num)))
				count++;
		ep = ep->e_next;
	}
	if (max_print > 0)
		math_str("\n");
	math_fmt("list (%ld element%s, %ld nonzero)", lp->l_count,
		((lp->l_count == 1) ? "" : "s"), count);
	if (max_print <= 0)
		return;

	/*
	 * Walk through the first few list elements, printing their
	 * value in short and unambiguous format.
	 */
	math_str(":\n");
	ep = lp->l_first;
	for (index = 0; index < max_print; index++) {
		math_fmt("  [[%ld]] = ", index);
		printvalue(&ep->e_value, PRINT_SHORT | PRINT_UNAMBIG);
		math_str("\n");
		ep = ep->e_next;
	}
	if (max_print < lp->l_count)
		math_str("  ...\n");
}

/* END CODE */
