/* 
 * Kodkod -- Copyright (c) 2005-2011, Emina Torlak
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package kodkod.util.collections;

import java.util.EmptyStackException;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * A Stack implementation based on a singly linked list.
 * @author Emina Torlak
 */
public final class LinkedStack<T> extends  Stack<T> {
	private StackEntry<T> head;
	private int size;
	
	/**
	 * Constructs an empty stack.
	 * @ensures no this.elems'
	 */
	public LinkedStack() {
		head = null;
		size = 0;
	}

	/**
	 * @see kodkod.util.collections.Stack#size()
	 */
	public int size() { return size; }
	
	/**
	 * Pushes an item onto the top of this stack and returns it. 
	 * @ensures this.size' = this.size + 1 && this.elems'[0] = item &&
	 *          all i: [0..this.size) | this.elems'[i+1] = this.elems[i]
	 * @return item
	 */
	public T push(T item) {
		head = new StackEntry<T>(item, head);
		size++;
		return item;
	}
	
	/**
	 * Removes the object at the top of this stack and returns that object as the value of this function.
	 * @ensures this.size' = this.size - 1 && 
	 *          all i: [1..this.size) | this.elems'[i-1] = this.elems[i]
	 * @return this.elems[0]
	 * @throws EmptyStackException  no this.elems
	 */
	public T pop() {
		if (head==null) throw new EmptyStackException();
		final T pop = head.data;
		head = head.next;
		size--;
		return pop;
	}
	
	/**
	 * Looks at the object at the top of this stack without removing it from the stack.
	 * @return this.elems[0]
	 * @throws EmptyStackException  no this.elems
	 */
	public T peek() {
		if (head==null) throw new EmptyStackException();
		return head.data;
	}
	
	/**
	 * Returns the 1-based position where an object is on this stack. 
	 * If the object o occurs as an item in this stack, this method 
	 * returns the distance from the top of the stack of the occurrence 
	 * nearest the top of the stack; the topmost item on the stack is 
	 * considered to be at distance 1. The equals method is used to 
	 * compare o to the items in this stack.
	 * @return o in this.elems[int] => min(this.elems.o) + 1, -1
	 */
	public int search(Object o) {
		StackEntry<T> e = head;
		int position = 1;
		while(e != null) {
			if (equal(o, e.data))
				return position;
			e = e.next;
			position++;
		}
		return -1;
	}
	
	/**
	 * Returns true if the stack is empty; otherwise returns false.
	 * @return no this.elems
	 */
	public boolean empty() { return head == null; }
	
	/**
	 * Iterates over the items in this LinkedStack, starting
	 * at the top of the stack and working its way down.
	 * @return iterator over the elements in this stack.
	 */
	public Iterator<T> iterator() {
		return new Iterator<T>() {
			private StackEntry<T> cursor = head, prev = null, pprev = null;
			public boolean hasNext() {
				return cursor != null;
			}

			public T next() {
				if (cursor==null) throw new NoSuchElementException();
				pprev = prev;
				prev = cursor;
				cursor = cursor.next;
				return prev.data;
			}

			public void remove() {
				if (prev==pprev) {
					throw new UnsupportedOperationException();
				} else if (prev==head) {
					head = cursor;
				} else {
					pprev.next = cursor;
					prev.next = null;
				}
				prev = pprev;
				size--;
			}
			
		};
	}
	
	/**
	 * Represents a stack entry. 
	 * @specfield data: T
	 * @specfield next: StackEntry<T>
	 */
	private static final class StackEntry<T> {
		T data;
		StackEntry<T> next;
		
		StackEntry(T data, StackEntry<T> next) {
			this.data = data;
			this.next = next;
		}
	}

	
}
