/**********************************************/
/* Linked list functions                      */
/* Evert Glebbeek 2002, 2003                  */
/* eglebbk@phys.uva.nl                        */
/**********************************************/
#include <allegro.h>
#include <stdlib.h>
#include "linked.h"

#define MAX_LINKS    (128*1024)

static LINK *ll_chunk = NULL;
static LINK **ll_stack = NULL;
static int ll_stack_counter = 0;

LINK *alloc_link(void)
{
   ASSERT(ll_stack_counter<MAX_LINKS);
   ASSERT(ll_stack);
   
   /* return the address of the last free link in the list */
   return ll_stack[ll_stack_counter++];
}

void free_link(LINK *l)
{
   ASSERT(ll_stack_counter);
   ASSERT(ll_stack);
   
   ll_stack[--ll_stack_counter]=l;
}

int get_link_count(void)
{
   return ll_stack_counter;
}

int init_lists(void)
{
   int c;

   ll_chunk = realloc(ll_chunk, sizeof(LINK)*MAX_LINKS);
   ll_stack = realloc(ll_stack, sizeof(LINK *)*MAX_LINKS);
   
   ll_stack_counter = 0;
   for (c=0; c<MAX_LINKS; c++)
      ll_stack[c] = &(ll_chunk[c]);
      
   return (sizeof(LINK)+sizeof(LINK *))*MAX_LINKS;
}

void free_lists(void)
{
   free(ll_chunk);
   free(ll_stack);
   
   ll_chunk = NULL;
   ll_stack = NULL;
   ll_stack_counter = 0;
}

/* Returns TRUE if element d appears in the list */
int in_list(const LINK *list, const void *data)
{
   LINK *lst = (LINK *)list;

   for(; lst; lst = lst->next) {
      if (lst->data == data)
         return 1;
   }
   return 0;
}

/* Duplicates a linked list */
LINK *duplicate_list(LINK *list)
{
   LINK *u = NULL;

   if (list) {
      u = alloc_link();
      if (!u)
         return NULL;
      u->next = NULL;
      u->prev = NULL;
      u->data = list->data;
      u->next = duplicate_list(list->next);
      if (u->next)
         u->next->prev = u;
   }
   return u;
}

/* Destroys a linked list. Calls free_data if not NULL at each link */
void destroy_list(LINK *list, void (*free_data)(void *))
{
   if (list) {
      if (list->next)
         destroy_list(list->next, free_data);
      if (free_data)
         free_data(list->data);
      free_link(list);
   }
}

/* Add a unit to the head of a linked list. Returns the new head of the list. */
LINK *add_to_list(void *data, LINK *list)
{
   LINK *newlink = alloc_link();
   
   ASSERT(newlink);

   newlink->data = data;
   newlink->prev = NULL;
   newlink->next = list;
   if (newlink->next)
      newlink->next->prev = newlink;

   return newlink;
}

/* Add a unit to the end of a linked list. Returns the new head of the list. */
LINK *append_to_list(void *data, LINK *list)
{
   
   if (!list) {
      LINK *newlink = alloc_link();
      ASSERT(newlink);

      newlink->data = data;
      newlink->prev = NULL;
      newlink->next = NULL;
   
      return newlink;
   } else {
      LINK *l = list;
      
      l->next = append_to_list(data, l->next);
      l->next->prev = l;
      return l;
   }
}
/*
LINK *append_to_list(void *data, LINK *list)
{
   LINK *newlink = alloc_link();
   LINK *l;
   
   ASSERT(newlink);

   newlink->data = data;
   newlink->prev = NULL;
   newlink->next = NULL;
   
   if (!list) {
      return newlink;
   } else {
      l = list;
      while (l->next)
         l = l->next;
      l->next = newlink;
      l->next->prev = l;
      return l;
   }
}
*/
/* Add an element to the linked list, in such a way that the list is */
/*  sorted. Returns the new head of the list. */
/* Similar to MergeSort algorithm */
LINK *merge_list(LINK *c, LINK *list, int (*sorter)(const void *, const void *))
{
   if (!list) {
      /* If the list is NULL, then a new list is created */
      c->next = NULL;
      return c;
   } else {
      /* Check if we must insert the character here */
      if (sorter(c->data, list->data) <= 0) {
         /* Insert character at this node */
         c->prev = list->prev;
         c->next = list;
         if (c->prev)
            c->prev->next = c;
         if (c->next)
            c->next->prev = c;
         return c;
      } else {
         /* Insert character after this node */
         list->next = merge_list(c, list->next, sorter);
         if (list->next)
            list->next->prev = list;
         return list;
      }
   }
}

/* The filter_list function can filter a list based on some reference */
/*  function and return a copy of those items in the list that pass the test */
/* The filter function should return TRUE for items that must be included */
LINK *filter_list(LINK *list, int (*filter)(void *))
{
   LINK *nl;
   LINK *l = NULL;
   LINK *h = NULL;
   
   ASSERT(filter);
   
   nl = list;
   /* l==h==NULL, so we need to create the list first */
   while (nl) {
      if (filter(nl->data)) {
         h = l = alloc_link();
         l->next = l->prev = NULL;
         l->data = nl->data;
      }
      nl = nl->next;
   }
   
   /* now the list has been created, continue filling it */
   while (nl) {
      if (filter(nl->data)) {
         l->next = alloc_link();
         l->next->prev = l;
         l->next->next = NULL;
         l->next->data = nl->data;
         l = l->next;
      }
      nl = nl->next;
   }
   
   return h;
}

int get_list_length(LINK *list)
{
   int n = 0;
   LINK *l;
   
   for(l=list; l; l=l->next)
      n++;
      
   return n;
}

/* Remove an element from om a linked list. If there are multiple instances */
/*  in the list, it will delete all of them. */
/* Returns the new head of the list */
LINK *remove_from_list(void *data, LINK *list)
{
   LINK *nul = list;
   LINK *ul = list;
   LINK *u;

   while (ul) {
      if (ul->data == data) {
         u = ul;
         ul = ul->next;
         if (u == nul)
            nul = nul->next;
         if (u->next)
            u->next->prev = u->prev;
         if (u->prev)
            u->prev->next = u->next;
         free_link(u);
      } else {
         ul = ul->next;
      }
   }
   return nul;
}
