package com.limegroup.gnutella.util;

import java.util.ConcurrentModificationException;
import java.util.NoSuchElementException;

import com.limegroup.gnutella.Assert;

/** 
 * A very simple fixed-size double-ended queue, i.e., a circular buffer.
 * The fixed size is intentional, not the result of laziness; use this 
 * data structure when you want to use a fix amount of resources.
 * This is not thread-safe.
 * For a minimal amount of efficiency, the internal buffer is only
 * allocated on the first insertion or retrieval, allowing lots of
 * Buffers to be created that may not be used.
 */
public final class IntBuffer implements Cloneable {
    /**
     * The abstraction function is
     *   [ buf[head], buf[head+1], ..., buf[tail-1] ] if head<=tail
     * or
     *   [ buf[head], buf[head+1], ..., buf[size-1], 
     *     buf[0], buf[1], ..., buf[tail-1] ]         otherwise
     *
     * Note that buf[head] is the location of the head, and
     * buf[tail] is just past the location of the tail. This
     * means that there is always one unused element of the array.
     * See p. 202 of  _Introduction to Algorithms_ by Cormen, 
     * Leiserson, Rivest for details.
     *
     * Also note that size is really the MAX size of this+1, i.e., 
     * the capacity, not the current size.
     *
     * INVARIANT: buf.length=size
     *            0<=head, tail<size
     *            size>=2
     */
    private final int size;
    private int buf[];
    private int head;
    private int tail;

    /** 
     * @requires size>=1
     * @effects creates a new, empty buffer that can hold 
     *  size elements.
     */
    public IntBuffer(int size) {
        Assert.that(size>=1);
        //one element of buf unused
        this.size = size+1;
        // lazily initialized to preserver memory.
        //buf = new int[size+1];
        head = 0;
        tail = 0;
    }

    /** "Copy constructor": constructs a new shallow copy of other. */
    public IntBuffer(IntBuffer other) {
        this.size=other.size;
        this.head=other.head;
        this.tail=other.tail;

        if(other.buf != null) {
            this.buf=new int[other.buf.length];
            System.arraycopy(other.buf, 0,
                             this.buf, 0,
                             other.buf.length);
        }
    }
    
    private void initialize() {
        if(buf == null)
            buf = new int[size + 1];
    }

	/*
	public int[] toArray() {
		int[] newBuf = new int[size];

		int index = tail-head;
		System.arraycopy(buf, head,
						 newBuf, 0,
						 index);

		System.arraycopy(buf, 0,
						 newBuf, index,
						 size-index);
	}
	*/

    /** Returns true iff this is empty. */
    public boolean isEmpty() {
        return head==tail;
    }

    /** Returns true iff this is full, e.g., adding another element 
     *  would force another out. */
    public boolean isFull() {
        return increment(tail)==head;
    }

    /** Same as getSize(). */
    public final int size() {
        return getSize();
    }

    /** Returns the number of elements in this.  Note that this never
     *  exceeds the value returned by getCapacity. */
    public int getSize() {
        if (head<=tail)
            //tail-1-head+1                  [see abstraction function]
            return tail-head;
        else
            //(size-1-head+1) + (tail-1+1)   [see abstraction function]
            return size-head+tail;
    }

    /** Returns the number of elements that this can hold, i.e., the
     *  max size that was passed to the constructor. */
    public int getCapacity() {
        return size-1;
    }

    private int decrement(int i) {
        if (i==0)
            return size-1;
        else
            return i-1;
    }

    private int increment(int i) {
        if (i==(size-1))
            return 0;
        else
            return i+1;
    }

    /** Returns the j s.t. buf[j]=this[i]. */
    private int index(int i) throws IndexOutOfBoundsException {        
        if (i<0 || i>=getSize())
            throw new IndexOutOfBoundsException("index: " + i + ", size: " + getSize());
        return (i+head) % size;
    }

    /** If i<0 or i>=getSize(), throws IndexOutOfBoundsException.
      * Else returns this[i] */
    public int get(int i) throws IndexOutOfBoundsException {
        initialize();
        return buf[index(i)];
    }

    /*
     * @modifies this[i]
     * @effects If i<0 or i>=getSize(), throws IndexOutOfBoundsException 
     *  and does not modify this.  Else this[i]=o.
     */
    public void set(int i, int value) throws IndexOutOfBoundsException {
        initialize();
        buf[index(i)] = value;
    }

    /** 
	 * Same as addFirst(x). 
	 */
    public int add(int x) {
        return addFirst(x);
    }

    /** 
     * @modifies this
     * @effects adds x to the head of this, removing the tail 
     *  if necessary so that the number of elements in this is less than
     *  or equal to the maximum size.  Returns the element removed, or null
     *  if none was removed.
     */
    public int addFirst(int x) {
        initialize();
		int ret = -1;
        if (isFull())
            ret=removeLast();
        head=decrement(head);
        buf[head]=x;
        return ret;
    }

    /** 
     * @modifies this
     * @effects adds x to the tail of this, removing the head 
     *  if necessary so that the number of elements in this is less than
     *  or equal to the maximum size.  Returns the element removed, or null
     *  if none was removed.
     */
    public int addLast(int x) {
        initialize();
		int ret = -1;
        if (isFull())
            ret=removeFirst();
        buf[tail]=x;
        tail=increment(tail);
        return ret;
    }


    /**
     * Returns true if the input object x is in the buffer.
     */
    public boolean contains(int x) {
        IntBufferIterator iterator = iterator();
        while (iterator.hasNext())
            if (iterator.nextInt() == x)
                return true;
        return false;
    }


    /**
     * Returns the head of this, or throws NoSuchElementException if
     * this is empty.
     */
    public int first() throws NoSuchElementException {
        if (isEmpty())
            throw new NoSuchElementException();
        return buf[head];
    }
    
    /**
     * Returns the tail of this, or throws NoSuchElementException if
     * this is empty.
     */
    public int last() throws NoSuchElementException {
        if (isEmpty())
            throw new NoSuchElementException();
        return buf[decrement(tail)];
    }    

    /**
     * @modifies this
     * @effects Removes and returns the head of this, or throws 
     *   NoSuchElementException if this is empty.
     */
    public int removeFirst() throws NoSuchElementException {
        if (isEmpty())
            throw new NoSuchElementException();
        int ret=buf[head];
        buf[head]=-1;     //optimization: don't retain removed values
        head=increment(head);
        return ret;
    }

    /**
     * @modifies this
     * @effects Removes and returns the tail of this, or throws 
     *   NoSuchElementException if this is empty.
     */
    public int removeLast() throws NoSuchElementException {
        if (isEmpty())
            throw new NoSuchElementException();
        tail=decrement(tail);
        int ret=buf[tail];
        buf[tail]=-1;    //optimization: don't retain removed values
        return ret;
    }

    /**
     * @modifies this
     * @effects Removes and returns the i'th element of this, or
     *  throws IndexOutOfBoundsException if i is not a valid index
     *  of this.  In the worst case, this runs in linear time with
     *  respect to size().
     */ 
    public int remove(int i) throws IndexOutOfBoundsException {
        int ret=get(i);
        //Shift all elements to left.  This could be micro-optimized.
        for (int j=index(i); j!=tail; j=increment(j)) {
            buf[j]=buf[increment(j)];
        }
        //Adjust tail pointer accordingly.
        tail=decrement(tail);
        return ret;
    }

    /**
     * @modifies this
     * @effects removes the first occurrence of x in this,
     *  if any, as determined by .equals.  Returns true if any 
     *  elements were removed.  In the worst case, this runs in linear 
     *  time with respect to size().
     */
    public boolean removeValue(int x) {
        for (int i=0; i<getSize(); i++) {
            if (x == get(i)) {
                remove(i);
                return true;
            }
        }
        return false;
    }

    /**
     * @modifies this
     * @effects removes all occurrences of x in this,
     *  if any, as determined by .equals.  Returns true if any 
     *  elements were removed.   In the worst case, this runs in linear 
     *  time with respect to size().
     */
    public boolean removeAll(int x) {
        boolean ret=false;
        for (int i=0; i<getSize(); i++) {
            if (x == get(i)) {
                remove(i);
                i--;
                ret=true;
            }
        }
        return ret;
    }


    /**
     * @modifies this
     * @effects removes all elements of this.
     */
    public void clear() {
        head=0;
        tail=0;
    }

    /** 
     * @effects returns an iterator that yields the elements of this, in 
     *  order, from head to tail.
     * @requires this not modified will iterator in use.
     */
    public IntBufferIterator iterator() {
        return new IntBufferIterator();
    }

	/**
	 * Specialized <tt>Iterator</tt> for <tt>IntBuffer</tt> that iterates
	 * over an array of ints.
	 */
    private class IntBufferIterator extends UnmodifiableIterator {
        /** The index of the next element to yield. */
        int index;	
        /** Defensive programming; detect modifications while
         *  iterator in use. */
        int oldHead;
        int oldTail;

        IntBufferIterator() {
            index=head;
            oldHead=head;
            oldTail=tail;
        }

        public boolean hasNext() {
            ensureNoModifications();
            return index!=tail;
        }

		public Object next() throws NoSuchElementException {
			throw new UnsupportedOperationException();
		}

        public int nextInt() throws NoSuchElementException {
            ensureNoModifications();
            if (!hasNext()) 
                throw new NoSuchElementException();

            int ret=buf[index];
            index=increment(index);
            return ret;
        }

        private void ensureNoModifications() {
            if (oldHead!=head || oldTail!=tail)
                throw new ConcurrentModificationException();
        }
    }

    /** Returns a shallow copy of this, of type <tt>IntBuffer</tt> */
    public Object clone() {
        return new IntBuffer(this);        
    }

	// overrides Object.toString to return more information
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("[");
        boolean isFirst=true;
        for (IntBufferIterator iter=iterator(); iter.hasNext(); ) {
            if (! isFirst) 
                sb.append(", ");
            else
                isFirst=false;
            sb.append(iter.nextInt());            
        }
        sb.append("]");
        return sb.toString();
    }
}
