package com.srbenoit.sparsearray;

import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * An iterator that will iterate over a sparse array's elements.
 *
 * @param  <E>  the data type that the sparse array contains
 */
public class SparseArrayIterator<E> implements Iterator<E> {

    /** the source array being iterated */
    private final transient SparseArray<E> source;

    /** the index of the current element, -1 if finished or not yet started */
    private transient int currentElement;

    /** the index of the next element to be returned, -1 if finished */
    private transient int nextElement;

    /**
     * Constructs a new <code>SparseArrayIterator</code>.
     *
     * @param  sourceArray  the source array being iterated
     */
    public SparseArrayIterator(final SparseArray<E> sourceArray) {

        this.source = sourceArray;
        this.nextElement = sourceArray.nextFilled(0);
        this.currentElement = -1;
    }

    /**
     * Returns <code>true</code> if the iteration has more elements. In other words, returns <code>
     * true</code> if <code>next</code> would return an element rather than throwing an exception.
     *
     * @return  <code>true</code> if the iteration has more elements; <code>false</code> otherwise
     */
    public boolean hasNext() {

        return this.nextElement != -1;
    }

    /**
     * Returns the next element in the iteration.
     *
     * @return  the next element in the iteration
     * @throws  NoSuchElementException  if the iteration has no more elements
     */
    public E next() throws NoSuchElementException {

        E result;

        if (this.nextElement == -1) {
            throw new NoSuchElementException();
        }

        result = this.source.get(this.nextElement);
        this.currentElement = this.nextElement;
        this.nextElement = this.source.nextFilled(this.currentElement + 1);

        return result;
    }

    /**
     * Removes from the underlying collection the last element returned by the iterator (optional
     * operation). This method can be called only once per call to <code>next</code>.
     *
     * <p>The behavior of an iterator is unspecified if the underlying collection is modified while
     * the iteration is in progress in any way other than by calling this method.
     *
     * @throws  IllegalStateException  if the <code>next</code> method has not yet been called, or
     *                                 the <code>remove</code> method has already been called after
     *                                 the last call to the <code>next</code> method
     */
    public void remove() throws IllegalStateException {

        if (this.currentElement == -1) {
            throw new IllegalStateException();
        }

        this.source.remove(this.currentElement);
        this.currentElement = -1;
    }
}
