/*
   List.c, Copyright  (c) by George Fankhauser,
   Swiss Federal Institute of Technology,
   Computer Engineering and Networks Laboratory.
 
   TOPSY -- A Teachable Operating System.
   Implementation of a tiny and simple
   micro kernel for teaching purposes.
 
   For further information, please visit http://www.tik.ee.ethz.ch/~topsy
 
   This software is provided under the terms of the GNU General Public Licence.
 
   A full copy of the GNU GPL is provided in the file COPYING found in the 
   development root of Topsy.
 */
/*
	
	File:                  $Source: /home/ruf/cvs_Topsy/cvs/Topsy/Topsy/List.c,v $
	Author(s):             George Fankhauser
	Affiliation:           ETH Zuerich, TIK
	Version:               $Revision: 2922 $
	Creation Date:         
	Last Date of Change:   $Date: 2007-03-19 17:48:52 +0100 (Mon, 19 Mär 2007) $      by: $Author: baderma $
*/

#include "List.h"
#include "Error.h"
#include "Support.h"

/* forward declarations */
static void removeElem(List list, ListElement hintElem);
Error listSearch(List list, void *item, Address *found);

List listNew()
{
  List list;
  Error xerror = hmAlloc((Address*)&list, sizeof(ListDesc));

  if (xerror != HM_ALLOCOK)
  {
    ERROR("hmAlloc failed");
    return NULL;
  }
  list->first = NULL;
  list->current = NULL; /* current points to the next element actually */
  /* this solves the problem when an item is removed */
  /* while getFirst/getNext are in use */
  list->last = NULL;

  return list;
}


Error listFree(List list)
{
  ListElement elem, prev;

  if (list == NULL)
    return LIST_ERROR;
  elem = list->first;
  while (elem != NULL)
  {
    prev = elem;
    elem = elem->next;
    hmFree(prev);
  }
  hmFree(list);
  return LIST_OK;
}


Error listAddInFront(List list, void* item, Address* hint)
{
  ListElement elem;
  Error xerror;

  if (list == NULL)
    return LIST_ERROR;
  xerror = hmAlloc((Address*)&elem, sizeof(ListElementDesc));
  if (xerror != HM_ALLOCOK)
  {
    ERROR("hmAlloc failed");
    return LIST_ERROR;
  }

  /* if the user of the list provides space to hint the position
   * of an element later, we write to the specified address
   */
  if (hint != NULL)
    *hint = elem;

  elem->next = list->first;
  elem->prev = NULL;

  if (list->first != NULL)
  {
    list->first->prev = elem;
  }
  else
  {
    list->last = elem;
  }
  list->first = elem;

  elem->item = item;

  return LIST_OK;
}


Error listAddAtEnd(List list, void* item, Address* hint)
{
  ListElement elem;
  Error xerror;

  if (list == NULL)
    return LIST_ERROR;
  if (list->first == NULL)
  {
    return (listAddInFront(list, item, hint));
  }
  else
  {
    xerror = hmAlloc((Address*)&elem, sizeof(ListElementDesc));
    if (xerror != HM_ALLOCOK)
    {
      ERROR("hmAlloc failed");
      return (Error)NULL;
    }

    /* if the user of the list provides space to hint the position
     * of an element later, we write to the specified address
     */
    if (hint != NULL)
      *hint = elem;

    elem->next = NULL;
    elem->prev = list->last;
    list->last->next = elem;
    list->last = elem;

    elem->item = item;
  }
  return LIST_OK;
}


Error listRemove(List list, void* item, Address hint)
{
  ListElement elem, prev, hintElem;

  if (list == NULL || item == NULL)
    return LIST_ERROR;
  hintElem = (ListElement)hint;

  /* hint is valid, use direct access to doubly linked list */
  if (hint != NULL && (hintElem->item == item))
  {
    /* remove element from listFrom */
    removeElem(list, hintElem);
    hintElem->item = NULL;
    hmFree(hintElem);
    return LIST_OK;
  }
  /* we have to search the list linearly */
  else
  {
    if (list->first == NULL)
      return LIST_ERROR;
    if (list->first->item == item)
    {
      /* if enum elem is to be removed, advance current ptr */
      if (list->first == list->current)
      {
        list->current = list->current->next;
      }
      prev = list->first;
      list->first = prev->next;
      if (list->first != NULL)
        list->first->prev = NULL; /***/
      else
        list->last = NULL; /***/
      prev->item = NULL;
      hmFree(prev);
      return LIST_OK;
    }
    prev = list->first;
    elem = prev->next;
    while (elem != NULL)
    {
      if (elem->item == item)
      {
        /* if enum elem is to be removed, advance current ptr */
        if (elem == list->current)
        {
          list->current = list->current->next;
        }
        prev->next = elem->next;
        if (prev->next != NULL)
          prev->next->prev = prev; /***/
        else
          list->last = prev; /***/
        elem->item = NULL;
        hmFree(elem);
        return LIST_OK;
      }
      prev = prev->next;
      elem = elem->next;
    }
  }
  return LIST_ERROR;
}


Error listSearch(List list, void *item, Address *found)
{

  ListElement listElem = list->first;
  *found = NULL;
  while (listElem && listElem->item != item)
    listElem = listElem->next;
  *found = listElem;

  return LIST_OK;
}

Error listMoveToEnd(List list, void* item, Address hint)
{
  ListElement hintElem;

  if (list == NULL || item == NULL)
    return LIST_ERROR;
  hintElem = (ListElement)hint;

  //-LR   asm(" cli ; hlt");

  while (hintElem && hintElem->item != item)
  {
    printf("ERROR: hintElem does not match item\n");
    listSearch(list, item, (Address *)&hintElem);
  }

  //-LR   asm(" cli ; hlt");

  /* hint is valid, use direct access to doubly linked list */
  if (hintElem != NULL && (hintElem->item == item))
  {
    if (list->last == hintElem)
      return LIST_OK;       /* already at end */

    /* remove element */
    if (list->first == hintElem)
    {
      list->first->next->prev = NULL;
      list->first = list->first->next;
    }
    else
    {
      hintElem->next->prev = hintElem->prev;
      hintElem->prev->next = hintElem->next;
    }
    /* add at end */
    list->last->next = hintElem;
    hintElem->prev = list->last;
    hintElem->next = NULL;
    list->last = hintElem;
    return LIST_OK;
  }
  /* linear search not supported */
  return LIST_ERROR;
}


Error listSwap(List listFrom, List listTo, void* item, Address hint)
{
  ListElement hintElem;

  if (listFrom == NULL || listTo == NULL || item == NULL)
    return LIST_ERROR;
  hintElem = (ListElement)hint;

  /* hint is valid, use direct access to doubly linked list */
  if (hint != NULL && (hintElem->item == item))
  {
    /* remove element from listFrom */
    removeElem(listFrom, hintElem);
    /* add it to listTo (in front) */
    hintElem->next = listTo->first;
    if (listTo->first != NULL)
      listTo->first->prev = hintElem;
    listTo->first = hintElem;
    hintElem->prev = NULL;
    /* if the new one is the only element, set last to first */
    if (listTo->last == NULL)
      listTo->last = hintElem;

    return LIST_OK;
  }
  /* linear search not supported */
  return LIST_ERROR;
}


static void removeElem(List list, ListElement hintElem)
{
  /* if enum elem is to be removed, advance current ptr */
  if (hintElem == list->current)
  {
    list->current = list->current->next;
  }
  /* first element */
  if (hintElem == list->first)
  {
    list->first = hintElem->next;
    if (hintElem->next != NULL)
      hintElem->next->prev = NULL;
    else
      list->last = NULL;
  }
  /* last element */
  else if (hintElem == list->last)
  {
    list->last = hintElem->prev;
    if (hintElem->prev != NULL)
      hintElem->prev->next = NULL;
    else
      list->first = NULL;
  }
  /* all the others in between */
  else
  {
    hintElem->next->prev = hintElem->prev;
    hintElem->prev->next = hintElem->next;
  }
}


int listIsEmpty(List list)
{
  return (list == NULL || list->first == NULL);
}


Error listGetFirst(List list, void** itemPtr)
{
  if (list == NULL)
    return LIST_ERROR;
  if (list->first == NULL)
  {
    *itemPtr = NULL;
    list->current = NULL;
  }
  else
  {
    *itemPtr = list->first->item;
    list->current = list->first->next;
  }
  return LIST_OK;
}


Error listGetNext(List list, void** itemPtr)
{
  if (list == NULL)
    return LIST_ERROR;
  if (list->current == NULL)
  {
    *itemPtr = NULL;
  }
  else
  {
    *itemPtr = list->current->item;
    list->current = list->current->next;
  }
  return LIST_OK;
}
