/* 
 * E-XML Library:  For XML, XML-RPC, HTTP, and related.
 * Copyright (C) 2002-2008  Elias Ross
 * 
 * genman@noderunner.net
 * http://noderunner.net/~genman
 * 
 * 1025 NE 73RD ST
 * SEATTLE WA 98115
 * USA
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * $Id$
 */

package net.noderunner.exml;

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

/** 
 * This is a basic stack data-structure, which holds items internally in
 * an array.
 * It dynamically re-allocates to grow and shrink.  It will not shrink
 * below its initial capacity.
 * <p>
 * Although internally, the datastructure is essentially
 * a list, it does not implement the methods from
 * <code>java.util.List</code> since in the strictest sense, a stack
 * should not allow access to its non-top element.
 * </p>
 */
final class ArrayStack<T>
	extends java.util.AbstractCollection<T>
	implements Stack<T>
{

	private Object items[];
	private int size;
	private int initialCapacity;

	/**
	 * Creates a ArrayStack with a default capacity of 16.
	 * The capacity will automatically double or half if necessary.
	 */
	public ArrayStack() 
	{
		this(16);
	}

	/**
	 * Creates a ArrayStack with a specific capacity.  
	 * The capacity will automatically double or half if necessary.
	 * @param capacity non-zero, positive capacity
	 */
	public ArrayStack(int capacity) 
	{
		if (capacity <= 0)
			throw new IllegalArgumentException("Must specify non-zero, positive capacity: " + capacity);
		// the last entry is always null
		items = new Object[capacity];
		size = 0;
		initialCapacity = capacity;
	}

	@Override
	public int size() {
		return size;
	}

	private void expandIfNeeded() {
		if (size == items.length) {
			Object newItems[] = new Object[items.length * 2];
			System.arraycopy(items, 0, newItems, 0, size);
			items = newItems;
		}
	}

	private void shrinkIfNeeded() {
		if (items.length > initialCapacity && size < items.length / 2) {
			Object newItems[];
			if (size <= initialCapacity / 2)
				newItems = new Object[initialCapacity];
			else
				newItems = new Object[items.length / 2];
			System.arraycopy(items, 0, newItems, 0, size);
			items = newItems;
		}
	}

	/**
	 * Adds an object to the end of this array stack.
	 * This object can be null.
	 * @return true
	 * @see #push
	 */
	@Override
	public boolean add(T element) {
		expandIfNeeded();
		items[size++] = element;
		return true;
	}
	
	@SuppressWarnings("unchecked")
    private T item(int i) {
		return (T)items[i];
	}

	private class ArrayStackIterator implements Iterator<T>
	{
		int at = size;
		@SuppressWarnings("unchecked")
        public T next() {
			if (at == 0)
				throw new NoSuchElementException("No more elements");
			return (T)item(--at);
		}
		public boolean hasNext() {
			return at != 0;
		}
		public void remove() {
			if (at == size)
				throw new UnsupportedOperationException("Call next first");
			if (at != size - 1)
				throw new UnsupportedOperationException("Cannot remove non-top elements");
			pop();
		}
	}

	/**
	 * Returns an iterator that first returns the top element, then
	 * the next item below, etc.  The <code>Iterator.remove</code>
	 * operation is only supported for removing the top element.
	 */
	@Override
	public Iterator<T> iterator() {
		return new ArrayStackIterator();
	}

	@Override
	public void clear() {
		size = 0;
		shrinkIfNeeded();
	}

	public void push(T element) {
		add(element);
	}

	public T peek() {
		if (size == 0)
			throw new NoSuchElementException("Empty ArrayStack");
		return item(size - 1);
	}

	public T pop() {
		if (size == 0)
			throw new NoSuchElementException("Empty ArrayStack");
		shrinkIfNeeded();
		size--;
		T top = item(size);
		items[size] = null;
		return top;
	}

	/**
	 * For unit-testing, returns the capacity of this collection.
	 */
	int capacity() {
		return items.length;
	}

	/**
	 * Constructs a new instance.
	 */
    public static <T> ArrayStack<T> create() {
        return new ArrayStack<T>();
    }

}
