/* Source file for the list abstraction.
 *
 */

#include "copyright.h"

#include "list.h"


/* 
 * This is a doubly linked list.
 * An empty list has one listnode that points to itself.
 */


/* REPRESENTATION */

struct listnode {
	Obj       value;	/* Element of the list. */
	struct    listnode *prev; /* Previous node. */
	struct    listnode *next; /* Next node. */
};
typedef struct listnode *  listnode;
#define listnode_NULLP     ((listnode) 0)

struct listrep {
	listnode  top;		/* Head node, never NULLP. */
};
typedef struct listrep *   listrep;
#define listrep_NULLP      ((listrep) 0)


/* TYPE CASTING. */

#define list_rep(list)     ((listrep) list)
#define list_obj(rep)      ((List) rep)


/* STATIC GLOBALS */

int list_id_number = 0;		/* For object name of this list. */
char *list_type_id = "List ";	/* For runtime type checking. */


/* PRIVATE OPERATIONS */

/* Create a listnode with the given value.
 * It's next and prev fields point to itself.
 */
listnode listnode_new (value)
    Obj     value;
{
	listnode node = (listnode) calloc (1, sizeof(struct listnode));

	if (node == listnode_NULLP)
	{
		perror ("Trying to allocate listnode.");
		exit(1);
	}
	node->value = value;
	node->prev = node;
	node->next = node;

	return node;
}


/* OPERATIONS */

/* Allocate and initialize an empty list.
 */
List list_new ()
{
	listrep  rep = (listrep) calloc (1, sizeof(struct listrep));


	if (rep == listrep_NULLP)
	{
		perror ("list_new");
		exit(1);
	}
  
	/* Initialize the ListRep fields. */
	rep->top = listnode_new (NULL);

	return list_obj (rep);
}


/* Push an element onto the list.
 * It both modifies and returns the list.
 */
List list_push (l, o)
    List  l;
    Obj   o;
{
	listrep  rep = list_rep (l);
	listnode top = rep->top;
	listnode newnode = listnode_new(o);


	newnode->next = top->next;
	newnode->prev = top;
	top->next->prev = newnode;
	top->next = newnode;

	return list_obj (rep);
}


/* Push an element onto the list.
 * It both modifies and returns the list.
 * The arguments are reverse from the list_push operation in order
 * to simplify passing this function to set and list traversing operations.
 */
void list_pushr (o, l)
    Obj   o;			/* Element to push onto list. */
    Obj   l;			/* List */
{
	(void) list_push (l, o);
}


/* Pop an element off of the list and return it.
 * If the list is empty return Obj_NULLP.
 */
Obj list_pop (l)
    List  l;
{
	listrep  rep = list_rep (l);
	listnode top = rep->top;
	listnode popnode;	/* Node to pop off and free. */
	Obj      value;


	/* Handle empty list case. */
	if (top->next == top)
		return NULL;

	/* Can assume that the list has at least one element. */
	popnode = top->next;
	top->next = popnode->next;
	popnode->next->prev = popnode->prev;

	value = popnode->value;
	(void) free ((char *) popnode);
	return value;
}


/* Search for an element within a list.
 * Return the first element for which elem_func returns TRUE,
 * or return NULL.
 */
Obj list_search (l, elem_equal, state)
    List   l;
    bool   (*elem_equal) (/* Obj element, Obj state */);
    Obj    state;
{
	listrep  rep = list_rep (l);
	listnode top = rep->top;
	listnode current;


	for (current = top->next ; current != top ; current = current->next)
	{
		if ((*elem_equal) (current->value, state))
			return((Obj) current->value);
	}
	return((Obj) NULL);
}


/* Search for an element within a list, and if found remove it
 * from the list and return it.
 * If not found, return NULL.
 */
Obj list_remove (l, elem_equal, state)
     List   l;
     bool   (*elem_equal) (/* Obj element, Obj state */);
     Obj    state;
{
	listrep  rep = list_rep (l);
	listnode top = rep->top;
	listnode current;


	for (current = top->next ; current != top ; current = current->next)
	{
		if ((*elem_equal) (current->value, state))
		{
			current->prev->next = current->next;
			current->next->prev = current->prev;
			current->next = current;
			current->prev = current;
			return((Obj) current->value);
		}
	}
	return((Obj) NULL);
}


/* Callback routine for list_remove and list_search to
 * locate an object with a particular address in memory.
 */
bool element_eq(current_element, desired_element)
    Obj	current_element;
    Obj	desired_element;
{
	if (current_element == desired_element)
		return True;
	return False;
}


/* Traverse the list starting with the element just pushed.
 */
void list_traverse_lifo (l, elem_func, state)
    List   l;
    void   (*elem_func) (/* Obj element, Obj state */);
    Obj    state;
{
	listrep  rep = list_rep (l);
	listnode top = rep->top;
	listnode current, next;


	for (current = top->next ; current != top ; current = next)
	{
		next = current->next; /* In case elem_func does a remove. */
		(*elem_func) (current->value, state);
	}
}


/* Traverse the list starting with the first element pushed.
 */
void list_traverse_fifo (l, elem_func, state)
    List   l;
    void   (*elem_func) (/* Obj element, Obj state */);
    Obj    state;
{
	listrep  rep = list_rep (l);
	listnode top = rep->top;
	listnode current, prev;


	for (current = top->prev ; current != top ; current = prev)
	{
		prev = current->prev; /* In case elem_func does a remove. */
		(*elem_func) (current->value, state);
	}
}


/* Deallocate a list and all its storage.  This operation
 * returns List_NULLP, which should be assigned to the list
 * variable that was passed to it in order to avoid a dangling
 * reference.
 * The elem_func function is invoked for each element in the list
 * passing the element and the state object.
 * If l is List_NULLP, then don't complain, just return List_NULLP.
 */
List list_free (l, elem_free, state)
    List   l;
    void   (*elem_free) (/* Obj element, Obj state */);
    Obj    state;
{
	listrep  rep = list_rep (l);
	listnode top = rep->top;
	listnode old;
	listnode current;

	if (l == List_NULLP)
		return List_NULLP;

	for (current = top->next ; current != top ; )
	{
		(*elem_free) (current->value, state);
		old = current;
		current = current->next;
		(void) free ((char *) old);
	}
	(void) free ((char *) current);
	(void) free ((char *) rep);
	return List_NULLP;
}


/* Remove and free a subset of the items from a list.
 * Return True if anything removed.
 * The elem_free routine is called with each element,
 * and if it returns true, the element is removed from
 * the list.
 */
bool list_free_subset (l, elem_free, state)
    List   l;
    bool   (*elem_free) (/* Obj element, Obj state */);
    Obj    state;
{
	listrep  rep = list_rep (l);
	listnode top = rep->top;
	listnode next;
	listnode current;
	bool	 got_one = False;
	

	if (l == List_NULLP)
		return got_one;

	for (current = top->next ; current != top ; current = next)
	{
		next = current->next;
		if ((*elem_free) (current->value, state))
		{
			got_one = True;
			current->prev->next = current->next;
			current->next->prev = current->prev;
			(void) free ((char *) current);
		}
	}
	return got_one;
}


/* Create a duplicate of the list.
 * This is a shallow copy in the sense that the new list
 * just contains pointers to the object that were pointed
 * to by the old list.  The objects on the old list are
 * not copied.
 */
List list_dup (l) 
    List    l;
{
	List    newlist = list_new ();

	list_traverse_fifo (l, list_pushr, newlist);
	return newlist;
}

