package freenet.support;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * LRU Queue
 * 
 * push()'ing an existing object move it to tail, no duplicated object are ever added.
 */
public class LRUQueue<T> {

    /*
     * I've just converted this to using the DLList and Hashtable
     * this makes it Hashtable time instead of O(N) for push and
     * remove, and Hashtable time instead of O(1) for pop.  Since
     * push is by far the most done operation, this should be an
     * overall improvement.
     */
	private final DoublyLinkedListImpl<QItem<T>> list = new DoublyLinkedListImpl<QItem<T>>();
	private final Map<T, QItem<T>> hash = new HashMap<T, QItem<T>>();
    
    public LRUQueue() {
    }
    
    /**
     *       push()ing an object that is already in
     *       the queue moves that object to the most
     *       recently used position, but doesn't add
     *       a duplicate entry in the queue.
     */
	public final synchronized void push(T obj) {
		if (obj == null)
			throw new NullPointerException();

		QItem<T> insert = hash.get(obj);
        if (insert == null) {
			insert = new QItem<T>(obj);
            hash.put(obj,insert);
        } else {
            list.remove(insert);
        }

        list.unshift(insert);
    } 

    /**
     * push to bottom (least recently used position)
     */
	public synchronized void pushLeast(T obj) {
		if (obj == null)
			throw new NullPointerException();

		QItem<T> insert = hash.get(obj);
        if (insert == null) {
			insert = new QItem<T>(obj);
            hash.put(obj,insert);
        } else {
            list.remove(insert);
        }

        list.push(insert);
	}
	
    /**
     *  @return Least recently pushed Object.
     */
	public final synchronized T pop() {
        if (!list.isEmpty()) {
			return hash.remove(list.pop().obj).obj;
        } else {
            return null;
        }
    }

    public final int size() {
        return list.size();
    }
    
    public final synchronized boolean remove(Object obj) {
		if (obj == null)
			throw new NullPointerException();

		QItem<T> i = hash.remove(obj);
	if(i != null) {
	    list.remove(i);
	    return true;
	} else {
	    return false;
	}
    }
    
    /**
     * Check if this queue contains obj
     * @param obj Object to match
     * @return true if this queue contains obj.
     */
    public final synchronized boolean contains(Object obj) {
        return hash.containsKey(obj);
    }
    
	public Enumeration<T> elements() {
        return new ItemEnumeration();
    }

	private class ItemEnumeration implements Enumeration<T> {

		private Enumeration<QItem<T>> source = list.reverseElements();
       
        @Override
        public boolean hasMoreElements() {
            return source.hasMoreElements();
        }

		@Override
		public T nextElement() {
			return source.nextElement().obj;
        }
    }

	private static class QItem<T> extends DoublyLinkedListImpl.Item<QItem<T>> {
		public T obj;

        public QItem(T obj) {
            this.obj = obj;
        }
    }

    /**
     * Return the objects in the queue as an array in an arbitrary and meaningless
     * order.
     */
	public synchronized Object[] toArray() {
		return hash.keySet().toArray();
	}

    /**
     * Return the objects in the queue as an array in an arbitrary and meaningless
     * order.
	 * @param array The array to fill in. If it is too small a new array of the same type will be allocated.
     */
	public synchronized <E> E[] toArray(E[] array) {
		return hash.keySet().toArray(array);
	}
	
	/**
	 * Return the objects in the queue as an array. The <strong>least</strong>
	 * recently used object is in <tt>[0]</tt>, the <strong>most</strong>
	 * recently used object is in <tt>[array.length-1]</tt>.
	 */

	public synchronized Object[] toArrayOrdered() {
		Object[] array = new Object[list.size()];
		int x = 0;
		for (Enumeration<QItem<T>> e = list.reverseElements(); e.hasMoreElements();) {
			array[x++] = e.nextElement().obj;
		}
		return array;
	}

	/**
	 * Return the objects in the queue as an array. The <strong>least</strong>
	 * recently used object is in <tt>[0]</tt>, the <strong>most</strong>
	 * recently used object is in <tt>[array.length-1]</tt>.
	 * 
	 * @param array
	 *            The array to fill in. If it is too small a new array of the
	 *            same type will be allocated.
	 */

	@SuppressWarnings("unchecked")
	public synchronized <E> E[] toArrayOrdered(E[] array) {
		array = toArray(array);
		int listSize = list.size();
		if(array.length != listSize)
			throw new IllegalStateException("array.length="+array.length+" but list.size="+listSize);
		int x = 0;
		for (Enumeration<QItem<T>> e = list.reverseElements(); e.hasMoreElements();) {
			array[x++] = (E) e.nextElement().obj;
		}
		return array;
	}
	
	public synchronized boolean isEmpty() {
		return hash.isEmpty();
	}
	
	public synchronized void clear() {
		list.clear();
		hash.clear();
	}

	public synchronized T get(T obj) {
		QItem<T> val = hash.get(obj);
		if(val == null) return null;
		return val.obj;
	}
}

