/**
 * 
 */
package com.github.phantomthief.util;

import java.util.Iterator;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * @author w.vela
 */
public class CursorIteratorEx<T, C, R> implements Iterable<T> {

    private final C initCursor;
    private final Function<C, R> dataRetriever;
    private final Function<R, C> cursorExtractor;
    private final Function<R, Iterator<T>> dataExtractor;
    private final Predicate<C> endChecker;

    /**
     * @param initCursor
     * @param dataRetriever
     * @param cursorExtractor
     * @param dataExtractor
     * @param endChecker
     */
    private CursorIteratorEx(C initCursor, Function<C, R> dataRetriever,
            Function<R, C> cursorExtractor, Function<R, Iterator<T>> dataExtractor,
            Predicate<C> endChecker) {
        this.initCursor = initCursor;
        this.dataRetriever = dataRetriever;
        this.cursorExtractor = cursorExtractor;
        this.dataExtractor = dataExtractor;
        this.endChecker = endChecker;
    }

    private final class RollingIterator implements Iterator<T> {

        private C currentCursor;
        private R currentData;
        private Iterator<T> currentIterator;

        public RollingIterator() {
            currentCursor = initCursor;
            if (endChecker.test(currentCursor)) {
                return;
            }
            currentData = dataRetriever.apply(currentCursor);
            if (currentData != null) {
                currentIterator = dataExtractor.apply(currentData);
                currentCursor = cursorExtractor.apply(currentData);
            }
        }

        /* (non-Javadoc)
         * @see java.util.Iterator#hasNext()
         */
        @Override
        public boolean hasNext() {
            if (currentIterator == null) {
                return false;
            }
            if (currentIterator.hasNext()) {
                return true;
            }
            roll();
            return currentIterator != null && currentIterator.hasNext();
        }

        private void roll() {
            if (endChecker.test(currentCursor)) {
                currentData = null;
                currentIterator = null;
                return;
            }
            currentData = dataRetriever.apply(currentCursor);
            if (currentData == null) {
                currentIterator = null;
            } else {
                currentCursor = cursorExtractor.apply(currentData);
                currentIterator = dataExtractor.apply(currentData);
            }
        }

        /* (non-Javadoc)
         * @see java.util.Iterator#next()
         */
        @Override
        public T next() {
            return currentIterator.next();
        }
    }

    /* (non-Javadoc)
     * @see java.lang.Iterable#iterator()
     */
    @Override
    public Iterator<T> iterator() {
        return new RollingIterator();
    }

    public Stream<T> stream() {
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator(),
                (Spliterator.NONNULL | Spliterator.IMMUTABLE)), false);
    }

    public static final class Builder<T, C, R> {

        private C initCursor;
        private Function<C, R> dataRetriever;
        private Function<R, C> cursorExtractor;
        private Function<R, Iterator<T>> dataExtractor;
        private Predicate<C> endChecker;

        public Builder<T, C, R> withInitCursor(C initCursor) {
            this.initCursor = initCursor;
            return this;
        }

        public Builder<T, C, R> withDataRetriever(Function<C, R> dataRetriever) {
            this.dataRetriever = dataRetriever;
            return this;
        }

        public Builder<T, C, R> withCursorExtractor(Function<R, C> cursorExtractor) {
            this.cursorExtractor = cursorExtractor;
            return this;
        }

        public Builder<T, C, R> withDataExtractor(Function<R, Iterator<T>> dataExtractor) {
            this.dataExtractor = dataExtractor;
            return this;
        }

        public Builder<T, C, R> withEndChecker(Predicate<C> endChecker) {
            this.endChecker = endChecker;
            return this;
        }

        public CursorIteratorEx<T, C, R> build() {
            ensure();
            return new CursorIteratorEx<>(initCursor, dataRetriever, cursorExtractor, dataExtractor,
                    endChecker);
        }

        private void ensure() {
            if (dataExtractor == null) {
                throw new NullPointerException("data extractor is null.");
            }
            if (dataRetriever == null) {
                throw new NullPointerException("data retriver is null.");
            }
            if (cursorExtractor == null) {
                throw new NullPointerException("data retriver is null.");
            }
            if (endChecker == null) {
                endChecker = Objects::isNull;
            }
        }

    }

    public static final <T, C, R> Builder<T, C, R> newBuilder() {
        return new Builder<>();
    }

}

@@ -422,24 +422,68 @@
 initCursor;%0A
+    private final boolean checkFirstCursor;%0A
     private 
@@ -514,24 +514,24 @@
 aRetriever;%0A
-
     private 
@@ -703,16 +703,47 @@
 tCursor%0A
+     * @param checkFirstCursor%0A
      * @
@@ -895,16 +895,42 @@
 tCursor,
+ boolean checkFirstCursor,
  Functio
@@ -1105,32 +1105,82 @@
 r = initCursor;%0A
+        this.checkFirstCursor = checkFirstCursor;%0A
         this.dat
@@ -1603,32 +1603,52 @@
             if (
+checkFirstCursor && 
 endChecker.test(
@@ -3512,32 +3512,74 @@
 e C initCursor;%0A
+        private boolean checkFirstCursor;%0A
         private 
@@ -3887,32 +3887,163 @@
 his;%0A        %7D%0A%0A
+        public Builder%3CT, C, R%3E checkFirstCursor() %7B%0A            this.checkFirstCursor = true;%0A            return this;%0A        %7D%0A%0A
         public B
@@ -4821,30 +4821,68 @@
 Cursor, 
-dataRetriever,
+checkFirstCursor, dataRetriever,%0A                   
  cursorE
@@ -4905,36 +4905,16 @@
 tractor,
-%0A                   
  endChec

add check first cursor switch.