///////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2001, Eric D. Friedman All Rights Reserved.
// Copyright (c) 2009, Rob Eden All Rights Reserved.
// Copyright (c) 2009, Jeff Randall All Rights Reserved.
//
// 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 General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
///////////////////////////////////////////////////////////////////////////////


package gnu.trove.list.linked;

import gnu.trove.list.TLinkable;
import gnu.trove.procedure.TObjectProcedure;

import java.io.*;
import java.util.AbstractSequentialList;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.lang.reflect.Array;



/**
 * <p>A LinkedList implementation which holds instances of type
 * <tt>TLinkable</tt>.
 * <p/>
 * Using this implementation allows you to get java.util.LinkedList
 * behavior (a doubly linked list, with Iterators that support insert
 * and delete operations) without incurring the overhead of creating
 * <tt>Node</tt> wrapper objects for every element in your list.
 * <p/>
 * The requirement to achieve this time/space gain is that the
 * Objects stored in the List implement the <tt>TLinkable</tt>
 * interface.
 * <p/>
 * The limitations are: <ul>
 * <li>the same object cannot be put into more than one list at the same time.
 * <li>the same object cannot be put into the same list more than once at the same time.
 * <li>objects must only be removed from list they are in.  That is,
 * if you have an object A and lists l1 and l2, you must ensure that
 * you invoke List.remove(A) on the correct list.
 * <li> It is also forbidden to invoke List.remove() with an unaffiliated
 * TLinkable (one that belongs to no list): this will destroy the list
 * you invoke it on.
 * </ul>
 *
 * @author Eric D. Friedman
 * @author Rob Eden
 * @author Jeff Randall
 * @version $Id: TLinkedList.java,v 1.1.2.3 2010/09/27 17:23:07 robeden Exp $
 * @see gnu.trove.list.TLinkable
 */


public class TLinkedList<T extends TLinkable<T>> extends AbstractSequentialList<T>
        implements Externalizable {

    static final long serialVersionUID = 1L;


    /** the head of the list */
    protected T _head;
    /** the tail of the list */
    protected T _tail;
    /** the number of elements in the list */
    protected int _size = 0;


    /** Creates a new <code>TLinkedList</code> instance. */
    public TLinkedList() {
        super();
    }


    /**
     * Returns an iterator positioned at <tt>index</tt>.  Assuming
     * that the list has a value at that index, calling next() will
     * retrieve and advance the iterator.  Assuming that there is a
     * value before <tt>index</tt> in the list, calling previous()
     * will retrieve it (the value at index - 1) and move the iterator
     * to that position.  So, iterating from front to back starts at
     * 0; iterating from back to front starts at <tt>size()</tt>.
     *
     * @param index an <code>int</code> value
     * @return a <code>ListIterator</code> value
     */
    public ListIterator<T> listIterator( int index ) {
        return new IteratorImpl( index );
    }


    /**
     * Returns the number of elements in the list.
     *
     * @return an <code>int</code> value
     */
    public int size() {
        return _size;
    }


    /**
     * Inserts <tt>linkable</tt> at index <tt>index</tt> in the list.
     * All values > index are shifted over one position to accommodate
     * the new addition.
     *
     * @param index    an <code>int</code> value
     * @param linkable an object of type TLinkable
     */
    public void add( int index, T linkable ) {
        if ( index < 0 || index > size() ) {
            throw new IndexOutOfBoundsException( "index:" + index );
        }
        insert( index, linkable );
    }


    /**
     * Appends <tt>linkable</tt> to the end of the list.
     *
     * @param linkable an object of type TLinkable
     * @return always true
     */
    public boolean add( T linkable ) {
        insert( _size, linkable );
        return true;
    }


    /**
     * Inserts <tt>linkable</tt> at the head of the list.
     *
     * @param linkable an object of type TLinkable
     */
    public void addFirst( T linkable ) {
        insert( 0, linkable );
    }


    /**
     * Adds <tt>linkable</tt> to the end of the list.
     *
     * @param linkable an object of type TLinkable
     */
    public void addLast( T linkable ) {
        insert( size(), linkable );
    }


    /** Empties the list. */
    public void clear() {
        if ( null != _head ) {
            for ( TLinkable<T> link = _head.getNext();
                  link != null;
                  link = link.getNext() ) {
                TLinkable<T> prev = link.getPrevious();
                prev.setNext( null );
                link.setPrevious( null );
            }
            _head = _tail = null;
        }
        _size = 0;
    }


    /**
     * Copies the list's contents into a native array.  This will be a
     * shallow copy: the Tlinkable instances in the Object[] array
     * have links to one another: changing those will put this list
     * into an unpredictable state.  Holding a reference to one
     * element in the list will prevent the others from being garbage
     * collected unless you clear the next/previous links.  <b>Caveat
     * programmer!</b>
     *
     * @return an <code>Object[]</code> value
     */
    public Object[] toArray() {
        Object[] o = new Object[_size];
        int i = 0;
        for ( TLinkable link = _head; link != null; link = link.getNext() ) {
            o[i++] = link;
        }
        return o;
    }


    /**
     * Copies the list to a native array, destroying the next/previous
     * links as the copy is made.  This list will be emptied after the
     * copy (as if clear() had been invoked).  The Object[] array
     * returned will contain TLinkables that do <b>not</b> hold
     * references to one another and so are less likely to be the
     * cause of memory leaks.
     *
     * @return an <code>Object[]</code> value
     */
    public Object[] toUnlinkedArray() {
        Object[] o = new Object[_size];
        int i = 0;
        for ( TLinkable<T> link = _head, tmp; link != null; i++ ) {
            o[i] = link;
            tmp = link;
            link = link.getNext();
            tmp.setNext( null ); // clear the links
            tmp.setPrevious( null );
        }
        _size = 0;              // clear the list
        _head = _tail = null;
        return o;
    }


    /**
     * Returns a typed array of the objects in the set.
     *
     * @param a an <code>Object[]</code> value
     * @return an <code>Object[]</code> value
     */
    @SuppressWarnings({"unchecked"})
    public T[] toUnlinkedArray( T[] a ) {
        int size = size();
        if ( a.length < size ) {
            a = (T[]) Array.newInstance( a.getClass().getComponentType(), size );
        }

        int i = 0;
        for ( T link = _head, tmp; link != null; i++ ) {
            a[i] = link;
            tmp = link;
            link = link.getNext();
            tmp.setNext( null ); // clear the links
            tmp.setPrevious( null );
        }
        _size = 0;              // clear the list
        _head = _tail = null;
        return a;
    }


    /**
     * A linear search for <tt>o</tt> in the list.
     *
     * @param o an <code>Object</code> value
     * @return a <code>boolean</code> value
     */
    public boolean contains( Object o ) {
        for ( TLinkable<T> link = _head; link != null; link = link.getNext() ) {
            if ( o.equals( link ) ) {
                return true;
            }
        }
        return false;
    }


    /** {@inheritDoc} */
    @Override
    @SuppressWarnings({"unchecked"})
    public T get( int index ) {
        // Blow out for bogus values
        if ( index < 0 || index >= _size ) {
            throw new IndexOutOfBoundsException( "Index: " + index + ", Size: " + _size );
        }

        // Determine if it's better to get there from the front or the back
        if ( index > ( _size >> 1 ) ) {
            int position = _size - 1;
            T node = _tail;

            while ( position > index ) {
                node = node.getPrevious();
                position--;
            }

            return node;
        } else {
            int position = 0;
            T node = _head;

            while ( position < index ) {
                node = node.getNext();
                position++;
            }

            return node;
        }
    }


    /**
     * Returns the head of the list
     *
     * @return an <code>Object</code> value
     */
    public T getFirst() {
        return _head;
    }


    /**
     * Returns the tail of the list.
     *
     * @return an <code>Object</code> value
     */
    public T getLast() {
        return _tail;
    }


    /**
     * Return the node following the given node. This method exists for two reasons:
     * <ol>
     * <li>It's really not recommended that the methods implemented by TLinkable be
     * called directly since they're used internally by this class.</li>
     * <li>This solves problems arising from generics when working with the linked
     * objects directly.</li>
     * </ol>
     * <p/>
     * NOTE: this should only be used with nodes contained in the list. The results are
     * undefined with anything else.
     *
     * @param current The current node
     * @return the node after the current node
     */
    @SuppressWarnings({"unchecked"})
    public T getNext( T current ) {
        return current.getNext();
    }


    /**
     * Return the node preceding the given node. This method exists for two reasons:
     * <ol>
     * <li>It's really not recommended that the methods implemented by TLinkable be
     * called directly since they're used internally by this class.</li>
     * <li>This solves problems arising from generics when working with the linked
     * objects directly.</li>
     * </ol>
     * <p/>
     * NOTE: this should only be used with nodes contained in the list. The results are
     * undefined with anything else.
     *
     * @param current The current node
     * @return the node after the current node
     */
    @SuppressWarnings({"unchecked"})
    public T getPrevious( T current ) {
        return current.getPrevious();
    }


    /**
     * Remove and return the first element in the list.
     *
     * @return an <code>Object</code> value
     */
    @SuppressWarnings({"unchecked"})
    public T removeFirst() {
        T o = _head;

        if ( o == null ) {
            return null;
        }

        T n = o.getNext();
        o.setNext( null );

        if ( null != n ) {
            n.setPrevious( null );
        }

        _head = n;
        if ( --_size == 0 ) {
            _tail = null;
        }
        return o;
    }


    /**
     * Remove and return the last element in the list.
     *
     * @return an <code>Object</code> value
     */
    @SuppressWarnings({"unchecked"})
    public T removeLast() {
        T o = _tail;

        if ( o == null ) {
            return null;
        }

        T prev = o.getPrevious();
        o.setPrevious( null );

        if ( null != prev ) {
            prev.setNext( null );
        }
        _tail = prev;
        if ( --_size == 0 ) {
            _head = null;
        }
        return o;
    }


    /**
     * Implementation of index-based list insertions.
     *
     * @param index    an <code>int</code> value
     * @param linkable an object of type TLinkable
     */
    @SuppressWarnings({"unchecked"})
    protected void insert( int index, T linkable ) {

        if ( _size == 0 ) {
            _head = _tail = linkable; // first insertion
        } else if ( index == 0 ) {
            linkable.setNext( _head ); // insert at front
            _head.setPrevious( linkable );
            _head = linkable;
        } else if ( index == _size ) { // insert at back
            _tail.setNext( linkable );
            linkable.setPrevious( _tail );
            _tail = linkable;
        } else {
            T node = get( index );

            T before = node.getPrevious();
            if ( before != null ) {
                before.setNext( linkable );
            }

            linkable.setPrevious( before );
            linkable.setNext( node );
            node.setPrevious( linkable );
        }
        _size++;
    }


    /**
     * Removes the specified element from the list.  Note that
     * it is the caller's responsibility to ensure that the
     * element does, in fact, belong to this list and not another
     * instance of TLinkedList.
     *
     * @param o a TLinkable element already inserted in this list.
     * @return true if the element was a TLinkable and removed
     */
    @SuppressWarnings({"unchecked"})
    public boolean remove( Object o ) {
        if ( o instanceof TLinkable ) {
            T p, n;
            TLinkable<T> link = (TLinkable<T>) o;

            p = link.getPrevious();
            n = link.getNext();

            if ( n == null && p == null ) { // emptying the list
                // It's possible this object is not something that's in the list. So,
                // make sure it's the head if it doesn't point to anything. This solves
                // problems caused by removing something multiple times.
                if ( o != _head ) {
                    return false;
                }

                _head = _tail = null;
            } else if ( n == null ) { // this is the tail
                // make previous the new tail
                link.setPrevious( null );
                p.setNext( null );
                _tail = p;
            } else if ( p == null ) { // this is the head
                // make next the new head
                link.setNext( null );
                n.setPrevious( null );
                _head = n;
            } else {            // somewhere in the middle
                p.setNext( n );
                n.setPrevious( p );
                link.setNext( null );
                link.setPrevious( null );
            }

            _size--;            // reduce size of list
            return true;
        } else {
            return false;
        }
    }


    /**
     * Inserts newElement into the list immediately before current.
     * All elements to the right of and including current are shifted
     * over.
     *
     * @param current    a <code>TLinkable</code> value currently in the list.
     * @param newElement a <code>TLinkable</code> value to be added to
     *                   the list.
     */
    public void addBefore( T current, T newElement ) {
        if ( current == _head ) {
            addFirst( newElement );
        } else if ( current == null ) {
            addLast( newElement );
        } else {
            T p = current.getPrevious();
            newElement.setNext( current );
            p.setNext( newElement );
            newElement.setPrevious( p );
            current.setPrevious( newElement );
            _size++;
        }
    }


    /**
     * Inserts newElement into the list immediately after current.
     * All elements to the left of and including current are shifted
     * over.
     *
     * @param current    a <code>TLinkable</code> value currently in the list.
     * @param newElement a <code>TLinkable</code> value to be added to
     *                   the list.
     */
    public void addAfter( T current, T newElement ) {
        if ( current == _tail ) {
            addLast( newElement );
        } else if ( current == null ) {
            addFirst( newElement );
        } else {
            T n = current.getNext();
            newElement.setPrevious( current );
            newElement.setNext( n );
            current.setNext( newElement );
            n.setPrevious( newElement );
            _size++;
        }
    }


    /**
     * Executes <tt>procedure</tt> for each entry in the list.
     *
     * @param procedure a <code>TObjectProcedure</code> value
     * @return false if the loop over the values terminated because
     *         the procedure returned false for some value.
     */
    @SuppressWarnings({"unchecked"})
    public boolean forEachValue( TObjectProcedure<T> procedure ) {
        T node = _head;
        while ( node != null ) {
            boolean keep_going = procedure.execute( node );
            if ( !keep_going ) {
                return false;
            }

            node = node.getNext();
        }

        return true;
    }


    public void writeExternal( ObjectOutput out ) throws IOException {
        // VERSION
        out.writeByte( 0 );

        // NUMBER OF ENTRIES
        out.writeInt( _size );

        // HEAD
        out.writeObject( _head );

        // TAIL
        out.writeObject( _tail );
    }


    @SuppressWarnings({"unchecked"})
    public void readExternal( ObjectInput in )
            throws IOException, ClassNotFoundException {

        // VERSION
        in.readByte();

        // NUMBER OF ENTRIED
        _size = in.readInt();

        // HEAD
        _head = (T) in.readObject();

        // TAIL
        _tail = (T) in.readObject();
    }


    /** A ListIterator that supports additions and deletions. */
    protected final class IteratorImpl implements ListIterator<T> {

        private int _nextIndex = 0;
        private T _next;
        private T _lastReturned;


        /**
         * Creates a new <code>Iterator</code> instance positioned at
         * <tt>index</tt>.
         *
         * @param position an <code>int</code> value
         */
        @SuppressWarnings({"unchecked"})
        IteratorImpl( int position ) {
            if ( position < 0 || position > _size ) {
                throw new IndexOutOfBoundsException();
            }

            _nextIndex = position;
            if ( position == 0 ) {
                _next = _head;
            } else if ( position == _size ) {
                _next = null;
            } else if ( position < ( _size >> 1 ) ) {
                int pos = 0;
                for ( _next = _head; pos < position; pos++ ) {
                    _next = _next.getNext();
                }
            } else {
                int pos = _size - 1;
                for ( _next = _tail; pos > position; pos-- ) {
                    _next = _next.getPrevious();
                }
            }
        }


        /**
         * Insert <tt>linkable</tt> at the current position of the iterator.
         * Calling next() after add() will return the added object.
         *
         * @param linkable an object of type TLinkable
         */
        public final void add( T linkable ) {
            _lastReturned = null;
            _nextIndex++;

            if ( _size == 0 ) {
                TLinkedList.this.add( linkable );
            } else {
                TLinkedList.this.addBefore( _next, linkable );
            }
        }


        /**
         * True if a call to next() will return an object.
         *
         * @return a <code>boolean</code> value
         */
        public final boolean hasNext() {
            return _nextIndex != _size;
        }


        /**
         * True if a call to previous() will return a value.
         *
         * @return a <code>boolean</code> value
         */
        public final boolean hasPrevious() {
            return _nextIndex != 0;
        }


        /**
         * Returns the value at the Iterator's index and advances the
         * iterator.
         *
         * @return an <code>Object</code> value
         * @throws NoSuchElementException if there is no next element
         */
        @SuppressWarnings({"unchecked"})
        public final T next() {
            if ( _nextIndex == _size ) {
                throw new NoSuchElementException();
            }

            _lastReturned = _next;
            _next = _next.getNext();
            _nextIndex++;
            return _lastReturned;
        }


        /**
         * returns the index of the next node in the list (the
         * one that would be returned by a call to next()).
         *
         * @return an <code>int</code> value
         */
        public final int nextIndex() {
            return _nextIndex;
        }


        /**
         * Returns the value before the Iterator's index and moves the
         * iterator back one index.
         *
         * @return an <code>Object</code> value
         * @throws NoSuchElementException if there is no previous element.
         */
        @SuppressWarnings({"unchecked"})
        public final T previous() {
            if ( _nextIndex == 0 ) {
                throw new NoSuchElementException();
            }

            if ( _nextIndex == _size ) {
                _lastReturned = _next = _tail;
            } else {
                _lastReturned = _next = _next.getPrevious();
            }

            _nextIndex--;
            return _lastReturned;
        }


        /**
         * Returns the previous element's index.
         *
         * @return an <code>int</code> value
         */
        public final int previousIndex() {
            return _nextIndex - 1;
        }


        /**
         * Removes the current element in the list and shrinks its
         * size accordingly.
         *
         * @throws IllegalStateException neither next nor previous
         *                               have been invoked, or remove or add have been invoked after
         *                               the last invocation of next or previous.
         */
        @SuppressWarnings({"unchecked"})
        public final void remove() {
            if ( _lastReturned == null ) {
                throw new IllegalStateException( "must invoke next or previous before invoking remove" );
            }

            if ( _lastReturned != _next ) {
                _nextIndex--;
            }
            _next = _lastReturned.getNext();
            TLinkedList.this.remove( _lastReturned );
            _lastReturned = null;
        }


        /**
         * Replaces the current element in the list with
         * <tt>linkable</tt>
         *
         * @param linkable an object of type TLinkable
         */
        public final void set( T linkable ) {
            if ( _lastReturned == null ) {
                throw new IllegalStateException();
            }

            swap( _lastReturned, linkable );
            _lastReturned = linkable;
        }


        /**
         * Replace from with to in the list.
         *
         * @param from a <code>TLinkable</code> value
         * @param to   a <code>TLinkable</code> value
         */
        private void swap( T from, T to ) {
	        T from_p = from.getPrevious();
	        T from_n = from.getNext();

	        T to_p = to.getPrevious();
	        T to_n = to.getNext();

	        // NOTE: 'to' cannot be null at this point
	        if ( from_n == to ) {
		        if ( from_p != null ) from_p.setNext( to );
		        to.setPrevious( from_p );
		        to.setNext( from );
		        from.setPrevious( to );
		        from.setNext( to_n );
		        if ( to_n != null ) to_n.setPrevious( from );
	        }
	        // NOTE: 'from' cannot be null at this point
	        else if ( to_n == from ) {
		        if ( to_p != null ) to_p.setNext( to );
		        to.setPrevious( from );
		        to.setNext( from_n );
		        from.setPrevious( to_p );
		        from.setNext( to );
		        if ( from_n != null ) from_n.setPrevious( to );
	        }
	        else {
				from.setNext( to_n );
				from.setPrevious( to_p );
				if ( to_p != null ) to_p.setNext( from );
				if ( to_n != null ) to_n.setPrevious( from );

				to.setNext( from_n );
				to.setPrevious( from_p );
				if ( from_p != null ) from_p.setNext( to );
				if ( from_n != null ) from_n.setPrevious( to );
	        }

	        if ( _head == from ) _head = to;
	        else if ( _head == to ) _head = from;

	        if ( _tail == from ) _tail = to;
	        else if ( _tail == to ) _tail = from;

	        if ( _lastReturned == from ) _lastReturned = to;
	        else if ( _lastReturned == to ) _lastReturned = from;

	        if ( _next == from ) _next = to;
	        else if ( _next == to ) _next = from;
        }
    }
} // TLinkedList
