/*
* Copyright (c) IBM Corporation 2017. All Rights Reserved.
* Project name: java-async-util
* This project is licensed under the Apache License 2.0, see LICENSE.
*/

package com.ibm.asyncutil.iteration;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;

import com.ibm.asyncutil.util.AsyncCloseable;
import com.ibm.asyncutil.util.Combinators;
import com.ibm.asyncutil.util.Either;
import com.ibm.asyncutil.util.StageSupport;

/**
 * A mechanism for asynchronously generating and consuming values
 *
 * <p>
 * Consider this an async version of {@link Stream}.
 *
 * <p>
 * AsyncIterators have lazy, pull based, evaluation semantics - values are not computed until they
 * are needed. AsyncIterators are not immutable - like streams, each value they produce is consumed
 * only once. Typically you should not apply multiple transformations to the same source
 * AsyncIterator, it almost certainly won't do what you want it to do.
 *
 * <p>
 * Implementors of the interface need only implement {@link #nextStage()}. Alternatively, the static
 * methods {@link #generate(Supplier)}, {@link #supply(Supplier)}, and
 * {@link #unfold(Object, Function)} can be used to create new AsyncIterators from functions that
 * generate iteration elements.
 *
 * <p>
 * A note on thread safety: This class makes no assumption that {@link #nextStage()} is thread safe!
 * Many methods that generate transformed iterators assume that nextStage will not be called
 * concurrently, and even stronger, that nextStage won't be called again until the previous stage
 * returned by nextStage has completed.
 *
 * <p>
 * Parallelization may still be accomplished using the <i>partially eager</i> methods described
 * below. The difference is that the parallelization in that case is from <b>producing</b> values in
 * parallel, <b>not consuming</b> values in parallel.
 *
 * <p>
 * To implement an AsyncIterator you must only implement the {@link #nextStage()} method- however,
 * it is recommended that users avoid actually using nextStage to consume the results of iteration.
 * It is less expressive and it can also be error prone; it is easy to cause a stack overflow by
 * incorrectly recursing on calls to nextStage. You should prefer to use the other higher level
 * methods on this interface.
 *
 * <p>
 * There are 2 main categories of such methods on this interface: Intermediate and Terminal. These
 * methods can be combined to form pipelines, which generally consist of a source (often created
 * with the static constructor methods on this interface ({@link #fromIterator(Iterator)},
 * {@link #unfold(Object, Function)}, etc)), followed by zero or more intermediate operations (such
 * as {@link #filter(Predicate)}, {@link #thenApply(Function)}), and completed with a terminal
 * operation which returns a {@link CompletionStage} (such as {@link #forEach(Consumer)} or
 * {@link #fold(Object, BinaryOperator)}). For example, suppose we wanted to accomplish the
 * following (blocking) procedure:
 *
 * <pre>
 * {@code
 * // request and lookup records one by one until we get 10 relevant records
 * List<Record> records = new ArrayList<>()
 * while (records.size() < 10) {
 *     // ask for a record identifier from a remote service (blocking)
 *     RecordId response = requestIdFromIdServer();
 *     // get the actual record from another service (blocking)
 *     Record record = getRecordFromRecordServer(recordIdentifier);
 *     // only add relevant records
 *     if (isRelevant(record)) {
 *        records.add(record);
 *     }
 * }
 * }
 * </pre>
 *
 * If we wanted to do it without doing any blocking, we can use a pipeline and return a
 * {@link CompletionStage} of the desired record list. Like the blocking version only one request
 * will be made at a time.
 *
 * <pre>
 * {@code
 * CompletionStage<RecordId> requestIdFromIdServer();
 * CompletionStage<Record> getRecordFromRecordServer(RecordId recordId);
 *
 * CompletionStage<List<Response>> responses =
 *   AsyncIterator.generate(this::requestIdFromIdServer) // source iterator
 *  .thenCompose(this::getRecordFromRecordServer)        // intermediate transformation
 *  .filter(record -> isRelevant(record))                // intermediate transformation
 *  .take(10)                                            // intermediate transformation
 *  .collect(Collectors.toList());                       // terminal operation
 *
 * }
 * </pre>
 *
 * <p>
 * <b>Intermediate methods</b> - All methods which return {@link AsyncIterator AsyncIterators} are
 * intermediate methods. They can further be broken down into lazy and partially eager methods.
 * Methods that end with the suffix <i>ahead</i> are partially eager, the rest are lazy. A lazy
 * intermediate transformation will not be evaluated until some downstream eager operation is
 * called. Furthermore, only what is needed to satisfy the eager operation will be evaluated from
 * the previous iterator in the chain. When only requesting a single element from the transformed
 * iterator, only a single element may be evaluated from the previous iterator (ex:
 * {@link #thenApply(Function)}), or potentially many elements (ex: {@link #filter(Predicate)}).
 *
 * <p>
 * Methods ending with the suffix <i> ahead </i>, are partially eager. They can be used when there
 * is an expensive transformation step that should be performed in parallel. They will eagerly
 * consume from their upstream iterator up to a specified amount (still sequentially!) and eagerly
 * apply the transformation step.
 *
 * <p>
 * Intermediate methods will propagate exceptions similarly to {@link CompletionStage}, a dependent
 * AsyncIterator will return exceptional stages if the upstream iterator generated exceptional
 * elements.
 *
 * <p>
 * <b>Terminal methods</b> - Terminal methods consume the iterator and return a
 * {@link CompletionStage}. After a terminal operation is called, the iterator is considered
 * consumed and should not be used further. If any of the stages in the chain that comprise
 * {@code this} iterator were exceptional, the {@link CompletionStage} returned by a terminal
 * operation will also be exceptional. The exception will short-circuit the terminal operation. For
 * example, a terminal operation such as {@link #forEach(Consumer)} will not to continue to run on
 * subsequent elements of the iterator and instead immediately complete its returned stage with the
 * error. Unless otherwise noted, this behavior holds for all terminal methods but may not
 * documented explicitly.
 *
 * <p>
 * The exception propagation scheme should be familiar to users of {@link CompletionStage}, upstream
 * errors will appear wherever the AsyncIterator is consumed and the result is observed (with
 * {@link CompletableFuture#join()} for instance). Exceptions at any stage in the pipeline can be
 * recovered from by using {@link #exceptionally(Function)}, however this won't recover exceptions
 * that are produced downstream. A daring user may have applications where they wish to manually
 * iterate past exceptions without converting them. This can be accomplished by using
 * {@link #nextStage()} directly, see the docs there for more details.
 *
 * <p>
 * Unless otherwise noted, methods on this interface are free to throw {@link NullPointerException}
 * if any of the provided arguments are {@code null}.
 *
 * <p>
 * The behavior of an AsyncIterator if {@link #nextStage()} is called after the end of iteration
 * marker is returned is left to the implementation. You may ensure that all subsequent calls always
 * return the end marker by using {@link #fuse()}.
 *
 * <p>
 * This interface extends {@link AsyncCloseable}, if there are resources associated with {@code
 * this} iterator that must be relinquished after iteration is complete, the {@link #close()} method
 * should be implemented. Because the majority of methods do not have a manually managed resource, a
 * default implementation of close which does nothing is provided. Terminal methods on this
 * interface do not call {@link #close()}, it is generally the user's responsibility. The exception
 * to this rule are for methods that take AsyncIterators or take functions that produce
 * AsyncIterators, such as {@link #concat(AsyncIterator)} or {@link #thenFlatten(Function)}, in that
 * case intermediate iterators will be automatically closed - any such cases will be documented on
 * the specific method. Similar to the situation with {@link Stream#close()}, because the common
 * case requires no resources the user should only call close if it is possible that the
 * {@link AsyncIterator} has resources.
 *
 * @author Ravi Khadiwala
 * @author Renar Narubin
 * @param <T> Type of object being iterated over.
 * @see Stream
 */
public interface AsyncIterator<T> extends AsyncCloseable {

  /** A marker enum that indicates there are no elements left in the iterator. */
  enum End {
    END;

    private static final Either<End, ?> ITERATION_END = Either.left(End.END);

    private static final CompletionStage<? extends Either<AsyncIterator.End, ?>> END_FUTURE =
        StageSupport.completedStage(ITERATION_END);

    /**
     * An {@link Either} instance which contains the {@link End} enum.
     * 
     * @return An {@link Either} containing the {@link End} instance
     */
    @SuppressWarnings("unchecked")
    public static <T> Either<AsyncIterator.End, T> end() {
      return (Either<AsyncIterator.End, T>) ITERATION_END;
    }


    /**
     * A {@link CompletionStage} which is already complete, and contains the {@link End#end()}
     * instance as its value.
     * 
     * @return A completed stage whose value is an {@link Either} containing the {@link End}
     *         instance
     */
    @SuppressWarnings("unchecked")
    public static <T> CompletionStage<Either<AsyncIterator.End, T>> endStage() {
      return (CompletionStage<Either<AsyncIterator.End, T>>) END_FUTURE;
    }

    @Override
    public String toString() {
      return "End of iteration";
    }
  }

  /**
   * Returns a stage that will be completed with the next element of {@code this} iterator when it
   * becomes available, or {@link End} if there are no more elements.
   *
   * <p>
   * This is not a terminal method, it can be safely called multiple times. However, this method is
   * <b>not thread safe</b>, and should only be called in a single-threaded fashion. Moreover,
   * sequential calls should not be made until the {@link CompletionStage} returned by the previous
   * call has completed. That is to say,
   *
   * <pre>
   * {@code
   * // illegal
   * pool.execute(() -> nextStage())
   * pool.execute(() -> nextStage())
   *
   * // just as illegal
   * f1 = nextStage();
   * f2 = nextStage();
   *
   * // good
   * nextStage().thenCompose(t -> nextStage());
   * }
   * </pre>
   *
   * Though this is not a terminal method, if a terminal method has been called it is no longer safe
   * to call this method. When nextStage returns {@link End}, the iterator has no more elements.
   * After an iterator emits an {@link End} indicator, the result of subsequent calls to nextStage
   * is undefined.
   *
   * <p>
   * An AsyncIterator may be capable of producing normally completing stages after having producing
   * exceptionally completed stages. nextStage is unique in that it can safely continue to be called
   * even after a returned stage completes exceptionally, whereas all terminal operations short
   * circuit when encountering an exception. If a user wishes to continue iteration after exception,
   * they must use nextStage directly, or install exception recovery with
   * {@link #exceptionally(Function)}.
   *
   * @return A {@link CompletionStage} of the next element for iteration held in the
   *         {@link Either#right()} position, or an instance of {@link End} held in the
   *         {@link Either#left()} position indicating the end of iteration.
   */
  CompletionStage<Either<End, T>> nextStage();

  /**
   * Relinquishes any resources associated with this iterator.
   *
   * <p>
   * This method should be overridden if manual resource management is required, the default
   * implementation does nothing. This method is <b>not</b> thread safe, and must not be called
   * concurrently with calls to {@link #nextStage()}. This method is not automatically called by
   * terminal methods, and must be explicitly called after iteration is complete if the underlying
   * iterator has resources to release. Similar to the situation with {@link Stream#close()},
   * because the common case requires no resources the user should only call close if it is possible
   * that the {@link AsyncIterator} has resources. Special care needs to be taken to call close even
   * in the case of an exception, {@link StageSupport#tryComposeWith(AsyncCloseable, Function)} can
   * make this more convenient.
   *
   * <pre>
   * {@code
   * class SocketBackedIterator implements AsyncIterator<byte[]> {
   *  ...
   *  {@literal @Override}
   *  CompletionStage<Void> close() { return socket.close(); }
   * }
   * AsyncCloseable.tryComposeWith(new SocketBackedIterator(socket), socketIt -> socketIt
   *  .thenCompose(this::deserialize)
   *  .filter(this::isRelevantMessage)
   *  .forEach(message -> System.out.println(message)));
   * }
   * </pre>
   *
   * Intermediate methods will pass calls to close to their upstream iterators, so it is safe to
   * call close on an intermediate result of an iterator instead of on it directly. For example,
   *
   * <pre>
   * {@code
   * AsyncIterator<byte[]> original = new SocketBackedIterator(socket);
   * AsyncIterator<Message> transformed = original.thenCompose(this::deserialize).filter(this::isRelevantMessage);
   *
   * transformed.close() // will close on original
   * }
   * </pre>
   *
   * @return a {@link CompletionStage} that completes when all resources associated with this
   *         iterator have been relinquished.
   */
  @Override
  default CompletionStage<Void> close() {
    return StageSupport.voidStage();
  }

  /**
   * Transforms {@code this} into a new AsyncIterator that iterates over the results of {@code fn}
   * applied to the outcomes of stages in this iterator when they complete normally. When stages in
   * {@code this} iterator complete exceptionally the returned iterator will emit an exceptional
   * stage without applying {@code fn}.
   *
   * <pre>
   * {@code
   * intIterator // 1,2,3,...
   *     .thenApply(Integer::toString) //"1","2","3"...
   * }
   * </pre>
   *
   * This is a lazy <i> intermediate </i> method.
   *
   * @param fn A function which produces a U from the given T
   * @return A new AsyncIterator which produces stages of fn applied to the result of the stages
   *         from {@code this} iterator
   */
  default <U> AsyncIterator<U> thenApply(final Function<? super T, ? extends U> fn) {
    return AsyncIterators.thenApplyImpl(this, fn, true, null);
  }

  /**
   * Transforms {@code this} into a new AsyncIterator that iterates over the results of {@code fn}
   * applied to the outcomes of stages in this iterator when they complete normally. When stages in
   * {@code this} iterator complete exceptionally the returned iterator will emit an exceptional
   * stage without applying {@code fn}. {@code fn} is executed with the previous stage's default
   * asynchronous execution facility.
   *
   * <pre>
   * {@code
   * intIterator // 1,2,3,...
   *     .thenApplyAsync(Integer::toString) //"1","2","3"...
   * }
   * </pre>
   *
   * This is a lazy <i> intermediate </i> method.
   *
   * @param fn A function which produces a U from the given T
   * @return A new AsyncIterator which produces stages of fn applied to the result of the stages
   *         from {@code this} iterator
   */
  default <U> AsyncIterator<U> thenApplyAsync(final Function<? super T, ? extends U> fn) {
    return AsyncIterators.thenApplyImpl(this, fn, false, null);
  }

  /**
   * Transforms {@code this} into a new AsyncIterator that iterates over the results of {@code fn}
   * applied to the outcomes of stages in this iterator when they complete normally. When stages in
   * {@code this} iterator complete exceptionally the returned iterator will emit an exceptional
   * stage without applying {@code fn}. {@code fn} is executed with the provided Executor.
   *
   * <pre>
   * {@code
   * intIterator // 1,2,3,...
   *     .thenApplyAsync(Integer::toString, executor) //"1","2","3"...
   * }
   * </pre>
   *
   * This is a lazy <i> intermediate </i> method.
   *
   * @param fn A function which produces a U from the given T
   * @param executor a {@link Executor} where the function {@code fn} should run
   * @return A new AsyncIterator which produces stages of fn applied to the result of the stages
   *         from {@code this} iterator
   */
  default <U> AsyncIterator<U> thenApplyAsync(
      final Function<? super T, ? extends U> fn, final Executor executor) {
    Objects.requireNonNull(executor);
    return AsyncIterators.thenApplyImpl(this, fn, false, executor);
  }

  /**
   * Transforms {@code this} into a new AsyncIterator using the produced stages of {@code fn}
   * applied to the output from the stages of {@code this}. When stages in {@code this} iterator
   * complete exceptionally the returned iterator will emit an exceptional stage without applying
   * {@code fn}.
   *
   * <pre>
   * {@code
   * CompletableFuture<String> asyncToString(final int i);
   * intIterator // 1, 2, 3
   *   .thenCompose(this::asyncToString); //"1", "2", "3"...
   * }
   * </pre>
   *
   * This is a lazy <i> intermediate </i> method.
   *
   * @param fn A function which produces a new {@link CompletionStage} from a T
   * @return A new AsyncIterator which produces stages of fn composed with the result of the stages
   *         from {@code this} iterator
   */
  default <U> AsyncIterator<U> thenCompose(
      final Function<? super T, ? extends CompletionStage<U>> fn) {
    return AsyncIterators.thenComposeImpl(this, fn, true, null);
  }

  /**
   * Transforms {@code this} into a new AsyncIterator using the produced stages of {@code fn}
   * applied to the output from the stages of {@code this}. When stages in {@code this} iterator
   * complete exceptionally the returned iterator will emit an exceptional stage without applying
   * {@code fn}. {@code fn} will be run on the default asynchronous execution facility of the stages
   * of {@code this}.
   *
   * <pre>
   * {@code
   * CompletableFuture<String> asyncToString(final int i);
   * intIterator // 1, 2, 3
   *   .thenComposeAsync(this::asyncToString); //"1", "2", "3"...
   * }
   * </pre>
   *
   * This is a lazy <i> intermediate </i> method.
   *
   * @param fn A function which produces a new {@link CompletionStage} from a T
   * @return A new AsyncIterator which produces stages of fn composed with the result of the stages
   *         from {@code this} iterator
   */
  default <U> AsyncIterator<U> thenComposeAsync(
      final Function<? super T, ? extends CompletionStage<U>> fn) {
    return AsyncIterators.thenComposeImpl(this, fn, false, null);
  }

  /**
   * Transforms {@code this} into a new AsyncIterator using the produced stages of {@code fn}
   * applied to the output from the stages of {@code this}. When stages in {@code this} iterator
   * complete exceptionally the returned iterator will emit an exceptional stage without applying
   * {@code fn}. {@code fn} will be run on the supplied executor.
   *
   * <pre>
   * {@code
   * CompletableFuture<String> asyncToString(final int i);
   * intIterator // 1, 2, 3
   *   .thenComposeAsync(this::asyncToString, executor); //"1", "2", "3"...
   * }
   * </pre>
   *
   * This is a lazy <i> intermediate </i> method.
   *
   * @param fn A function which produces a new {@link CompletionStage} from a T
   * @param executor a {@link Executor} where the function {@code fn} should run
   * @return A new AsyncIterator which produces stages of fn composed with the result of the stages
   *         from {@code this} iterator
   */
  default <U> AsyncIterator<U> thenComposeAsync(
      final Function<? super T, ? extends CompletionStage<U>> fn, final Executor executor) {
    Objects.requireNonNull(executor);
    return AsyncIterators.thenComposeImpl(this, fn, false, executor);
  }

  /**
   * Composes fn with the stages of {@code this} iterator to produce new AsyncIterators, and
   * flattens the resulting iterator of iterators.
   *
   * <p>
   * Suppose we were making requests for locations with an x coordinate and a y coordinate.
   *
   * <pre>
   * {@code
   * CompletableFuture<Z> lookupCoord(int x, int y);
   * AsyncIterator<Z> zs = AsyncIterator.range(0, xmax)
   *  .thenFlatten(x -> AsyncIterator.range(0, ymax)
   *    .thenCompose(y -> lookupCoord(x, y)));
   *
   * // would print z result for (0, 0), (0, 1), (0, 2) ....
   * zs.forEach(z ->  print(z)).toCompletableFuture().join();
   * }
   * </pre>
   *
   * <p>
   * Once all elements from an AsyncIterator produced by {@code fn} have been consumed,
   * {@link #close()} is called on that iterator. If {@link #close()} produces an exception, an
   * exceptional stage will be produced in the returned iterator. If {@link #close()} exceptions
   * should be ignored, they should either be squashed in the iterators produced by {@code fn}, or
   * the consumer may use manual {@link #nextStage()} iteration to continue past exceptions on the
   * returned iterator. It is still necessary to {@link #close()} the returned iterator, as the last
   * AsyncIterator produced by {@code fn} may have only been partially consumed and would not be
   * closed.
   *
   * <p>
   * This is a lazy <i> intermediate </i> method.
   *
   * @param fn A function which produces a new AsyncIterator
   * @return A new AsyncIterator consisting of flattened iterators from applying {@code fn} to
   *         elements of {@code this}
   */
  default <U> AsyncIterator<U> thenFlatten(
      final Function<? super T, ? extends AsyncIterator<U>> fn) {
    return AsyncIterator.concat(this.thenApply(fn));
  }

  /**
   * Applies a transformation and flattening to {@code this} iterator with parallelism. This method
   * will consume results from {@code this} sequentially, but will apply the mapping function {@code
   * fn} in parallel. The resulting iterator will retain the order of {@code this}. Up to {@code
   * executeAhead} asynchronous operations past what the consumer of the new iterator has already
   * consumed can be started in parallel.
   *
   * <p>
   * Once all elements from an AsyncIterator produced by {@code fn} have been consumed,
   * {@link #close()} is called on that iterator. If {@link #close()} produces an exception, an
   * exceptional stage will be produced in the handled iterator. If {@link #close()} exceptions
   * should be ignored, they should either be squashed in the input iterators or the consumer may
   * use manual {@link #nextStage()} iteration to continue past exceptions. It is still necessary to
   * {@link #close()} the returned iterator; this will close {@code this} iterator as well as the up
   * to {@code executeAhead} iterators that have been eagerly produced by {@code fn}. The
   * {@link CompletionStage} returned from calling {@link #close()} on the returned iterator will
   * not complete until the close completes on all eagerly produced iterators.
   *
   * <p>
   * This is a partially eager <i> intermediate </i> method.
   *
   * @param fn A function which produces a {@link CompletionStage} which will complete with an
   *        AsyncIterator
   * @param executeAhead An integer indicating the number of allowable calls to {@code fn} that can
   *        be made ahead of what the user has already consumed from the returned iterator
   * @return A new AsyncIterator consisting of flattened iterators from applying {@code fn} to
   *         elements of {@code this}
   * @see #thenFlatten(Function)
   */
  default <U> AsyncIterator<U> thenFlattenAhead(
      final Function<? super T, ? extends CompletionStage<? extends AsyncIterator<U>>> fn,
      final int executeAhead) {
    Objects.requireNonNull(fn);
    final Function<Either<End, T>, CompletionStage<Either<End, AsyncIterator<U>>>> eitherF =
        nt -> nt.fold(
            stop -> End.endStage(),
            t -> fn.apply(t).thenApply(Either::right));

    final AsyncIterator<AsyncIterator<U>> nestedAsyncIterator =
        new AsyncIterators.PartiallyEagerAsyncIterator<>(
            this,
            executeAhead,
            eitherF,
            // close any unused eagerly evaluated results when we're finished
            AsyncIterator::close);
    return AsyncIterator.concat(nestedAsyncIterator);
  }

  /**
   * Applies a transformation to {@code this} iterator with parallelism. This method will consume
   * results from {@code this} sequentially, but will apply the mapping function {@code fn} in
   * parallel. The resulting iterator will retain the order of {@code this}. Up to {@code
   * executeAhead} asynchronous operations past what the consumer of the new iterator has already
   * consumed can be started in parallel.
   *
   * <p>
   * This is a partially eager <i> intermediate </i> method.
   *
   * @param fn A function which produces a new CompletionStage
   * @param executeAhead An integer indicating the number of allowable calls to fn that can be made
   *        ahead of the user has already consumed
   * @return A transformed AsyncIterator
   * @see #thenCompose(Function)
   */
  default <U> AsyncIterator<U> thenComposeAhead(
      final Function<? super T, ? extends CompletionStage<U>> fn, final int executeAhead) {
    Objects.requireNonNull(fn);

    // apply user function and wrap future result in a Either
    final Function<Either<End, T>, CompletionStage<Either<End, U>>> eitherF =
        nt -> nt.fold(stop -> End.endStage(),
            t -> fn.apply(t).thenApply(Either::right));

    return new AsyncIterators.PartiallyEagerAsyncIterator<>(this, executeAhead, eitherF, null);
  }

  /**
   * Transforms the AsyncIterator into one which will only produce results that match {@code
   * predicate}.
   *
   * <p>
   * This is a lazy <i> intermediate </i> method.
   *
   * @param predicate A function that takes a T and returns true if it should be returned by the new
   *        iterator, and false otherwise
   * @return a new AsyncIterator which will only return results that match predicate
   */
  default AsyncIterator<T> filter(final Predicate<? super T> predicate) {

    // keep looping looking for a value that satisfies predicate as long as the current value
    // doesn't, and we're not out of elements
    final Predicate<Either<End, T>> shouldKeepLooking =
        either -> either.fold(end -> false, predicate.negate()::test);

    return new AsyncIterator<T>() {
      @Override
      public CompletionStage<Either<End, T>> nextStage() {
        return AsyncIterator.this
            .nextStage()
            .thenCompose(
                t -> AsyncTrampoline.asyncWhile(
                    shouldKeepLooking,
                    c -> AsyncIterator.this.nextStage(),
                    t));
      }

      @Override
      public CompletionStage<Void> close() {
        return AsyncIterator.this.close();
      }
    };
  }

  /**
   * Applies a transformation and filters this AsyncIterator at the same time. Since
   * {@link Optional} cannot hold null values, this method cannot be used to map to an iterator of
   * possibly null types.
   *
   * <p>
   * This is a lazy <i> intermediate </i> method.
   *
   * @param fn a conditional transformation from {@code T} to {@code U}. If fn produces empty, this
   *        result will not be included in the new iterator
   * @return An AsyncIterator of all the {@code U}s that were present
   */
  default <U> AsyncIterator<U> filterApply(final Function<? super T, Optional<U>> fn) {
    return this.thenApply(fn).filter(Optional::isPresent).thenApply(Optional::get);
  }

  /**
   * Composes and filters an AsyncIterator at the same time. Since {@link Optional} cannot hold null
   * values, this method cannot be used to map to an iterator of possibly null types.
   *
   * <p>
   * This is a lazy <i> intermediate </i> method.
   *
   * @param fn an asynchronous conditional transformation from T to U. If fn produces empty, this
   *        result will not be included in the new iterator
   * @return An AsyncIterator of all the {@code U}s that were present
   */
  default <U> AsyncIterator<U> filterCompose(
      final Function<? super T, ? extends CompletionStage<Optional<U>>> fn) {
    return this.thenCompose(fn).filter(Optional::isPresent).thenApply(Optional::get);
  }

  /**
   * Returns an AsyncIterator that will return only the first n elements of {@code this}
   * AsyncIterator.
   *
   * <p>
   * This is a lazy <i> intermediate </i> method.
   *
   * @param n the maximum number of elements to take from this iterator
   * @return an AsyncIterator which will return {@code n} elements or less.
   */
  default AsyncIterator<T> take(final long n) {
    return new AsyncIterator<T>() {
      int count = 0;

      @Override
      public CompletionStage<Either<End, T>> nextStage() {
        if (++this.count > n) {
          return End.endStage();
        } else {
          return AsyncIterator.this.nextStage();
        }
      }

      @Override
      public CompletionStage<Void> close() {
        return AsyncIterator.this.close();
      }
    };
  }

  /**
   * Returns an AsyncIterator that returns elements from the backing iterator until coming across an
   * element that does not satisfy the predicate.
   *
   * <p>
   * This is a lazy <i> intermediate </i> method.
   *
   * @param predicate a predicate which returns {@code true} if we can continue returning values
   *        from the iterator, and {@code false otherwise}
   * @return A new AsyncIterator that will return T's until the predicate fails
   */
  default AsyncIterator<T> takeWhile(final Predicate<? super T> predicate) {
    return new AsyncIterator<T>() {
      boolean predicateFailed = false;

      @Override
      public CompletionStage<Either<End, T>> nextStage() {
        return AsyncIterator.this
            .nextStage()
            .thenApply(
                either -> either.flatMap(
                    t -> {
                      if (this.predicateFailed) {
                        return End.end();
                      } else if (!predicate.test(t)) {
                        this.predicateFailed = true;
                        return End.end();
                      } else {
                        return Either.right(t);
                      }
                    }));
      }

      @Override
      public CompletionStage<Void> close() {
        return AsyncIterator.this.close();
      }
    };
  }

  /**
   * Returns an AsyncIterator where any exception produced by {@code this} iterator will be
   * transformed with the provided function.
   *
   * <p>
   * This is a lazy <i> intermediate </i> method.
   *
   * @param fn the Function used to convert an error from this iterator into a T. If {@code fn}
   *        itself throws an exception, that exception will be emitted in the resulting iterator.
   * @return a new AsyncIterator where exceptions from this iterator have been converted using
   *         {@code fn}
   */
  default AsyncIterator<T> exceptionally(final Function<Throwable, ? extends T> fn) {
    return new AsyncIterator<T>() {
      @Override
      public CompletionStage<Either<End, T>> nextStage() {
        return AsyncIterators.convertSynchronousException(AsyncIterator.this::nextStage)
            .exceptionally(ex -> Either.right(fn.apply(ex)));
      }

      @Override
      public CompletionStage<Void> close() {
        return AsyncIterator.this.close();
      }
    };
  }

  /**
   * Fuses the iterator to the {@link End} result after iteration has stopped. It is normally
   * undefined behavior to call {@link #nextStage()} after {@link End} has already been returned. On
   * a fused iterator, nextStage will just continue to return End.
   *
   * <p>
   * This is a lazy <i> intermediate </i> method.
   *
   * @return An iterator where it is safe to call {@link #nextStage()} after {@link End} has already
   *         been returned
   */
  default AsyncIterator<T> fuse() {
    return new AsyncIterator<T>() {
      boolean end = false;

      @Override
      public CompletionStage<Either<End, T>> nextStage() {
        if (this.end) {
          return End.endStage();
        }
        return AsyncIterator.this
            .nextStage()
            .thenApply(
                either -> {
                  either.forEach(endMarker -> this.end = true, t -> {
                  });
                  return either;
                });
      }

      @Override
      public CompletionStage<Void> close() {
        return AsyncIterator.this.close();
      }
    };
  }

  /**
   * Collects the results of this iterator in batches, returning an iterator of those batched
   * collections.
   *
   * <p>
   * This may be useful for performing bulk operations on many elements, rather than on one element
   * at a time.
   *
   * <p>
   * This is a lazy <i> intermediate </i> method.
   *
   * @param collector a {@link Collector} used to collect the elements of this iterator into
   *        individual batches. Each batch will be created by invoking the collector's
   *        {@link Collector#supplier()} method
   * @param shouldAddToBatch a predicate which determines whether a given element encountered during
   *        iteration should be added to the given (current) batch. If this predicate returns true
   *        for the given element and container, the element will be {@link Collector#accumulator()
   *        added} to the container, and the batching operation will continue to draw from the
   *        underlying iterator. If this predicate returns false, the element will not be added and
   *        the current batch will be {@link Collector#finisher() finished} and returned by the
   *        batching iterator. The element which did not meet the predicate will be tested again by
   *        the next batch
   * @return an AsyncIterator which invokes several iterations of the underlying iterator with each
   *         advance, collecting these elements into containers provided by the given
   *         {@link Collector}.
   */
  default <A, R> AsyncIterator<R> batch(
      final Collector<? super T, A, R> collector,
      final BiPredicate<? super A, ? super T> shouldAddToBatch) {
    return new AsyncIterator<R>() {
      /**
       * This field holds the result of the latest call to the underlying iterator's 'nextStage'; At
       * the start of the batching iterator's 'nextStage' method, this holds the value which was
       * rejected by the last 'addToBatch' call (or empty if the iterator terminated, or null if
       * this is the first call). If non-End, this rejected value should be tested again in the next
       * batch. If End, iteration should terminate
       */
      private Either<End, T> lastAdvance = null;

      @Override
      public CompletionStage<Either<End, R>> nextStage() {
        // the first call has no preceding value to start the batch, so draw from iter
        return this.lastAdvance == null
            ? AsyncIterator.this
                .nextStage()
                .thenCompose(
                    eitherT -> {
                      this.lastAdvance = eitherT;
                      return collectBatch();
                    })
            : collectBatch();
      }

      @Override
      public CompletionStage<Void> close() {
        return AsyncIterator.this.close();
      }

      private CompletionStage<Either<End, R>> collectBatch() {
        return this.lastAdvance.fold(
            end -> End.endStage(),
            ignoredT -> {
              final A batch = collector.supplier().get();

              return AsyncTrampoline.asyncWhile(
                  eitherT -> eitherT.fold(end -> false, t -> shouldAddToBatch.test(batch, t)),
                  eitherT -> {
                    collector
                        .accumulator()
                        .accept(batch, eitherT.fold(
                            end -> {
                              throw new IllegalStateException();
                            },
                            t -> t));
                    return AsyncIterator.this.nextStage();
                  },
                  this.lastAdvance)
                  .thenApply(
                      eitherT -> {
                        this.lastAdvance = eitherT;
                        return Either.right(AsyncIterators.finishContainer(batch, collector));
                      });
            });
      }
    };
  }

  /**
   * A convenience method provided to invoke {@link #batch(Collector, BiPredicate)} with a predicate
   * that limits batches to a fixed size.
   *
   * <p>
   * Each batch will be as large as the given {@code batchSize} except possibly the last one, which
   * may be smaller due to exhausting the underlying iterator.
   *
   * <p>
   * This is a lazy <i> intermediate </i> method.
   *
   * @see #batch(Collector, BiPredicate)
   */
  default <A, R> AsyncIterator<R> batch(
      final Collector<? super T, A, R> collector, final int batchSize) {
    class CountingContainer {
      final A container;
      int size;

      public CountingContainer(final A container, final int size) {
        this.container = container;
        this.size = size;
      }
    }

    class CountingCollector
        implements Collector<T, CountingContainer, R>,
        Supplier<CountingContainer>,
        BiConsumer<CountingContainer, T>,
        BinaryOperator<CountingContainer>,
        BiPredicate<CountingContainer, T> {
      private final Supplier<A> parentSupplier = collector.supplier();
      private final BiConsumer<A, ? super T> parentAccumulator = collector.accumulator();
      private final BinaryOperator<A> parentCombiner = collector.combiner();
      private final Set<Collector.Characteristics> characteristics;

      CountingCollector() {
        final Set<Collector.Characteristics> characteristics =
            EnumSet.copyOf(collector.characteristics());
        // remove concurrent (if present) because the increments aren't thread safe
        characteristics.remove(Characteristics.CONCURRENT);

        // remove identity (if present) because the finisher is necessary to unbox the container
        characteristics.remove(Characteristics.IDENTITY_FINISH);
        this.characteristics = Collections.unmodifiableSet(characteristics);
      }

      @Override
      public Supplier<CountingContainer> supplier() {
        return this;
      }

      @Override
      public BiConsumer<CountingContainer, T> accumulator() {
        return this;
      }

      @Override
      public BinaryOperator<CountingContainer> combiner() {
        return this;
      }

      @Override
      public Function<CountingContainer, R> finisher() {
        return countingContainer -> AsyncIterators.finishContainer(countingContainer.container,
            collector);
      }

      @Override
      public Set<Collector.Characteristics> characteristics() {
        return this.characteristics;
      }

      // supplier
      @Override
      public CountingContainer get() {
        return new CountingContainer(this.parentSupplier.get(), 0);
      }

      // accumulator
      @Override
      public void accept(final CountingContainer countingContainer, final T t) {
        this.parentAccumulator.accept(countingContainer.container, t);
        countingContainer.size++;
      }

      // combiner
      @Override
      public CountingContainer apply(final CountingContainer c1, final CountingContainer c2) {
        final A combined = this.parentCombiner.apply(c1.container, c2.container);
        // many mutable collectors simply addAll to the left container and return it.
        // this is an optimistic check to save a new container creation
        if (combined == c1.container) {
          c1.size += c2.size;
          return c1;
        } else {
          return new CountingContainer(combined, c1.size + c2.size);
        }
      }

      // shouldAddToBatch
      @Override
      public boolean test(final CountingContainer countingContainer, final T t) {
        return countingContainer.size < batchSize;
      }
    }

    final CountingCollector counter = new CountingCollector();
    return batch(counter, counter);
  }

  /**
   * Sequentially accumulates the elements of type T in this iterator into a U. This provides an
   * immutable style terminal reduction operation as opposed to the mutable style supported by
   * {@link #collect}. For example, to sum the lengths of Strings in an AsyncIterator, {@code
   * stringIt.fold(0, (acc, s) -> acc + s.length())}.
   *
   * <p>
   * This is a <i>terminal method</i>.
   *
   * @param accumulator a function that produces a new accumulation from an existing accumulation
   *        and a new element
   * @param identity a starting U value
   * @return a {@link CompletionStage} containing the resulting U from repeated application of
   *         accumulator
   */
  default <U> CompletionStage<U> fold(
      final U identity,
      final BiFunction<U, ? super T, U> accumulator) {
    @SuppressWarnings("unchecked")
    final U[] uarr = (U[]) new Object[] {identity};
    return this.collect(() -> uarr, (u, t) -> uarr[0] = accumulator.apply(uarr[0], t))
        .thenApply(arr -> arr[0]);
  }

  /**
   * Sequentially accumulates the elements of type T in this iterator into a single T value. This
   * provides an immutable style terminal reduction operation as opposed to the mutable style
   * supported by {@link #collect}. For example, to sum an iterator of ints,
   * {@code intIt.fold(0, (acc,
   * i) -> acc + i)}.
   *
   * <p>
   * This is a <i>terminal method</i>.
   *
   * @param accumulator a function that takes the current accumulated value and a value to fold in
   *        (in that order), and produces a new accumulated value.
   * @param identity a default T value
   * @return a {@link CompletionStage} containing the resulting T from repeated application of
   *         accumulator
   */
  default CompletionStage<T> fold(final T identity, final BinaryOperator<T> accumulator) {

    // don't make this a lambda - otherwise it will look like a BinaryOperator instead of a
    // BiFunction and we'll recurse
    final BiFunction<T, T, T> biAccumulator =
        new BiFunction<T, T, T>() {
          @Override
          public T apply(final T t, final T u) {
            return accumulator.apply(t, u);
          }
        };
    return fold(identity, biAccumulator);
  }

  /**
   * Forces the eager evaluation of the entire iterator, stopping only when {@code this} iterator is
   * out of elements or an exception is encountered.
   *
   * <p>
   * This is a <i>terminal method</i>.
   *
   * @return a {@link CompletionStage} that is completed when consumption is finished
   */
  default CompletionStage<Void> consume() {
    return AsyncTrampoline.asyncWhile(() -> nextStage().thenApply(Either::isRight));
  }

  /**
   * Performs a mutable reduction operation using collector and return a CompletionStage of the
   * result.
   *
   * <p>
   * This is a <i>terminal method</i>.
   *
   * @param collector a {@link Collector} which will sequentially collect the contents of this
   *        iterator into an {@code R}
   * @param <A> The intermediate type of the accumulated object
   * @param <R> The final type of the accumulated object
   * @return a {@link CompletionStage} which will complete with the collected value
   * @see Stream#collect(Collector)
   */
  default <R, A> CompletionStage<R> collect(final Collector<? super T, A, R> collector) {
    final A container = collector.supplier().get();
    final BiConsumer<A, ? super T> acc = collector.accumulator();
    return forEach(t -> acc.accept(container, t))
        .thenApply(ig -> AsyncIterators.finishContainer(container, collector));
  }

  /**
   * Performs a mutable reduction operation and return a {@link CompletionStage} of the result. A
   * mutable reduction is one where the accumulator has mutable state and additional elements are
   * incorporated by updating that state.
   *
   * <p>
   * This is a <i>terminal method</i>.
   *
   * @param supplier a supplier for a stateful accumulator
   * @param accumulator a function which can incorporate T elements into a stateful accumulation
   * @return a {@link CompletionStage} which will complete with the accumulated value
   * @see Stream#collect(Supplier, BiConsumer, BiConsumer)
   */
  default <R> CompletionStage<R> collect(
      final Supplier<R> supplier, final BiConsumer<R, ? super T> accumulator) {
    final R container = supplier.get();
    return forEach(t -> accumulator.accept(container, t)).thenApply(ig -> container);
  }

  /**
   * Performs the side effecting action until the end of iteration is reached
   *
   * <p>
   * This is a <i>terminal method</i>.
   *
   * @param action a side-effecting action that takes a T
   * @return a {@link CompletionStage} that returns when there are no elements left to apply {@code
   *     action} to, or an exception has been encountered.
   */
  default CompletionStage<Void> forEach(final Consumer<? super T> action) {
    return AsyncTrampoline.asyncWhile(
        () -> nextStage()
            .thenApply(
                eitherT -> {
                  eitherT.forEach(
                      ig -> {
                      },
                      action);
                  return eitherT.isRight();
                }));
  }

  /**
   * Gets the first element that satisfies predicate, or empty if no such element exists
   *
   * <p>
   * This is a <i>terminal method</i>.
   *
   * @param predicate the predicate that returns true for the desired element
   * @return a {@link CompletionStage} that completes with the first T to satisfy predicate, or
   *         empty if no such T exists
   */
  default CompletionStage<Optional<T>> find(final Predicate<? super T> predicate) {
    final CompletionStage<Either<End, T>> future = AsyncIterators
        .convertSynchronousException(this.filter(predicate)::nextStage);
    return future.thenApply((final Either<End, T> e) -> e.right());
  }

  /**
   * Flattens an {@link Iterator} of AsyncIterators into a single AsyncIterator.
   *
   * <pre>
   * {@code
   * AsyncIterators.concat(stream
   *   .map(value -> getAsyncIterator(value))
   *   .iterator())
   * }
   * </pre>
   *
   * Once all elements from an input AsyncIterator have been consumed, {@link #close()} is
   * internally called on that iterator. If this internal call to {@link #close()} produces an
   * exception, an exceptional stage will be included in the returned iterator. It is still
   * necessary to {@link #close()} the returned iterator, as this will close the most recently
   * accessed input iterator (which may be only partially consumed). Note that only the constituent
   * iterators encountered during traversal will be closed -- any remaining elements in the
   * encompassing synchronous iterator will be untouched if the returned iterator is only partially
   * traversed.
   * <p>
   * If {@link #close()} exceptions should be ignored, they should either be squashed in the input
   * iterators or the consumer may use manual {@link #nextStage()} iteration to continue past
   * exceptions.
   *
   * @param asyncIterators an Iterator of AsyncIterators to concatenate
   * @return A single AsyncIterator that is the concatenation of asyncIterators
   */
  static <T> AsyncIterator<T> concat(final Iterator<? extends AsyncIterator<T>> asyncIterators) {
    if (!asyncIterators.hasNext()) {
      return AsyncIterator.empty();
    }

    return new AsyncIterators.ConcatAsyncIterator<>(asyncIterators);
  }

  /**
   * Flattens a Collection of AsyncIterators into a single AsyncIterator.
   *
   * <pre>
   * // returns an AsyncInterator of 0,1,2,3,4
   * {@code
   * AsyncIterators.concat(Arrays.asList(
   *   AsyncIterators.range(0, 3),
   *   AsyncIterators.range(3, 5)))
   * }
   * </pre>
   *
   * Once all elements from an input AsyncIterator have been consumed, {@link #close()} is
   * internally called on that iterator. If this internal call to {@link #close()} produces an
   * exception, an exceptional stage will be included in the returned iterator. It is still
   * necessary to {@link #close()} the returned iterator, as this will close any remaining input
   * iterators (which may be only partially consumed).
   * <p>
   * If {@link #close()} exceptions should be ignored, they should either be squashed in the input
   * iterators or the consumer may use manual {@link #nextStage()} iteration to continue past
   * exceptions.
   * <p>
   * Unlike {@link #concat(Iterator)} and {@link #concat(AsyncIterator)}, closing the returned
   * iterator will additionally close any input iterators that were not encountered during
   * traversal. Because the input is a collection (which is eager unlike iterators) the constituent
   * iterators have likely been initialized and possibly already hold resources. As a convenience,
   * this concatenation will close all input iterators so that their references do not need to be
   * held outside of this concatenation's context. If closing all of the input iterators is not
   * desired, consider using {@link #concat(Iterator)} on the collection's
   * {@link Collection#iterator() iterator}
   *
   * @param asyncIterators a Collection of AsyncIterators to concatenate
   * @return A single AsyncIterator that is the concatenation of asyncIterators
   */
  static <T> AsyncIterator<T> concat(final Collection<? extends AsyncIterator<T>> asyncIterators) {
    final Iterator<? extends AsyncIterator<T>> iter = asyncIterators.iterator();
    if (!iter.hasNext()) {
      return AsyncIterator.empty();
    }

    return new AsyncIterators.ConcatAsyncIterator<T>(iter) {
      @Override
      public CompletionStage<Void> close() {
        final CompletionStage<Void> superClose = super.close();
        if (iter.hasNext()) {
          final Collection<CompletionStage<Void>> remainingIters = new ArrayList<>();
          do {
            remainingIters.add(iter.next().close());
          } while (iter.hasNext());
          return superClose.thenCombine(Combinators.allOf(remainingIters), (ig1, ig2) -> null);
        } else {
          return superClose;
        }
      }
    };
  }

  /**
   * Flattens an AsyncIterator of AsyncIterators into a single AsyncIterator
   *
   * <pre>
   * {@code
   * // returns an AsyncIterator of 0,1,2,0,1,2,0,1,2
   * AsyncIterators.concat(AsyncIterators.generate(() -> AsyncIterators.range(0, 3)).take(3))
   * }
   * </pre>
   * 
   * Once all elements from an input AsyncIterator have been consumed, {@link #close()} is
   * internally called on that iterator. If this internal call to {@link #close()} produces an
   * exception, an exceptional stage will be included in the returned iterator. It is still
   * necessary to {@link #close()} the returned iterator, as this will close the given encompassing
   * iterator, and the most recently accessed input iterator (which may be only partially consumed).
   * Note that only the constituent iterators encountered during traversal will be closed -- any
   * remaining elements in the encompassing iterator will be untouched if the returned iterator is
   * only partially traversed.
   * <p>
   * If {@link #close()} exceptions should be ignored, they should either be squashed in the input
   * iterators or the consumer may use manual {@link #nextStage()} iteration to continue past
   * exceptions.
   *
   * @param asyncIterators an AsyncIterator of AsyncIterators
   * @return A single AsyncIterator that is the concatenation of {@code asyncIterators}
   */
  static <T> AsyncIterator<T> concat(final AsyncIterator<AsyncIterator<T>> asyncIterators) {
    return new AsyncIterator<T>() {
      // initially set the iterator to empty so we always recurse the first time
      // after that, if curr ever becomes null, we're done
      AsyncIterator<T> curr = AsyncIterator.empty();

      @Override
      public CompletionStage<Either<End, T>> nextStage() {
        if (this.curr == null) {
          // out of iterators
          return End.endStage();
        }

        /*
         * on each iteration call nextStage. If it's empty, we should set curr to the next iterator.
         * If we are out of iterators, curr becomes null, and we stop iterating
         */
        return AsyncIterators.asyncWhileAsyncInitial(
            either -> !either.isRight() && this.curr != null,
            either -> {
              /*
               * if the result from the last call to nextStage() is empty, grab another
               * AsyncIterator out of asyncIterators and set curr
               */
              return either.fold(
                  end -> {
                    // current iterator was out of elements
                    return StageSupport.thenComposeOrRecover(
                        /*
                         * when reaching the end of one iterator, it must be closed before opening a
                         * new one. if the `close` future yields an error, an errorOnce iterator is
                         * concatenated with that close's exception, so the poll on the ConcatIter
                         * would encounter this exception. By using an errorOnce iter, the caller
                         * could choose to ignore the exception and attempt iterating again, which
                         * will pop the next asyncIterator off the meta iterator
                         */
                        AsyncIterators.convertSynchronousException(this.curr::close),
                        (t, throwable) -> throwable == null
                            ? asyncIterators.nextStage()
                            : StageSupport.completedStage(
                                Either.right(AsyncIterators.errorOnce(throwable))))
                        .thenCompose(nextIt -> {
                          // null if no iterators left
                          this.curr = nextIt.right().orElse(null);

                          // return the next future from the newly updated curr
                          return this.curr != null
                              ? this.curr.nextStage()
                              : End.<T>endStage();
                        });
                  },
                  t -> StageSupport.completedStage(either));
            },
            this.curr.nextStage());
      }

      @Override
      public CompletionStage<Void> close() {
        if (this.curr == null) {
          return asyncIterators.close();
        }
        return StageSupport.thenComposeOrRecover(
            AsyncIterators.convertSynchronousException(asyncIterators::close),
            (t, ex) -> {
              CompletionStage<Void> ret = this.curr.close();
              if (ex != null) {
                ret = ret.thenCompose(ig -> StageSupport.exceptionalStage(ex));
              }
              return ret;
            });
      }
    };
  }

  /**
   * Creates an iterator that is the result of fn applied to iteration elements returned by tIt and
   * uI. If either input iterator terminates, the returned iterator will terminate. If either input
   * iterator returns an exception, an exceptional result will be emitted by the returned iterator.
   * In the case of an exception, a single result will still be consumed from both iterators.
   *
   * <p>
   * When the returned iterator is {@link #close() closed}, the stage returned by close will be
   * complete when both {@code tIt} and {@code uIt} have been closed.
   *
   * @param tIt an AsyncIterator of Ts
   * @param uIt an AsyncIterator of Us
   * @param fn a function that produces a V from a T and a U
   * @return AsyncIterator of fn applied to elements of tIt and uIt
   */
  static <T, U, V> AsyncIterator<V> zipWith(
      final AsyncIterator<T> tIt,
      final AsyncIterator<U> uIt,
      final BiFunction<? super T, ? super U, V> fn) {
    // once all futures are complete, if all are nonempty, then apply fn to the arg
    return new AsyncIterator<V>() {
      @Override
      public CompletionStage<Either<End, V>> nextStage() {
        // call nextStage before checking for an exception
        final CompletionStage<Either<End, T>> tFuture =
            AsyncIterators.convertSynchronousException(tIt::nextStage);
        final CompletionStage<Either<End, U>> uFuture =
            AsyncIterators.convertSynchronousException(uIt::nextStage);
        return tFuture.thenCombine(uFuture, (et, eu) -> AsyncIterators.zipWith(et, eu, fn));
      }

      @Override
      public CompletionStage<Void> close() {
        return Combinators
            .allOf(Arrays.asList(
                AsyncIterators.convertSynchronousException(tIt::close),
                AsyncIterators.convertSynchronousException(uIt::close)));
      }
    };
  }

  /**
   * Creates an empty AsyncIterator.
   *
   * @return an AsyncIterator that will immediately produce an {@link End} marker
   */
  @SuppressWarnings("unchecked")
  static <T> AsyncIterator<T> empty() {
    return (AsyncIterator<T>) AsyncIterators.EMPTY_ITERATOR;
  }

  /**
   * Creates an AsyncIterator from an {@link Iterator}
   *
   * @param iterator an {@link Iterator} of T elements
   * @return A new AsyncIterator which will yield the elements of {@code iterator}
   */
  static <T> AsyncIterator<T> fromIterator(final Iterator<? extends T> iterator) {
    return () -> StageSupport.completedStage(
        iterator.hasNext() ? Either.right(iterator.next()) : End.end());
  }

  /**
   * Creates an AsyncIterator of one element.
   *
   * @param t the element to return
   * @return an AsyncIterator which yields the element t, and then afterward produce the {@link End}
   *         marker.
   */
  static <T> AsyncIterator<T> once(final T t) {
    return new AsyncIterator<T>() {
      Either<End, T> curr = Either.right(t);

      @Override
      public CompletionStage<Either<End, T>> nextStage() {
        final Either<End, T> prev = this.curr;
        this.curr = End.end();
        return StageSupport.completedStage(prev);
      }
    };
  }

  /**
   * Creates an AsyncIterator for which all downstream operations will be completed with an
   * exception.
   *
   * @param ex the exception which the {@link CompletionStage CompletionStages} of the returned
   *        iterator will be completed with
   * @return an AsyncIterator that produces exceptional CompletionStages
   */
  static <T> AsyncIterator<T> error(final Throwable ex) {
    final CompletionStage<Either<End, T>> stage = StageSupport.exceptionalStage(ex);
    return () -> stage;
  }

  /**
   * Creates an infinite AsyncIterator of the same value.
   *
   * @param t the value to repeat
   * @return An AsyncIterator that will always return {@code t}
   */
  static <T> AsyncIterator<T> repeat(final T t) {
    final CompletionStage<Either<End, T>> ret = StageSupport.completedStage(Either.right(t));
    return () -> ret;
  }

  /**
   * Creates an AsyncIterator for a range.
   *
   * <p>
   * Similar to {@code for(i = start; i < end; i++)}.
   *
   * <p>
   * The stages returned by nextStage will be already completed.
   *
   * @param start the start point of iteration (inclusive)
   * @param end the end point of iteration (exclusive)
   * @return an AsyncIterator that will return longs from start to end
   */
  static AsyncIterator<Long> range(final long start, final long end) {
    if (start >= end) {
      return AsyncIterator.empty();
    }
    return new AsyncIterator<Long>() {
      long counter = start;

      @Override
      public CompletionStage<Either<End, Long>> nextStage() {
        if (this.counter < end) {
          return StageSupport.completedStage(Either.right(this.counter++));
        } else {
          return End.endStage();
        }
      }
    };
  }

  /**
   * Creates an infinite AsyncIterator starting at {@code start}.
   *
   * @param start the start point of iteration (inclusive)
   * @return an AsyncIterator that will return longs starting with start
   */
  static AsyncIterator<Long> infiniteRange(final long start) {
    return new AsyncIterator<Long>() {
      long counter = start;

      @Override
      public CompletionStage<Either<End, Long>> nextStage() {
        return StageSupport.completedStage(Either.right(this.counter++));
      }
    };
  }

  /**
   * Creates an AsyncIterator from a collection of {@link CompletionStage CompletionStages}. When a
   * stage completes, the value becomes available for consumption in the returned iterator. If a
   * stage completes exceptionally, the returned iterator will emit an exceptional stage. The order
   * in which values are returned does not reflect the original order of the collection of stages.
   *
   * @param stages a Collection of {@link CompletionStage CompletionStages} that will be emitted in
   *        the returned iterator as they complete
   * @return AsyncIterator of values produced by stages in order of completion
   */
  static <T> AsyncIterator<T> unordered(final Collection<? extends CompletionStage<T>> stages) {
    final int size = stages.size();
    if (size == 0) {
      return AsyncIterator.empty();
    }
    final AtomicInteger count = new AtomicInteger();
    final AsyncQueue<Either<Throwable, T>> channel = AsyncQueues.unbounded();
    for (final CompletionStage<T> future : stages) {
      future.whenComplete((t, ex) -> {
        final Either<Throwable, T> toSend = ex != null ? Either.left(ex) : Either.right(t);
        channel.send(toSend);
        if (count.incrementAndGet() == size) {
          // terminate the channel
          channel.terminate();
        }
      });
    }
    return channel.thenCompose(either -> either.fold(
        StageSupport::exceptionalStage,
        StageSupport::completedStage));
  }

  /**
   * Creates an infinite AsyncIterator of type T.
   *
   * @param supplier supplies stages for elements to be yielded by the returned iterator
   * @return AsyncIterator returning values generated from {@code supplier}
   */
  static <T> AsyncIterator<T> generate(final Supplier<? extends CompletionStage<T>> supplier) {
    return () -> supplier.get().thenApply(Either::right);
  }

  /**
   * Creates an AsyncIterator of type T
   *
   * @param supplier produces CompletionStages of elements in the iterator or indicates end of
   *        iteration with {@link End}
   * @return An AsyncIterator that produces the values generated by the {@code supplier}
   */
  static <T> AsyncIterator<T> supply(
      final Supplier<? extends CompletionStage<Either<End, T>>> supplier) {
    return supplier::get;
  }

  /**
   * Successively applies the asynchronous function f to the seed until the stage returned by f
   * contains an empty optional or returns an exception. Creates an iterator of values of
   * applications
   *
   * <p>
   * For example, if {@code f = t -> StageSupport.completedStage(Either.right(f(t)))}, then this
   * would produce an asynchronous stream of the values {@code seed, f(seed), f(f(seed)),
   * f(f(f(seed))),...}. The iterator is potentially infinite - it would be in the preceding
   * example.
   *
   * @param seed the first value produced by the returned iterator
   * @param f the function that is applied to the previous value to generate the next value
   * @return AsyncIterator of the values returned by f
   * @see Stream#iterate(Object, java.util.function.UnaryOperator)
   */
  static <T> AsyncIterator<T> unfold(
      final T seed, final Function<? super T, ? extends CompletionStage<Either<End, T>>> f) {
    return new AsyncIterator<T>() {
      CompletionStage<Either<End, T>> prev = StageSupport.completedStage(Either.right(seed));

      @Override
      public CompletionStage<Either<End, T>> nextStage() {
        // if there was a value, apply f to it
        final CompletionStage<Either<End, T>> ret = this.prev;
        this.prev = this.prev.thenCompose(
            nxt -> nxt.fold(end -> End.endStage(), f));
        return ret;
      }
    };
  }
}