/*
 *
 * SinglyLinkedList.java
 *
 * An implementation of the List interface using a 
 * singly linked list.
 *
 * (P) 2000 Laurentiu Cristofor
 * modified for Spring 2003 Dennis Wortman
 * modified for Fall 2004 by Betty O'Neil
 */

import java.util.Iterator;
import java.util.Collection;
import java.util.List;
import java.util.NoSuchElementException;

public class SinglyLinkedList implements Collection
{
  // this is our node class, a singly linked node!
  private class Node
  {
    Object data;
    Node next;

    Node(Object o, Node n)
    {
      data = o;
      next = n;
    }

    Node(Object o)
    {
      this(o, null);
    }

    Node( )
    {
      this(null,null);
    }
  }

  private Node head; // the head reference
  private int size;  // we keep the size here

  public SinglyLinkedList()
  {
      head = new Node(); // dummy header node!
  }

  // helper method; we cannot use equals with a null reference
  private boolean areEqual(Object obj1, Object obj2)
  {
      return (obj1 == null ? obj1 == obj2 : obj1.equals(obj2));
  }

  // helper method
  // insert a new node with given element after a given node in list
  private void insertAfter(Node node, Object element)
  {
      // create the new node
      Node n = new Node(element, node.next);
      // insert the node in the list
      node.next = n;
      // update the list size
      size++;
  }

  // iterative implementation
  public void add(int index, Object element)
  {
      if (index < 0 || index > size)
	throw new IllegalArgumentException("invalid index: " + index);

      // we move to the node before the place where we need to insert
      Node current = head;
      for (int i = 0; i < index; i++)
	current = current.next;

      insertAfter(current, element);
  }

  // iterative implementation
  public boolean add(Object o)
  {
      // we move to the last node
      Node current = head;
      while (current.next != null)
	current = current.next;

      insertAfter(current, o);

      return true;
  }

  // helper method
  private void insertAfter(Node node, Collection c)
  {
      // get an iterator over the collection
      Iterator iter = c.iterator();

      while (iter.hasNext())
      {
	  insertAfter(node, iter.next());
	  // keep node pointing to the end of the list
	  node = node.next;
      }
  }

  // iterative implementation
  public boolean addAll(Collection c)
  {
      // check first if there is anything to add
      if (c.isEmpty())
	return true;

      // we move to the last node
      Node current = head;
      while (current.next != null)
	current = current.next;

      insertAfter(current, c);

      return true;
  }

  // iterative implementation
  public boolean addAll(int index, Collection c)
  {
      if (index < 0 || index > size)
	throw new IllegalArgumentException("invalid index: " + index);

      // check if there is anything to add
      if (c.isEmpty())
	return true;

      // we move to the node before the place where we need to insert
      Node current = head;
      for (int i = 0; i < index; i++)
	current = current.next;

      insertAfter(current, c);

      return true;
  }

  public void clear()
  {
      head.next = null;
      size = 0;
  }

  // recursive implementation
  public boolean contains(Object o)
  {
      return contains(head.next, o);
  }

  // here is where the recursion takes place
  private boolean contains(Node node, Object o)
  {
      // termination check
      if (node == null)
	return false;

      if (areEqual(o, node.data))
	return true;
      else
	return contains(node.next, o);
  }

  // here we'll simply use the precedent method
  public boolean containsAll(Collection c)
  {
      Iterator iter = c.iterator();

      while (iter.hasNext())
	if (!contains(iter.next()))
	  return false;

      return true;
  }

  // iterative implementation
  public boolean equals(Object o)
  {
      // check for identity first
      if (o == this)
	return true;

      // o must be a List to be able to compare
      if (! (o instanceof List))
	return false;

      List other = (List)o;

      // sizes must be equal
      if (size != other.size())
	return false;

      // scan both lists using iterators and return false
      // at the first difference encountered.
      for (Iterator i1 = iterator(), i2 = other.iterator();
	   i1.hasNext(); )
	if (!areEqual(i1.next(), i2.next()))
	  return false;

      // no differences encountered
      return true;
  }

  // recursive implementation
  public Object get(int index)
  {
      if (index < 0 || index >= size)
	throw new IllegalArgumentException("invalid index: " + index);

      return get(head.next, index);
  }

  // here is where the recursion takes place
  private Object get(Node node, int index)
  {
      if (index == 0)
	return node.data;
      else
	return get(node.next, index - 1);
  }

  // no implementation
  public int hashCode()
  {
      throw new UnsupportedOperationException();
  }

  // iterative implementation
  public int indexOf(Object o)
  {
      int index = 0;

      // doing this test outside the loop
      // instead of inside the loop 
      // makes the code more efficient
      // since we do the test only once!
      if (o == null)
	for (Node current = head.next; current != null; 
	     current = current.next, index++)
        {
	    if (current.data == null)
	      return index;
        }
      else
	for (Node current = head.next; current != null; 
	     current = current.next, index++)
	  if (o.equals(current.data))
	    return index;

      return -1;
  }

  public Iterator iterator()
  {
      return new SLLIterator();
  }

  private class SLLIterator implements Iterator
  {
      // We maintain current and previous as the iterator moves
      // down the list (pa5: or just previous, if you want, but just
      // current is not enough to do remove())
      private Node current;
      private Node previous;    // useful for remove()

      // pa5: you fill in methods
      public boolean hasNext( )
      {
	  throw new UnsupportedOperationException();
      }
      
      public Object next( )
      {
	  throw new UnsupportedOperationException();
      }

      public void remove()
      {
	  throw new UnsupportedOperationException();
      }
  }

  // iterative implementation
  public int lastIndexOf(Object o)
  {
      int last_index = -1;
      int index = 0;

      // doing this test outside the loop
      // instead of inside the loop 
      // makes the code more efficient
      // since we do the test only once!
      if (o == null)
	for (Node current = head.next; current != null; 
	     current = current.next, index++)
        {
	    if (current.data == null)
	      last_index = index;
        }
      else
	for (Node current = head.next; current != null; 
	     current = current.next, index++)
	  if (o.equals(current.data))
	    last_index = index;

      return last_index;
  }

  // recursive implementation
  public Object remove(int index)
  {
      if (index < 0 || index >= size)
	throw new IllegalArgumentException("invalid index: " + index);

      return remove(head, index);
  }

  // the recursion takes place here
  private Object remove(Node node, int index)
  {
      if (index == 0)
      {
	  Object element = node.next.data;
	  node.next = node.next.next;
	  size --;
	  return element;
      }
      else
	return remove(node.next, index - 1);
  }

  // iterative method
  public boolean remove(Object o)
  {
      Node current = head;

      // at the end of the loop current will either be positioned before
      // the node to be removed or on the last node of the list
      if (o == null)
	while (current.next != null && current.next.data != null)
	  current = current.next;
      else
	while (current.next != null && !o.equals(current.next.data))
	  current = current.next;

      if (current.next == null)
	return false;

      // remove node
      current.next = current.next.next;
      size --;
      return true;
  }

  // iterative method
  public boolean removeAll(Collection c)
  {
      boolean changed = false;

      for (Node current = head; current.next != null; )
	if (c.contains(current.next.data))
        {
	    // delete node but don't update current!
	    current.next = current.next.next;
	    size--;
	    changed = true;
        }
	else
	  current = current.next;

      return changed;
  }

  // iterative method
  public boolean retainAll(Collection c)
  {
      boolean changed = false;

      for (Node current = head; current.next != null; )
	if (!c.contains(current.next.data))
        {
	    // delete node but don't update current!
	    current.next = current.next.next;
	    size--;
	    changed = true;
        }
	else
	  current = current.next;

      return changed;
  }

  // recursive method
  public Object set(int index, Object element)
  {
      if (index < 0 || index >= size)
	throw new IllegalArgumentException("invalid index: " + index);

      return set(head.next, index, element);
  }

  // the recursion takes place here
  private Object set(Node node, int index, Object element)
  {
      if (index == 0)
      {
	  Object data = node.data;
	  node.data = element;
	  return data;
      }

      return set(node.next, index - 1, element);
  }

  public int size()
  {
      return size;
  }

  // we don't implement this method to keep the homework simple
  public List subList(int fromIndex, int toIndex)
  {
      throw new UnsupportedOperationException();
  }

  // iterative implementation
  public Object[] toArray()
  {
      Object[] data = new Object[size];
      int i = 0;

      for (Node current = head; current.next != null; current = current.next)
	data[i++] = current.next.data;

      return data;
  }

  // iterative implementation
  public Object[] toArray(Object[] a)
  {
      if (a.length < size)
	a = (Object[])java.lang.reflect.Array.
	  newInstance(a.getClass().getComponentType(), size);

      int i = 0;

      for (Node current = head; current.next != null; current = current.next)
	a[i++] = current.next.data;

      if (a.length > size)
	a[size] = null;
      
      return a;
  }

  public boolean isEmpty()
  {
    return size == 0;
  }
}        
