/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.collection.iterators;

import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.IntFunction;

/**
 * Abstract class which can be used to implement spliterator for any collection which offers random access.
 * Class implements reasonable split strategy which avoids buffering elements (as default implementation does).
 *
 * Spliterators does not offer backward and forward iteration, nor modifications,
 * so there is only one abstract method. Because of that, preferred way of creating spliterator is to use static #of
 * method, which takes index range and lambda function.
 *
 * Please note, that spliterator assumes that size of collection won't change during spliterators' lifetime.
 *
 * @param <T> T
 */
public abstract class RandomAccessSpliterator<T> implements Spliterator<T> {

    private int index;

    private int end;

    /**
     * Creates spliterator for specified range
     * @param begin begin
     * @param end end
     */
    protected RandomAccessSpliterator(int begin, int end) {
        this.index = begin;
        this.end = end;
    }

    /**
     * Creates spliterator of specified size with values generated by "getter"
     *
     * @param size size
     * @param getter getter
     * @param <T> T
     * @return spliterator
     */
    public static <T> RandomAccessSpliterator<T> of(int size, IntFunction<T> getter) {
        return of(0, size, getter);
    }

    /**
     * Creates spliterator for specified range with values generated by "getter"
     *
     * @param begin begin
     * @param end end
     * @param getter getter
     * @param <T> T
     * @return spliterator
     */
    public static <T> RandomAccessSpliterator<T> of(int begin, int end, IntFunction<T> getter) {
        return new RandomAccessSpliterator<T>(begin, end) {
            @Override
            protected T get(int index) {
                return getter.apply(index);
            }
        };
    }

    /**
     * Must be implemented: returns elements at specified index (in constant time).
     * Spliterator validates index before calling this method.
     *
     * @param index index
     * @return T
     */
    protected abstract T get(int index);

    @Override
    public int characteristics() {
        return SIZED | SUBSIZED;
    }

    @Override
    public boolean tryAdvance(Consumer<? super T> action) {
        if(index < end) {
            action.accept(get(index++));
            return true;
        }
        return false;
    }

    @Override
    public Spliterator<T> trySplit() {
        int mid = (index + end) / 2;
        if(end==mid || index == mid) {
            return null;
        }
        return of(index, index = mid, this::get);
    }

    @Override
    public long estimateSize() {
        return end - index;
    }

    @Override
    public void forEachRemaining(Consumer<? super T> action) {
        try {
            for (int i = index; i < end; i++) {
                action.accept(get(i));
            }
        } finally {
            index = end;
        }
    }

}
