/*******************************************************************************
 * Copyright (c) 2010, 2015 Sonatype, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Stuart McCulloch (Sonatype, Inc.) - initial API and implementation
 *******************************************************************************/
package org.eclipse.sisu.wire;

import java.util.AbstractSequentialList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map.Entry;
import java.util.NoSuchElementException;

/**
 * {@link List} backed by an {@link Iterable} sequence of map entries.
 */
public final class EntryListAdapter<V>
    extends AbstractSequentialList<V>
{
    // ----------------------------------------------------------------------
    // Implementation fields
    // ----------------------------------------------------------------------

    private final Iterable<? extends Entry<?, V>> iterable;

    // ----------------------------------------------------------------------
    // Constructors
    // ----------------------------------------------------------------------

    public EntryListAdapter( final Iterable<? extends Entry<?, V>> iterable )
    {
        this.iterable = iterable;
    }

    // ----------------------------------------------------------------------
    // Public methods
    // ----------------------------------------------------------------------

    @Override
    public Iterator<V> iterator()
    {
        return new ValueIterator<V>( iterable );
    }

    @Override
    public ListIterator<V> listIterator( final int index )
    {
        return new ValueListIterator<V>( iterable, index );
    }

    @Override
    public boolean isEmpty()
    {
        return false == iterator().hasNext();
    }

    @Override
    public int size()
    {
        int size = 0;
        for ( final Iterator<?> i = iterable.iterator(); i.hasNext(); i.next() )
        {
            size++;
        }
        return size;
    }

    // ----------------------------------------------------------------------
    // Implementation types
    // ----------------------------------------------------------------------

    /**
     * Value {@link Iterator} backed by a Key:Value {@link Iterator}.
     */
    private static final class ValueIterator<V>
        implements Iterator<V>
    {
        // ----------------------------------------------------------------------
        // Implementation fields
        // ----------------------------------------------------------------------

        private final Iterator<? extends Entry<?, V>> iterator;

        // ----------------------------------------------------------------------
        // Constructors
        // ----------------------------------------------------------------------

        ValueIterator( final Iterable<? extends Entry<?, V>> iterable )
        {
            this.iterator = iterable.iterator();
        }

        // ----------------------------------------------------------------------
        // Public methods
        // ----------------------------------------------------------------------

        public boolean hasNext()
        {
            return iterator.hasNext();
        }

        public V next()
        {
            return iterator.next().getValue();
        }

        public void remove()
        {
            throw new UnsupportedOperationException();
        }
    }

    /**
     * Value {@link ListIterator} backed by a cached Key:Value {@link Iterator}.
     */
    private static final class ValueListIterator<V>
        implements ListIterator<V>
    {
        // ----------------------------------------------------------------------
        // Implementation fields
        // ----------------------------------------------------------------------

        private final Iterator<? extends Entry<?, V>> iterator;

        private final List<Entry<?, V>> entryCache = new ArrayList<Entry<?, V>>();

        private int index;

        // ----------------------------------------------------------------------
        // Constructors
        // ----------------------------------------------------------------------

        ValueListIterator( final Iterable<? extends Entry<?, V>> iterable, final int index )
        {
            if ( index < 0 )
            {
                throw new IndexOutOfBoundsException();
            }
            this.iterator = iterable.iterator();
            try
            {
                while ( this.index < index )
                {
                    next(); // position iterator at the index position
                }
            }
            catch ( final NoSuchElementException e )
            {
                throw new IndexOutOfBoundsException();
            }
        }

        // ----------------------------------------------------------------------
        // Public methods
        // ----------------------------------------------------------------------

        public boolean hasNext()
        {
            return index < entryCache.size() || iterator.hasNext();
        }

        public boolean hasPrevious()
        {
            return index > 0;
        }

        public V next()
        {
            if ( index >= entryCache.size() )
            {
                entryCache.add( iterator.next() );
            }
            return entryCache.get( index++ ).getValue();
        }

        public V previous()
        {
            if ( index <= 0 )
            {
                throw new NoSuchElementException();
            }
            return entryCache.get( --index ).getValue();
        }

        public int nextIndex()
        {
            return index;
        }

        public int previousIndex()
        {
            return index - 1;
        }

        public void add( final V o )
        {
            throw new UnsupportedOperationException();
        }

        public void set( final V o )
        {
            throw new UnsupportedOperationException();
        }

        public void remove()
        {
            throw new UnsupportedOperationException();
        }
    }
}
