/*
 * Copyright 2015, 2017 StreamEx contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package one.util.streamex;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.Buffer;
import java.util.*;
import java.util.Map.Entry;
import java.util.PrimitiveIterator.OfInt;
import java.util.Spliterators.AbstractIntSpliterator;
import java.util.concurrent.ForkJoinPool;
import java.util.function.*;
import java.util.stream.*;

import static one.util.streamex.StreamExInternals.*;

/**
 * An {@link IntStream} implementation with additional functionality
 * 
 * @author Tagir Valeev
 */
public class IntStreamEx extends BaseStreamEx<Integer, IntStream, Spliterator.OfInt, IntStreamEx> implements IntStream {
    IntStreamEx(IntStream stream, StreamContext context) {
        super(stream, context);
    }

    IntStreamEx(Spliterator.OfInt spliterator, StreamContext context) {
        super(spliterator, context);
    }

    @Override
    IntStream createStream() {
        return StreamSupport.intStream(spliterator, context.parallel);
    }

    private static IntStreamEx seq(IntStream stream) {
        return new IntStreamEx(stream, StreamContext.SEQUENTIAL);
    }

    final IntStreamEx delegate(Spliterator.OfInt spliterator) {
        return new IntStreamEx(spliterator, context);
    }

    private <A> A collectSized(Supplier<A> supplier, ObjIntConsumer<A> accumulator, BiConsumer<A, A> combiner,
                               IntFunction<A> sizedSupplier, ObjIntConsumer<A> sizedAccumulator) {
        if (isParallel())
            return collect(supplier, accumulator, combiner);
        java.util.Spliterator.OfInt spliterator = spliterator();
        long size = spliterator.getExactSizeIfKnown();
        A intermediate;
        if (size >= 0 && size <= Integer.MAX_VALUE) {
            intermediate = sizedSupplier.apply((int) size);
            spliterator.forEachRemaining((IntConsumer) i -> sizedAccumulator.accept(intermediate, i));
        } else {
            intermediate = supplier.get();
            spliterator.forEachRemaining((IntConsumer) i -> accumulator.accept(intermediate, i));
        }
        return intermediate;
    }

    @Override
    public IntStreamEx unordered() {
        return (IntStreamEx) super.unordered();
    }

    @Override
    public IntStreamEx onClose(Runnable closeHandler) {
        return (IntStreamEx) super.onClose(closeHandler);
    }

    @Override
    public IntStreamEx filter(IntPredicate predicate) {
        return new IntStreamEx(stream().filter(predicate), context);
    }

    /**
     * Returns a stream consisting of the elements of this stream that don't
     * match the given predicate.
     *
     * <p>
     * This is an intermediate operation.
     *
     * @param predicate a non-interfering, stateless predicate to apply to each
     *        element to determine if it should be excluded
     * @return the new stream
     */
    public IntStreamEx remove(IntPredicate predicate) {
        return filter(predicate.negate());
    }

    /**
     * Returns true if this stream contains the specified value.
     *
     * <p>
     * This is a short-circuiting terminal operation.
     * 
     * @param value the value too look for in the stream
     * @return true if this stream contains the specified value
     * @see IntStream#anyMatch(IntPredicate)
     */
    public boolean has(int value) {
        return anyMatch(x -> x == value);
    }

    /**
     * Returns a stream consisting of the elements of this stream that don't
     * equal to the given value.
     *
     * <p>
     * This is an intermediate operation.
     *
     * @param value the value to remove from the stream.
     * @return the new stream
     * @since 0.2.2
     * @see #without(int...)
     * @see #remove(IntPredicate)
     */
    public IntStreamEx without(int value) {
        return filter(val -> val != value);
    }

    /**
     * Returns a stream consisting of the elements of this stream that don't
     * equal to any of the supplied values.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate</a>
     * operation. May return itself if no values were supplied.
     * 
     * <p>
     * Current implementation scans the supplied values linearly for every
     * stream element.
     * 
     * <p>
     * If the {@code values} array is changed between calling this method and
     * finishing the stream traversal, then the result of the stream traversal
     * is undefined: changes may or may not be taken into account.
     *
     * @param values the values to remove from the stream.
     * @return the new stream
     * @since 0.5.5
     * @see #without(int)
     * @see #remove(IntPredicate)
     */
    public IntStreamEx without(int... values) {
        if (values.length == 0)
            return this;
        if (values.length == 1)
            return without(values[0]);
        return filter(x -> {
            for (int val : values) {
                if (x == val)
                    return false;
            }
            return true;
        });
    }

    /**
     * Returns a stream consisting of the elements of this stream that strictly
     * greater than the specified value.
     *
     * <p>
     * This is an intermediate operation.
     *
     * @param value a value to compare to
     * @return the new stream
     * @since 0.2.3
     */
    public IntStreamEx greater(int value) {
        return filter(val -> val > value);
    }

    /**
     * Returns a stream consisting of the elements of this stream that greater
     * than or equal to the specified value.
     *
     * <p>
     * This is an intermediate operation.
     *
     * @param value a value to compare to
     * @return the new stream
     * @since 0.2.3
     */
    public IntStreamEx atLeast(int value) {
        return filter(val -> val >= value);
    }

    /**
     * Returns a stream consisting of the elements of this stream that strictly
     * less than the specified value.
     *
     * <p>
     * This is an intermediate operation.
     *
     * @param value a value to compare to
     * @return the new stream
     * @since 0.2.3
     */
    public IntStreamEx less(int value) {
        return filter(val -> val < value);
    }

    /**
     * Returns a stream consisting of the elements of this stream that less than
     * or equal to the specified value.
     *
     * <p>
     * This is an intermediate operation.
     *
     * @param value a value to compare to
     * @return the new stream
     * @since 0.2.3
     */
    public IntStreamEx atMost(int value) {
        return filter(val -> val <= value);
    }

    @Override
    public IntStreamEx map(IntUnaryOperator mapper) {
        return new IntStreamEx(stream().map(mapper), context);
    }

    @Override
    public <U> StreamEx<U> mapToObj(IntFunction<? extends U> mapper) {
        return new StreamEx<>(stream().mapToObj(mapper), context);
    }

    @Override
    public LongStreamEx mapToLong(IntToLongFunction mapper) {
        return new LongStreamEx(stream().mapToLong(mapper), context);
    }

    @Override
    public DoubleStreamEx mapToDouble(IntToDoubleFunction mapper) {
        return new DoubleStreamEx(stream().mapToDouble(mapper), context);
    }

    /**
     * Returns an {@link EntryStream} consisting of the {@link Entry} objects
     * which keys and values are results of applying the given functions to the
     * elements of this stream.
     *
     * <p>
     * This is an intermediate operation.
     *
     * @param <K> The {@code Entry} key type
     * @param <V> The {@code Entry} value type
     * @param keyMapper a non-interfering, stateless function to apply to each
     *        element
     * @param valueMapper a non-interfering, stateless function to apply to each
     *        element
     * @return the new stream
     * @since 0.3.1
     */
    public <K, V> EntryStream<K, V> mapToEntry(IntFunction<? extends K> keyMapper,
            IntFunction<? extends V> valueMapper) {
        return new EntryStream<>(stream().mapToObj(t -> new AbstractMap.SimpleImmutableEntry<>(keyMapper.apply(t),
                valueMapper.apply(t))), context);
    }

    @Override
    public IntStreamEx flatMap(IntFunction<? extends IntStream> mapper) {
        return new IntStreamEx(stream().flatMap(mapper), context);
    }

    /**
     * Returns a {@link LongStreamEx} consisting of the results of replacing
     * each element of this stream with the contents of a mapped stream produced
     * by applying the provided mapping function to each element. Each mapped
     * stream is closed after its contents have been placed into this stream.
     * (If a mapped stream is {@code null} an empty stream is used, instead.)
     *
     * <p>
     * This is an intermediate operation.
     *
     * @param mapper a non-interfering, stateless function to apply to each
     *        element which produces a {@code LongStream} of new values
     * @return the new stream
     * @since 0.3.0
     */
    public LongStreamEx flatMapToLong(IntFunction<? extends LongStream> mapper) {
        return new LongStreamEx(stream().mapToObj(mapper).flatMapToLong(Function.identity()), context);
    }

    /**
     * Returns a {@link DoubleStreamEx} consisting of the results of replacing
     * each element of this stream with the contents of a mapped stream produced
     * by applying the provided mapping function to each element. Each mapped
     * stream is closed after its contents have been placed into this stream.
     * (If a mapped stream is {@code null} an empty stream is used, instead.)
     *
     * <p>
     * This is an intermediate operation.
     *
     * @param mapper a non-interfering, stateless function to apply to each
     *        element which produces a {@code DoubleStream} of new values
     * @return the new stream
     * @since 0.3.0
     */
    public DoubleStreamEx flatMapToDouble(IntFunction<? extends DoubleStream> mapper) {
        return new DoubleStreamEx(stream().mapToObj(mapper).flatMapToDouble(Function.identity()), context);
    }

    /**
     * Returns a {@link StreamEx} consisting of the results of replacing each
     * element of this stream with the contents of a mapped stream produced by
     * applying the provided mapping function to each element. Each mapped
     * stream is closed after its contents have been placed into this stream.
     * (If a mapped stream is {@code null} an empty stream is used, instead.)
     *
     * <p>
     * This is an intermediate operation.
     *
     * @param <R> The element type of the new stream
     * @param mapper a non-interfering, stateless function to apply to each
     *        element which produces a {@code Stream} of new values
     * @return the new stream
     * @since 0.3.0
     */
    public <R> StreamEx<R> flatMapToObj(IntFunction<? extends Stream<R>> mapper) {
        return new StreamEx<>(stream().mapToObj(mapper).flatMap(Function.identity()), context);
    }

    /**
     * Returns a new stream containing all the elements of the original stream interspersed with
     * given delimiter.
     * 
     * <p>
     * For example, {@code IntStreamEx.of(1, 2, 3).intersperse(4)} will yield a stream containing
     * five elements: 1, 4, 2, 4, 3.
     * 
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate operation</a>.
     * 
     * @param delimiter a delimiter to be inserted between each pair of elements
     * @return the new stream
     * @since 0.6.6
     */
    public IntStreamEx intersperse(int delimiter) {
        return new IntStreamEx(stream().flatMap(s -> IntStreamEx.of(delimiter, s)).skip(1), context);
    }

    @Override
    public IntStreamEx distinct() {
        return new IntStreamEx(stream().distinct(), context);
    }

    @Override
    public IntStreamEx sorted() {
        return new IntStreamEx(stream().sorted(), context);
    }

    /**
     * Returns a stream consisting of the elements of this stream sorted
     * according to the given comparator. Stream elements are boxed before
     * passing to the comparator.
     *
     * <p>
     * For ordered streams, the sort is stable. For unordered streams, no
     * stability guarantees are made.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">stateful intermediate
     * operation</a>.
     *
     * @param comparator a
     *        <a href="package-summary.html#NonInterference">non-interfering
     *        </a>, <a href="package-summary.html#Statelessness">stateless</a>
     *        {@code Comparator} to be used to compare stream elements
     * @return the new stream
     */
    public IntStreamEx sorted(Comparator<Integer> comparator) {
        return new IntStreamEx(stream().boxed().sorted(comparator).mapToInt(Integer::intValue), context);
    }

    /**
     * Returns a stream consisting of the elements of this stream in reverse
     * sorted order.
     *
     * <p>
     * This is a stateful intermediate operation.
     *
     * @return the new stream
     * @since 0.0.8
     */
    public IntStreamEx reverseSorted() {
        IntUnaryOperator inv = x -> ~x;
        return new IntStreamEx(stream().map(inv).sorted().map(inv), context);
    }

    /**
     * Returns a stream consisting of the elements of this stream, sorted
     * according to the natural order of the keys extracted by provided
     * function.
     *
     * <p>
     * For ordered streams, the sort is stable. For unordered streams, no
     * stability guarantees are made.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">stateful intermediate
     * operation</a>.
     *
     * @param <V> the type of the {@code Comparable} sort key
     * @param keyExtractor a
     *        <a href="package-summary.html#NonInterference">non-interfering
     *        </a>, <a href="package-summary.html#Statelessness">stateless</a>
     *        function to be used to extract sorting keys
     * @return the new stream
     */
    public <V extends Comparable<? super V>> IntStreamEx sortedBy(IntFunction<V> keyExtractor) {
        return sorted(Comparator.comparing(keyExtractor::apply));
    }

    /**
     * Returns a stream consisting of the elements of this stream, sorted
     * according to the int values extracted by provided function.
     *
     * <p>
     * For ordered streams, the sort is stable. For unordered streams, no
     * stability guarantees are made.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">stateful intermediate
     * operation</a>.
     *
     * @param keyExtractor a
     *        <a href="package-summary.html#NonInterference">non-interfering
     *        </a>, <a href="package-summary.html#Statelessness">stateless</a>
     *        function to be used to extract sorting keys
     * @return the new stream
     */
    public IntStreamEx sortedByInt(IntUnaryOperator keyExtractor) {
        return sorted(Comparator.comparingInt(keyExtractor::applyAsInt));
    }

    /**
     * Returns a stream consisting of the elements of this stream, sorted
     * according to the long values extracted by provided function.
     *
     * <p>
     * For ordered streams, the sort is stable. For unordered streams, no
     * stability guarantees are made.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">stateful intermediate
     * operation</a>.
     *
     * @param keyExtractor a
     *        <a href="package-summary.html#NonInterference">non-interfering
     *        </a>, <a href="package-summary.html#Statelessness">stateless</a>
     *        function to be used to extract sorting keys
     * @return the new stream
     */
    public IntStreamEx sortedByLong(IntToLongFunction keyExtractor) {
        return sorted(Comparator.comparingLong(keyExtractor::applyAsLong));
    }

    /**
     * Returns a stream consisting of the elements of this stream, sorted
     * according to the double values extracted by provided function.
     *
     * <p>
     * For ordered streams, the sort is stable. For unordered streams, no
     * stability guarantees are made.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">stateful intermediate
     * operation</a>.
     *
     * @param keyExtractor a
     *        <a href="package-summary.html#NonInterference">non-interfering
     *        </a>, <a href="package-summary.html#Statelessness">stateless</a>
     *        function to be used to extract sorting keys
     * @return the new stream
     */
    public IntStreamEx sortedByDouble(IntToDoubleFunction keyExtractor) {
        return sorted(Comparator.comparingDouble(keyExtractor::applyAsDouble));
    }

    @Override
    public IntStreamEx peek(IntConsumer action) {
        return new IntStreamEx(stream().peek(action), context);
    }

    /**
     * Returns a stream consisting of the elements of this stream, additionally
     * performing the provided action on the first stream element when it's
     * consumed from the resulting stream.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * <p>
     * The action is called at most once. For parallel stream pipelines, it's
     * not guaranteed in which thread it will be executed, so if it modifies
     * shared state, it is responsible for providing the required
     * synchronization.
     *
     * <p>
     * Note that the action might not be called at all if the first element is
     * not consumed from the input (for example, if there's short-circuiting
     * operation downstream which stopped the stream before the first element).
     * 
     * <p>
     * This method exists mainly to support debugging.
     *
     * @param action a
     *        <a href="package-summary.html#NonInterference"> non-interfering
     *        </a> action to perform on the first stream element as it is
     *        consumed from the stream
     * @return the new stream
     * @since 0.6.0
     */
    public IntStreamEx peekFirst(IntConsumer action) {
        return mapFirst(x -> {
            action.accept(x);
            return x;
        });
    }

    /**
     * Returns a stream consisting of the elements of this stream, additionally
     * performing the provided action on the last stream element when it's
     * consumed from the resulting stream.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * <p>
     * The action is called at most once. For parallel stream pipelines, it's
     * not guaranteed in which thread it will be executed, so if it modifies
     * shared state, it is responsible for providing the required
     * synchronization.
     * 
     * <p>
     * Note that the action might not be called at all if the last element is
     * not consumed from the input (for example, if there's short-circuiting
     * operation downstream).
     * 
     * <p>
     * This method exists mainly to support debugging.
     *
     * @param action a
     *        <a href="package-summary.html#NonInterference"> non-interfering
     *        </a> action to perform on the first stream element as it is
     *        consumed from the stream
     * @return the new stream
     * @since 0.6.0
     */
    public IntStreamEx peekLast(IntConsumer action) {
        return mapLast(x -> {
            action.accept(x);
            return x;
        });
    }

    @Override
    public IntStreamEx limit(long maxSize) {
        return new IntStreamEx(stream().limit(maxSize), context);
    }

    @Override
    public IntStreamEx skip(long n) {
        return new IntStreamEx(stream().skip(n), context);
    }

    /**
     * Returns a stream consisting of the remaining elements of this stream
     * after discarding the first {@code n} elements of the stream. If this
     * stream contains fewer than {@code n} elements then an empty stream will
     * be returned.
     *
     * <p>
     * This is a stateful quasi-intermediate operation. Unlike
     * {@link #skip(long)} it skips the first elements even if the stream is
     * unordered. The main purpose of this method is to workaround the problem
     * of skipping the first elements from non-sized source with further
     * parallel processing and unordered terminal operation (such as
     * {@link #forEach(IntConsumer)}). This problem was fixed in OracleJDK 8u60.
     * 
     * <p>
     * Also it behaves much better with infinite streams processed in parallel.
     * For example,
     * {@code IntStreamEx.iterate(0, i->i+1).skip(1).limit(100).parallel().toArray()}
     * will likely to fail with {@code OutOfMemoryError}, but will work nicely
     * if {@code skip} is replaced with {@code skipOrdered}.
     *
     * <p>
     * For sequential streams this method behaves exactly like
     * {@link #skip(long)}.
     *
     * @param n the number of leading elements to skip
     * @return the new stream
     * @throws IllegalArgumentException if {@code n} is negative
     * @see #skip(long)
     * @since 0.3.2
     */
    public IntStreamEx skipOrdered(long n) {
        Spliterator.OfInt spliterator = (isParallel() ? StreamSupport.intStream(spliterator(), false) : stream()).skip(
            n).spliterator();
        return delegate(spliterator);
    }

    @Override
    public void forEach(IntConsumer action) {
        if (spliterator != null && !isParallel()) {
            spliterator().forEachRemaining(action);
        } else {
            if (context.fjp != null)
                context.terminate(() -> {
                    stream().forEach(action);
                    return null;
                });
            else {
                stream().forEach(action);
            }
        }
    }

    @Override
    public void forEachOrdered(IntConsumer action) {
        if (spliterator != null && !isParallel()) {
            spliterator().forEachRemaining(action);
        } else {
            if (context.fjp != null)
                context.terminate(() -> {
                    stream().forEachOrdered(action);
                    return null;
                });
            else {
                stream().forEachOrdered(action);
            }
        }
    }

    @Override
    public int[] toArray() {
        if (context.fjp != null)
            return context.terminate(stream()::toArray);
        return stream().toArray();
    }

    /**
     * Returns a {@code byte[]} array containing the elements of this stream
     * which are converted to bytes using {@code (byte)} cast operation.
     *
     * <p>
     * This is a terminal operation.
     *
     * @return an array containing the elements of this stream
     * @since 0.3.0
     */
    public byte[] toByteArray() {
        return collectSized(ByteBuffer::new, ByteBuffer::add, ByteBuffer::addAll, ByteBuffer::new,
            ByteBuffer::addUnsafe).toArray();
    }

    /**
     * Returns a {@code char[]} array containing the elements of this stream
     * which are converted to chars using {@code (char)} cast operation.
     *
     * <p>
     * This is a terminal operation.
     *
     * @return an array containing the elements of this stream
     * @since 0.3.0
     */
    public char[] toCharArray() {
        return collectSized(CharBuffer::new, CharBuffer::add, CharBuffer::addAll, CharBuffer::new,
            CharBuffer::addUnsafe).toArray();
    }

    /**
     * Returns a {@code short[]} array containing the elements of this stream
     * which are converted to shorts using {@code (short)} cast operation.
     *
     * <p>
     * This is a terminal operation.
     *
     * @return an array containing the elements of this stream
     * @since 0.3.0
     */
    public short[] toShortArray() {
        return collectSized(ShortBuffer::new, ShortBuffer::add, ShortBuffer::addAll, ShortBuffer::new,
            ShortBuffer::addUnsafe).toArray();
    }

    /**
     * Returns a {@link BitSet} containing the elements of this stream.
     *
     * <p>
     * This is a terminal operation.
     *
     * @return a {@code BitSet} which set bits correspond to the elements of
     *         this stream.
     * @since 0.2.0
     */
    public BitSet toBitSet() {
        return collect(BitSet::new, BitSet::set, BitSet::or);
    }

    /**
     * Returns an {@code InputStream} lazily populated from the current
     * {@code IntStreamEx}.
     * 
     * <p>
     * Note that only the least-significant byte of every number encountered in
     * this stream is preserved in the resulting {@code InputStream}, other
     * bytes are silently lost. Thus it's a caller responsibility to check
     * whether this may cause problems.
     * 
     * <p>
     * This is a terminal operation.
     * 
     * <p>
     * When the resulting {@code InputStream} is closed, this
     * {@code IntStreamEx} is closed as well.
     *
     * @return a new {@code InputStream}.
     * @see #of(InputStream)
     * @since 0.6.1
     */
    public InputStream asByteInputStream() {
        Spliterator.OfInt spltr = spliterator();
        return new InputStream() {
            private int last;

            @Override
            public int read() {
                return spltr.tryAdvance((int val) -> last = val) ? (last & 0xFF) : -1;
            }

            @Override
            public void close() {
                IntStreamEx.this.close();
            }
        };
    }

    @Override
    public int reduce(int identity, IntBinaryOperator op) {
        if (context.fjp != null)
            return context.terminate(() -> stream().reduce(identity, op));
        return stream().reduce(identity, op);
    }

    @Override
    public OptionalInt reduce(IntBinaryOperator op) {
        if (context.fjp != null)
            return context.terminate(op, stream()::reduce);
        return stream().reduce(op);
    }

    /**
     * Folds the elements of this stream using the provided accumulation
     * function, going left to right. This is equivalent to:
     * 
     * <pre>
     * {@code
     *     boolean foundAny = false;
     *     int result = 0;
     *     for (int element : this stream) {
     *         if (!foundAny) {
     *             foundAny = true;
     *             result = element;
     *         }
     *         else
     *             result = accumulator.apply(result, element);
     *     }
     *     return foundAny ? OptionalInt.of(result) : OptionalInt.empty();
     * }
     * </pre>
     * 
     * <p>
     * This is a terminal operation.
     * 
     * <p>
     * This method cannot take all the advantages of parallel streams as it must
     * process elements strictly left to right. If your accumulator function is
     * associative, consider using {@link #reduce(IntBinaryOperator)} method.
     * 
     * <p>
     * For parallel stream it's not guaranteed that accumulator will always be
     * executed in the same thread.
     *
     * @param accumulator a
     *        <a href="package-summary.html#NonInterference">non-interfering
     *        </a>, <a href="package-summary.html#Statelessness">stateless</a>
     *        function for incorporating an additional element into a result
     * @return the result of the folding
     * @see #foldLeft(int, IntBinaryOperator)
     * @see #reduce(IntBinaryOperator)
     * @since 0.4.0
     */
    public OptionalInt foldLeft(IntBinaryOperator accumulator) {
        PrimitiveBox b = new PrimitiveBox();
        forEachOrdered(t -> {
            if (b.b)
                b.i = accumulator.applyAsInt(b.i, t);
            else {
                b.i = t;
                b.b = true;
            }
        });
        return b.asInt();
    }

    /**
     * Folds the elements of this stream using the provided seed object and
     * accumulation function, going left to right. This is equivalent to:
     * 
     * <pre>
     * {@code
     *     int result = seed;
     *     for (int element : this stream)
     *         result = accumulator.apply(result, element)
     *     return result;
     * }
     * </pre>
     *
     * <p>
     * This is a terminal operation.
     * 
     * <p>
     * This method cannot take all the advantages of parallel streams as it must
     * process elements strictly left to right. If your accumulator function is
     * associative, consider using {@link #reduce(int, IntBinaryOperator)}
     * method.
     * 
     * <p>
     * For parallel stream it's not guaranteed that accumulator will always be
     * executed in the same thread.
     *
     * @param seed the starting value
     * @param accumulator a
     *        <a href="package-summary.html#NonInterference">non-interfering
     *        </a>, <a href="package-summary.html#Statelessness">stateless</a>
     *        function for incorporating an additional element into a result
     * @return the result of the folding
     * @see #reduce(int, IntBinaryOperator)
     * @see #foldLeft(IntBinaryOperator)
     * @since 0.4.0
     */
    public int foldLeft(int seed, IntBinaryOperator accumulator) {
        int[] box = new int[] { seed };
        forEachOrdered(t -> box[0] = accumulator.applyAsInt(box[0], t));
        return box[0];
    }

    /**
     * Produces an array containing cumulative results of applying the
     * accumulation function going left to right.
     * 
     * <p>
     * This is a terminal operation.
     * 
     * <p>
     * For parallel stream it's not guaranteed that accumulator will always be
     * executed in the same thread.
     * 
     * <p>
     * This method cannot take all the advantages of parallel streams as it must
     * process elements strictly left to right.
     *
     * @param accumulator a
     *        <a href="package-summary.html#NonInterference">non-interfering
     *        </a>, <a href="package-summary.html#Statelessness">stateless</a>
     *        function for incorporating an additional element into a result
     * @return the array where the first element is the first element of this
     *         stream and every successor element is the result of applying
     *         accumulator function to the previous array element and the
     *         corresponding stream element. The resulting array has the same
     *         length as this stream.
     * @see #foldLeft(IntBinaryOperator)
     * @since 0.5.1
     */
    public int[] scanLeft(IntBinaryOperator accumulator) {
        Spliterator.OfInt spliterator = spliterator();
        long size = spliterator.getExactSizeIfKnown();
        IntBuffer buf = new IntBuffer(size >= 0 && size <= Integer.MAX_VALUE ? (int) size : INITIAL_SIZE);
        delegate(spliterator).forEachOrdered(i -> buf.add(buf.size == 0 ? i
                : accumulator.applyAsInt(buf.data[buf.size - 1], i)));
        return buf.toArray();
    }

    /**
     * Produces an array containing cumulative results of applying the
     * accumulation function going left to right using given seed value.
     * 
     * <p>
     * This is a terminal operation.
     * 
     * <p>
     * For parallel stream it's not guaranteed that accumulator will always be
     * executed in the same thread.
     * 
     * <p>
     * This method cannot take all the advantages of parallel streams as it must
     * process elements strictly left to right.
     *
     * @param seed the starting value
     * @param accumulator a
     *        <a href="package-summary.html#NonInterference">non-interfering
     *        </a>, <a href="package-summary.html#Statelessness">stateless</a>
     *        function for incorporating an additional element into a result
     * @return the array where the first element is the seed and every successor
     *         element is the result of applying accumulator function to the
     *         previous array element and the corresponding stream element. The
     *         resulting array is one element longer than this stream.
     * @see #foldLeft(int, IntBinaryOperator)
     * @since 0.5.1
     */
    public int[] scanLeft(int seed, IntBinaryOperator accumulator) {
        return prepend(seed).scanLeft(accumulator);
    }

    /**
     * {@inheritDoc}
     * 
     * @see #collect(IntCollector)
     */
    @Override
    public <R> R collect(Supplier<R> supplier, ObjIntConsumer<R> accumulator, BiConsumer<R, R> combiner) {
        if (context.fjp != null)
            return context.terminate(() -> stream().collect(supplier, accumulator, combiner));
        return stream().collect(supplier, accumulator, combiner);
    }

    /**
     * Performs a mutable reduction operation on the elements of this stream
     * using an {@link IntCollector} which encapsulates the supplier,
     * accumulator and merger functions making easier to reuse collection
     * strategies.
     *
     * <p>
     * Like {@link #reduce(int, IntBinaryOperator)}, {@code collect} operations
     * can be parallelized without requiring additional synchronization.
     *
     * <p>
     * This is a terminal operation.
     *
     * @param <A> the intermediate accumulation type of the {@code IntCollector}
     * @param <R> type of the result
     * @param collector the {@code IntCollector} describing the reduction
     * @return the result of the reduction
     * @see #collect(Supplier, ObjIntConsumer, BiConsumer)
     * @since 0.3.0
     */
    @SuppressWarnings("unchecked")
    public <A, R> R collect(IntCollector<A, R> collector) {
        if (collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH))
            return (R) collect(collector.supplier(), collector.intAccumulator(), collector.merger());
        return collector.finisher().apply(collect(collector.supplier(), collector.intAccumulator(), collector
                .merger()));
    }

    @Override
    public int sum() {
        return reduce(0, Integer::sum);
    }

    @Override
    public OptionalInt min() {
        return reduce(Integer::min);
    }

    /**
     * Returns the minimum element of this stream according to the provided
     * {@code Comparator}.
     *
     * <p>
     * This is a terminal operation.
     *
     * @param comparator a non-interfering, stateless {@link Comparator} to
     *        compare elements of this stream
     * @return an {@code OptionalInt} describing the minimum element of this
     *         stream, or an empty {@code OptionalInt} if the stream is empty
     * @since 0.1.2
     */
    public OptionalInt min(Comparator<Integer> comparator) {
        return reduce((a, b) -> comparator.compare(a, b) > 0 ? b : a);
    }

    /**
     * Returns the minimum element of this stream according to the provided key
     * extractor function.
     *
     * <p>
     * This is a terminal operation.
     *
     * @param <V> the type of the {@code Comparable} sort key
     * @param keyExtractor a non-interfering, stateless function
     * @return an {@code OptionalInt} describing some element of this stream for
     *         which the lowest value was returned by key extractor, or an empty
     *         {@code OptionalInt} if the stream is empty
     * @since 0.1.2
     */
    public <V extends Comparable<? super V>> OptionalInt minBy(IntFunction<V> keyExtractor) {
        ObjIntBox<V> result = collect(() -> new ObjIntBox<>(null, 0), (box, i) -> {
            V val = Objects.requireNonNull(keyExtractor.apply(i));
            if (box.a == null || box.a.compareTo(val) > 0) {
                box.a = val;
                box.b = i;
            }
        }, (box1, box2) -> {
            if (box2.a != null && (box1.a == null || box1.a.compareTo(box2.a) > 0)) {
                box1.a = box2.a;
                box1.b = box2.b;
            }
        });
        return result.a == null ? OptionalInt.empty() : OptionalInt.of(result.b);
    }

    /**
     * Returns the minimum element of this stream according to the provided key
     * extractor function.
     *
     * <p>
     * This is a terminal operation.
     *
     * @param keyExtractor a non-interfering, stateless function
     * @return an {@code OptionalInt} describing the first element of this
     *         stream for which the lowest value was returned by key extractor,
     *         or an empty {@code OptionalInt} if the stream is empty
     * @since 0.1.2
     */
    public OptionalInt minByInt(IntUnaryOperator keyExtractor) {
        int[] result = collect(() -> new int[3], (acc, i) -> {
            int key = keyExtractor.applyAsInt(i);
            if (acc[2] == 0 || acc[1] > key) {
                acc[0] = i;
                acc[1] = key;
                acc[2] = 1;
            }
        }, (acc1, acc2) -> {
            if (acc2[2] == 1 && (acc1[2] == 0 || acc1[1] > acc2[1]))
                System.arraycopy(acc2, 0, acc1, 0, 3);
        });
        return result[2] == 1 ? OptionalInt.of(result[0]) : OptionalInt.empty();
    }

    /**
     * Returns the minimum element of this stream according to the provided key
     * extractor function.
     *
     * <p>
     * This is a terminal operation.
     *
     * @param keyExtractor a non-interfering, stateless function
     * @return an {@code OptionalInt} describing some element of this stream for
     *         which the lowest value was returned by key extractor, or an empty
     *         {@code OptionalInt} if the stream is empty
     * @since 0.1.2
     */
    public OptionalInt minByLong(IntToLongFunction keyExtractor) {
        return collect(PrimitiveBox::new, (box, i) -> {
            long key = keyExtractor.applyAsLong(i);
            if (!box.b || box.l > key) {
                box.b = true;
                box.l = key;
                box.i = i;
            }
        }, PrimitiveBox.MIN_LONG).asInt();
    }

    /**
     * Returns the minimum element of this stream according to the provided key
     * extractor function.
     *
     * <p>
     * This is a terminal operation.
     *
     * @param keyExtractor a non-interfering, stateless function
     * @return an {@code OptionalInt} describing some element of this stream for
     *         which the lowest value was returned by key extractor, or an empty
     *         {@code OptionalInt} if the stream is empty
     * @since 0.1.2
     */
    public OptionalInt minByDouble(IntToDoubleFunction keyExtractor) {
        return collect(PrimitiveBox::new, (box, i) -> {
            double key = keyExtractor.applyAsDouble(i);
            if (!box.b || Double.compare(box.d, key) > 0) {
                box.b = true;
                box.d = key;
                box.i = i;
            }
        }, PrimitiveBox.MIN_DOUBLE).asInt();
    }

    @Override
    public OptionalInt max() {
        return reduce(Integer::max);
    }

    /**
     * Returns the maximum element of this stream according to the provided
     * {@code Comparator}.
     *
     * <p>
     * This is a terminal operation.
     *
     * @param comparator a non-interfering, stateless {@link Comparator} to
     *        compare elements of this stream
     * @return an {@code OptionalInt} describing the maximum element of this
     *         stream, or an empty {@code OptionalInt} if the stream is empty
     */
    public OptionalInt max(Comparator<Integer> comparator) {
        return reduce((a, b) -> comparator.compare(a, b) >= 0 ? a : b);
    }

    /**
     * Returns the maximum element of this stream according to the provided key
     * extractor function.
     *
     * <p>
     * This is a terminal operation.
     *
     * @param <V> the type of the {@code Comparable} sort key
     * @param keyExtractor a non-interfering, stateless function
     * @return an {@code OptionalInt} describing the first element of this
     *         stream for which the highest value was returned by key extractor,
     *         or an empty {@code OptionalInt} if the stream is empty
     * @since 0.1.2
     */
    public <V extends Comparable<? super V>> OptionalInt maxBy(IntFunction<V> keyExtractor) {
        ObjIntBox<V> result = collect(() -> new ObjIntBox<>(null, 0), (box, i) -> {
            V val = Objects.requireNonNull(keyExtractor.apply(i));
            if (box.a == null || box.a.compareTo(val) < 0) {
                box.a = val;
                box.b = i;
            }
        }, (box1, box2) -> {
            if (box2.a != null && (box1.a == null || box1.a.compareTo(box2.a) < 0)) {
                box1.a = box2.a;
                box1.b = box2.b;
            }
        });
        return result.a == null ? OptionalInt.empty() : OptionalInt.of(result.b);
    }

    /**
     * Returns the maximum element of this stream according to the provided key
     * extractor function.
     *
     * <p>
     * This is a terminal operation.
     *
     * @param keyExtractor a non-interfering, stateless function
     * @return an {@code OptionalInt} describing the first element of this
     *         stream for which the highest value was returned by key extractor,
     *         or an empty {@code OptionalInt} if the stream is empty
     * @since 0.1.2
     */
    public OptionalInt maxByInt(IntUnaryOperator keyExtractor) {
        int[] result = collect(() -> new int[3], (acc, i) -> {
            int key = keyExtractor.applyAsInt(i);
            if (acc[2] == 0 || acc[1] < key) {
                acc[0] = i;
                acc[1] = key;
                acc[2] = 1;
            }
        }, (acc1, acc2) -> {
            if (acc2[2] == 1 && (acc1[2] == 0 || acc1[1] < acc2[1]))
                System.arraycopy(acc2, 0, acc1, 0, 3);
        });
        return result[2] == 1 ? OptionalInt.of(result[0]) : OptionalInt.empty();
    }

    /**
     * Returns the maximum element of this stream according to the provided key
     * extractor function.
     *
     * <p>
     * This is a terminal operation.
     *
     * @param keyExtractor a non-interfering, stateless function
     * @return an {@code OptionalInt} describing the first element of this
     *         stream for which the highest value was returned by key extractor,
     *         or an empty {@code OptionalInt} if the stream is empty
     * @since 0.1.2
     */
    public OptionalInt maxByLong(IntToLongFunction keyExtractor) {
        return collect(PrimitiveBox::new, (box, i) -> {
            long key = keyExtractor.applyAsLong(i);
            if (!box.b || box.l < key) {
                box.b = true;
                box.l = key;
                box.i = i;
            }
        }, PrimitiveBox.MAX_LONG).asInt();
    }

    /**
     * Returns the maximum element of this stream according to the provided key
     * extractor function.
     *
     * <p>
     * This is a terminal operation.
     *
     * @param keyExtractor a non-interfering, stateless function
     * @return an {@code OptionalInt} describing the first element of this
     *         stream for which the highest value was returned by key extractor,
     *         or an empty {@code OptionalInt} if the stream is empty
     * @since 0.1.2
     */
    public OptionalInt maxByDouble(IntToDoubleFunction keyExtractor) {
        return collect(PrimitiveBox::new, (box, i) -> {
            double key = keyExtractor.applyAsDouble(i);
            if (!box.b || Double.compare(box.d, key) < 0) {
                box.b = true;
                box.d = key;
                box.i = i;
            }
        }, PrimitiveBox.MAX_DOUBLE).asInt();
    }

    @Override
    public long count() {
        if (context.fjp != null)
            return context.terminate(stream()::count);
        return stream().count();
    }

    @Override
    public OptionalDouble average() {
        if (context.fjp != null)
            return context.terminate(stream()::average);
        return stream().average();
    }

    @Override
    public IntSummaryStatistics summaryStatistics() {
        return collect(IntSummaryStatistics::new, IntSummaryStatistics::accept, IntSummaryStatistics::combine);
    }

    @Override
    public boolean anyMatch(IntPredicate predicate) {
        if (context.fjp != null)
            return context.terminate(predicate, stream()::anyMatch);
        return stream().anyMatch(predicate);
    }

    @Override
    public boolean allMatch(IntPredicate predicate) {
        if (context.fjp != null)
            return context.terminate(predicate, stream()::allMatch);
        return stream().allMatch(predicate);
    }

    @Override
    public boolean noneMatch(IntPredicate predicate) {
        return !anyMatch(predicate);
    }

    @Override
    public OptionalInt findFirst() {
        if (context.fjp != null)
            return context.terminate(stream()::findFirst);
        return stream().findFirst();
    }

    /**
     * Returns an {@link OptionalInt} describing the first element of this
     * stream, which matches given predicate, or an empty {@code OptionalInt} if
     * there's no matching element.
     *
     * <p>
     * This is a short-circuiting terminal operation.
     *
     * @param predicate a
     *        <a href="package-summary.html#NonInterference">non-interfering
     *        </a>, <a href="package-summary.html#Statelessness">stateless</a>
     *        predicate which returned value should match
     * @return an {@code OptionalInt} describing the first matching element of
     *         this stream, or an empty {@code OptionalInt} if there's no
     *         matching element
     * @see #findFirst()
     */
    public OptionalInt findFirst(IntPredicate predicate) {
        return filter(predicate).findFirst();
    }

    @Override
    public OptionalInt findAny() {
        if (context.fjp != null)
            return context.terminate(stream()::findAny);
        return stream().findAny();
    }

    /**
     * Returns an {@link OptionalInt} describing some element of the stream,
     * which matches given predicate, or an empty {@code OptionalInt} if there's
     * no matching element.
     *
     * <p>
     * This is a short-circuiting terminal operation.
     *
     * <p>
     * The behavior of this operation is explicitly nondeterministic; it is free
     * to select any element in the stream. This is to allow for maximal
     * performance in parallel operations; the cost is that multiple invocations
     * on the same source may not return the same result. (If a stable result is
     * desired, use {@link #findFirst(IntPredicate)} instead.)
     *
     * @param predicate a
     *        <a href="package-summary.html#NonInterference">non-interfering
     *        </a>, <a href="package-summary.html#Statelessness">stateless</a>
     *        predicate which returned value should match
     * @return an {@code OptionalInt} describing some matching element of this
     *         stream, or an empty {@code OptionalInt} if there's no matching
     *         element
     * @see #findAny()
     * @see #findFirst(IntPredicate)
     */
    public OptionalInt findAny(IntPredicate predicate) {
        return filter(predicate).findAny();
    }

    /**
     * Returns an {@link OptionalLong} describing the zero-based index of the
     * first element of this stream, which equals to the given value, or an
     * empty {@code OptionalLong} if there's no matching element.
     *
     * <p>
     * This is a short-circuiting terminal operation.
     *
     * @param value a value to look for
     * @return an {@code OptionalLong} describing the index of the first
     *         matching element of this stream, or an empty {@code OptionalLong}
     *         if there's no matching element.
     * @see #indexOf(IntPredicate)
     * @since 0.4.0
     */
    public OptionalLong indexOf(int value) {
        return boxed().indexOf(i -> i == value);
    }

    /**
     * Returns an {@link OptionalLong} describing the zero-based index of the
     * first element of this stream, which matches given predicate, or an empty
     * {@code OptionalLong} if there's no matching element.
     *
     * <p>
     * This is a short-circuiting terminal operation.
     *
     * @param predicate a
     *        <a href="package-summary.html#NonInterference">non-interfering
     *        </a>, <a href="package-summary.html#Statelessness">stateless</a>
     *        predicate which returned value should match
     * @return an {@code OptionalLong} describing the index of the first
     *         matching element of this stream, or an empty {@code OptionalLong}
     *         if there's no matching element.
     * @see #findFirst(IntPredicate)
     * @since 0.4.0
     */
    public OptionalLong indexOf(IntPredicate predicate) {
        return boxed().indexOf(predicate::test);
    }

    @Override
    public LongStreamEx asLongStream() {
        return new LongStreamEx(stream().asLongStream(), context);
    }

    @Override
    public DoubleStreamEx asDoubleStream() {
        return new DoubleStreamEx(stream().asDoubleStream(), context);
    }

    @Override
    public StreamEx<Integer> boxed() {
        return new StreamEx<>(stream().boxed(), context);
    }

    @Override
    public IntStreamEx sequential() {
        return (IntStreamEx) super.sequential();
    }

    @Override
    public IntStreamEx parallel() {
        return (IntStreamEx) super.parallel();
    }

    @Override
    public IntStreamEx parallel(ForkJoinPool fjp) {
        return (IntStreamEx) super.parallel(fjp);
    }

    @Override
    public OfInt iterator() {
        return Spliterators.iterator(spliterator());
    }

    /**
     * Returns a new {@code IntStreamEx} which is a concatenation of this stream
     * and the stream containing supplied values
     * 
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a>.
     * 
     * @param values the values to append to the stream
     * @return the new stream
     */
    public IntStreamEx append(int... values) {
        if (values.length == 0)
            return this;
        return new IntStreamEx(IntStream.concat(stream(), IntStream.of(values)), context);
    }

    /**
     * Creates a lazily concatenated stream whose elements are all the elements
     * of this stream followed by all the elements of the other stream. The
     * resulting stream is ordered if both of the input streams are ordered, and
     * parallel if either of the input streams is parallel. When the resulting
     * stream is closed, the close handlers for both input streams are invoked.
     *
     * @param other the other stream
     * @return this stream appended by the other stream
     * @see IntStream#concat(IntStream, IntStream)
     */
    public IntStreamEx append(IntStream other) {
        return new IntStreamEx(IntStream.concat(stream(), other), context.combine(other));
    }

    /**
     * Returns a new {@code IntStreamEx} which is a concatenation of the stream
     * containing supplied values and this stream
     * 
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a>.
     * 
     * @param values the values to prepend to the stream
     * @return the new stream
     */
    public IntStreamEx prepend(int... values) {
        if (values.length == 0)
            return this;
        return new IntStreamEx(IntStream.concat(IntStream.of(values), stream()), context);
    }

    /**
     * Creates a lazily concatenated stream whose elements are all the elements
     * of the other stream followed by all the elements of this stream. The
     * resulting stream is ordered if both of the input streams are ordered, and
     * parallel if either of the input streams is parallel. When the resulting
     * stream is closed, the close handlers for both input streams are invoked.
     *
     * @param other the other stream
     * @return this stream prepended by the other stream
     * @see IntStream#concat(IntStream, IntStream)
     */
    public IntStreamEx prepend(IntStream other) {
        return new IntStreamEx(IntStream.concat(other, stream()), context.combine(other));
    }

    /**
     * Returns an object-valued {@link StreamEx} consisting of the elements of
     * given array corresponding to the indices which appear in this stream.
     *
     * <p>
     * This is an intermediate operation.
     *
     * @param <U> the element type of the new stream
     * @param array the array to take the elements from
     * @return the new stream
     * @since 0.1.2
     */
    public <U> StreamEx<U> elements(U[] array) {
        return mapToObj(idx -> array[idx]);
    }

    /**
     * Returns an object-valued {@link StreamEx} consisting of the elements of
     * given {@link List} corresponding to the indices which appear in this
     * stream.
     *
     * <p>
     * The list elements are accessed using {@link List#get(int)}, so the list
     * should provide fast random access. The list is assumed to be unmodifiable
     * during the stream operations.
     * 
     * <p>
     * This is an intermediate operation.
     *
     * @param <U> the element type of the new stream
     * @param list the list to take the elements from
     * @return the new stream
     * @since 0.1.2
     */
    public <U> StreamEx<U> elements(List<U> list) {
        return mapToObj(list::get);
    }

    /**
     * Returns an {@link IntStreamEx} consisting of the elements of given array
     * corresponding to the indices which appear in this stream.
     *
     * <p>
     * This is an intermediate operation.
     *
     * @param array the array to take the elements from
     * @return the new stream
     * @since 0.1.2
     */
    public IntStreamEx elements(int[] array) {
        return map(idx -> array[idx]);
    }

    /**
     * Returns a {@link LongStreamEx} consisting of the elements of given array
     * corresponding to the indices which appear in this stream.
     *
     * <p>
     * This is an intermediate operation.
     *
     * @param array the array to take the elements from
     * @return the new stream
     * @since 0.1.2
     */
    public LongStreamEx elements(long[] array) {
        return mapToLong(idx -> array[idx]);
    }

    /**
     * Returns a {@link DoubleStreamEx} consisting of the elements of given
     * array corresponding to the indices which appear in this stream.
     *
     * <p>
     * This is an intermediate operation.
     *
     * @param array the array to take the elements from
     * @return the new stream
     * @since 0.1.2
     */
    public DoubleStreamEx elements(double[] array) {
        return mapToDouble(idx -> array[idx]);
    }

    /**
     * Returns a {@link String} consisting of chars from this stream.
     * 
     * <p>
     * This is a terminal operation.
     * 
     * <p>
     * During string creation stream elements are converted to chars using
     * {@code (char)} cast operation.
     * 
     * @return a new {@code String}
     * @since 0.2.1
     */
    public String charsToString() {
        return collect(StringBuilder::new, (sb, c) -> sb.append((char) c), StringBuilder::append).toString();
    }

    /**
     * Returns a {@link String} consisting of code points from this stream.
     * 
     * <p>
     * This is a terminal operation.
     * 
     * @return a new {@code String}
     * @since 0.2.1
     */
    public String codePointsToString() {
        return collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString();
    }

    /**
     * Returns a stream consisting of the results of applying the given function
     * to the every adjacent pair of elements of this stream.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a>.
     * 
     * <p>
     * The output stream will contain one element less than this stream. If this
     * stream contains zero or one element the output stream will be empty.
     *
     * @param mapper a non-interfering, stateless function to apply to each
     *        adjacent pair of this stream elements.
     * @return the new stream
     * @since 0.2.1
     */
    public IntStreamEx pairMap(IntBinaryOperator mapper) {
        return delegate(new PairSpliterator.PSOfInt(mapper, null, spliterator(), PairSpliterator.MODE_PAIRS));
    }

    /**
     * Returns a {@link String} which is the concatenation of the results of
     * calling {@link String#valueOf(int)} on each element of this stream,
     * separated by the specified delimiter, in encounter order.
     *
     * <p>
     * This is a terminal operation.
     * 
     * @param delimiter the delimiter to be used between each element
     * @return the result of concatenation. For empty input stream empty String
     *         is returned.
     * @since 0.3.1
     */
    public String joining(CharSequence delimiter) {
        return collect(IntCollector.joining(delimiter));
    }

    /**
     * Returns a {@link String} which is the concatenation of the results of
     * calling {@link String#valueOf(int)} on each element of this stream,
     * separated by the specified delimiter, with the specified prefix and
     * suffix in encounter order.
     *
     * <p>
     * This is a terminal operation.
     * 
     * @param delimiter the delimiter to be used between each element
     * @param prefix the sequence of characters to be used at the beginning of
     *        the joined result
     * @param suffix the sequence of characters to be used at the end of the
     *        joined result
     * @return the result of concatenation. For empty input stream
     *         {@code prefix + suffix} is returned.
     * @since 0.3.1
     */
    public String joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) {
        return collect(IntCollector.joining(delimiter, prefix, suffix));
    }

    /**
     * Returns a stream consisting of all elements from this stream until the
     * first element which does not match the given predicate is found.
     * 
     * <p>
     * This is a short-circuiting stateful operation. It can be either
     * <a href="package-summary.html#StreamOps">intermediate or
     * quasi-intermediate</a>. When using with JDK 1.9 or higher it calls the
     * corresponding JDK 1.9 implementation. When using with JDK 1.8 it uses own
     * implementation.
     * 
     * <p>
     * While this operation is quite cheap for sequential stream, it can be
     * quite expensive on parallel pipelines.
     * 
     * @param predicate a non-interfering, stateless predicate to apply to
     *        elements.
     * @return the new stream.
     * @since 0.3.6
     * @see #takeWhileInclusive(IntPredicate)
     * @see #dropWhile(IntPredicate)
     */
    public IntStreamEx takeWhile(IntPredicate predicate) {
        return VER_SPEC.callWhile(this, Objects.requireNonNull(predicate), false);
    }

    /**
     * Returns a stream consisting of all elements from this stream until the
     * first element which does not match the given predicate is found
     * (including the first mismatching element).
     * 
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a>.
     * 
     * <p>
     * While this operation is quite cheap for sequential stream, it can be
     * quite expensive on parallel pipelines.
     * 
     * @param predicate a non-interfering, stateless predicate to apply to
     *        elements.
     * @return the new stream.
     * @since 0.5.5
     * @see #takeWhile(IntPredicate)
     */
    public IntStreamEx takeWhileInclusive(IntPredicate predicate) {
        Objects.requireNonNull(predicate);
        return delegate(new TakeDrop.TDOfInt(spliterator(), false, true, predicate));
    }

    /**
     * Returns a stream consisting of all elements from this stream starting
     * from the first element which does not match the given predicate. If the
     * predicate is true for all stream elements, an empty stream is returned.
     * 
     * <p>
     * This is a stateful operation. It can be either
     * <a href="package-summary.html#StreamOps">intermediate or
     * quasi-intermediate</a>. When using with JDK 1.9 or higher it calls the
     * corresponding JDK 1.9 implementation. When using with JDK 1.8 it uses own
     * implementation.
     * 
     * <p>
     * While this operation is quite cheap for sequential stream, it can be
     * quite expensive on parallel pipelines.
     * 
     * @param predicate a non-interfering, stateless predicate to apply to
     *        elements.
     * @return the new stream.
     * @since 0.3.6
     */
    public IntStreamEx dropWhile(IntPredicate predicate) {
        return VER_SPEC.callWhile(this, Objects.requireNonNull(predicate), true);
    }

    /**
     * Returns a stream where the first element is the replaced with the result
     * of applying the given function while the other elements are left intact.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a>.
     *
     * @param mapper a
     *        <a href="package-summary.html#NonInterference">non-interfering
     *        </a>, <a href="package-summary.html#Statelessness">stateless</a>
     *        function to apply to the first element
     * @return the new stream
     * @since 0.4.1
     */
    public IntStreamEx mapFirst(IntUnaryOperator mapper) {
        return delegate(new PairSpliterator.PSOfInt((a, b) -> b, mapper, spliterator(),
                PairSpliterator.MODE_MAP_FIRST));
    }

    /**
     * Returns a stream where the last element is the replaced with the result
     * of applying the given function while the other elements are left intact.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a>.
     *
     * <p>
     * The mapper function is called at most once. It could be not called at all
     * if the stream is empty or there is short-circuiting operation downstream.
     *
     * @param mapper a
     *        <a href="package-summary.html#NonInterference">non-interfering
     *        </a>, <a href="package-summary.html#Statelessness">stateless</a>
     *        function to apply to the last element
     * @return the new stream
     * @since 0.4.1
     */
    public IntStreamEx mapLast(IntUnaryOperator mapper) {
        return delegate(new PairSpliterator.PSOfInt((a, b) -> a, mapper, spliterator(), PairSpliterator.MODE_MAP_LAST));
    }

    /**
     * Returns a stream containing cumulative results of applying the
     * accumulation function going left to right.
     * 
     * <p>
     * This is a stateful
     * <a href="package-summary.html#StreamOps">quasi-intermediate</a>
     * operation.
     *
     * <p>
     * This operation resembles {@link #scanLeft(IntBinaryOperator)}, but unlike
     * {@code scanLeft} this operation is intermediate and accumulation function
     * must be associative.
     * 
     * <p>
     * This method cannot take all the advantages of parallel streams as it must
     * process elements strictly left to right. Using an unordered source or
     * removing the ordering constraint with {@link #unordered()} may improve
     * the parallel processing speed.
     *
     * @param op an <a href="package-summary.html#Associativity">associative</a>
     *        , <a href="package-summary.html#NonInterference">non-interfering
     *        </a>, <a href="package-summary.html#Statelessness">stateless</a>
     *        function for computing the next element based on the previous one
     * @return the new stream.
     * @see #scanLeft(IntBinaryOperator)
     * @since 0.6.1
     */
    public IntStreamEx prefix(IntBinaryOperator op) {
        return delegate(new PrefixOps.OfInt(spliterator(), op));
    }

    // Necessary to generate proper JavaDoc
    // does not add overhead as it appears in bytecode anyways as bridge method
    @Override
    public <U> U chain(Function<? super IntStreamEx, U> mapper) {
        return mapper.apply(this);
    }

    /**
     * Returns an empty sequential {@code IntStreamEx}.
     *
     * @return an empty sequential stream
     */
    public static IntStreamEx empty() {
        return of(Spliterators.emptyIntSpliterator());
    }

    /**
     * Returns a sequential {@code IntStreamEx} containing a single element.
     *
     * @param element the single element
     * @return a singleton sequential stream
     */
    public static IntStreamEx of(int element) {
        return of(new ConstSpliterator.OfInt(element, 1, true));
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} whose elements are the
     * specified values.
     *
     * @param elements the elements of the new stream
     * @return the new stream
     */
    public static IntStreamEx of(int... elements) {
        return of(Arrays.spliterator(elements));
    }

    /**
     * Returns a sequential {@link IntStreamEx} with the specified range of the
     * specified array as its source.
     *
     * @param array the array, assumed to be unmodified during use
     * @param startInclusive the first index to cover, inclusive
     * @param endExclusive index immediately past the last index to cover
     * @return an {@code IntStreamEx} for the array range
     * @throws ArrayIndexOutOfBoundsException if {@code startInclusive} is
     *         negative, {@code endExclusive} is less than
     *         {@code startInclusive}, or {@code endExclusive} is greater than
     *         the array size
     * @since 0.1.1
     * @see Arrays#stream(int[], int, int)
     */
    public static IntStreamEx of(int[] array, int startInclusive, int endExclusive) {
        return of(Arrays.spliterator(array, startInclusive, endExclusive));
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} whose elements are the
     * specified values casted to int.
     *
     * @param elements the elements of the new stream
     * @return the new stream
     * @since 0.2.0
     */
    public static IntStreamEx of(byte... elements) {
        return of(elements, 0, elements.length);
    }

    /**
     * Returns a sequential {@link IntStreamEx} with the specified range of the
     * specified array as its source. Array values will be casted to int.
     *
     * @param array the array, assumed to be unmodified during use
     * @param startInclusive the first index to cover, inclusive
     * @param endExclusive index immediately past the last index to cover
     * @return an {@code IntStreamEx} for the array range
     * @throws ArrayIndexOutOfBoundsException if {@code startInclusive} is
     *         negative, {@code endExclusive} is less than
     *         {@code startInclusive}, or {@code endExclusive} is greater than
     *         the array size
     * @since 0.2.0
     */
    public static IntStreamEx of(byte[] array, int startInclusive, int endExclusive) {
        rangeCheck(array.length, startInclusive, endExclusive);
        return of(new RangeBasedSpliterator.OfByte(startInclusive, endExclusive, array));
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} whose elements are the
     * specified values casted to int.
     *
     * @param elements the elements of the new stream
     * @return the new stream
     * @since 0.2.0
     */
    public static IntStreamEx of(char... elements) {
        return of(elements, 0, elements.length);
    }

    /**
     * Returns a sequential {@link IntStreamEx} with the specified range of the
     * specified array as its source. Array values will be casted to int.
     *
     * @param array the array, assumed to be unmodified during use
     * @param startInclusive the first index to cover, inclusive
     * @param endExclusive index immediately past the last index to cover
     * @return an {@code IntStreamEx} for the array range
     * @throws ArrayIndexOutOfBoundsException if {@code startInclusive} is
     *         negative, {@code endExclusive} is less than
     *         {@code startInclusive}, or {@code endExclusive} is greater than
     *         the array size
     * @since 0.2.0
     */
    public static IntStreamEx of(char[] array, int startInclusive, int endExclusive) {
        rangeCheck(array.length, startInclusive, endExclusive);
        return of(new RangeBasedSpliterator.OfChar(startInclusive, endExclusive, array));
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} whose elements are the
     * specified values casted to int.
     *
     * @param elements the elements of the new stream
     * @return the new stream
     * @since 0.2.0
     */
    public static IntStreamEx of(short... elements) {
        return of(elements, 0, elements.length);
    }

    /**
     * Returns a sequential {@link IntStreamEx} with the specified range of the
     * specified array as its source. Array values will be casted to int.
     *
     * @param array the array, assumed to be unmodified during use
     * @param startInclusive the first index to cover, inclusive
     * @param endExclusive index immediately past the last index to cover
     * @return an {@code IntStreamEx} for the array range
     * @throws ArrayIndexOutOfBoundsException if {@code startInclusive} is
     *         negative, {@code endExclusive} is less than
     *         {@code startInclusive}, or {@code endExclusive} is greater than
     *         the array size
     * @since 0.2.0
     */
    public static IntStreamEx of(short[] array, int startInclusive, int endExclusive) {
        rangeCheck(array.length, startInclusive, endExclusive);
        return of(new RangeBasedSpliterator.OfShort(startInclusive, endExclusive, array));
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} backed by the content of
     * given {@link InputStream}.
     * 
     * <p>
     * The resulting stream contains int values between 0 and 255 (0xFF)
     * inclusive, as they are returned by the {@link InputStream#read()} method.
     * If you want to get <code>byte</code> values (e.g. -1 instead of 255),
     * simply cast the stream elements like <code>.map(b -&gt; (byte)b)</code>.
     * The terminal -1 value is excluded from the resulting stream.
     * 
     * <p>
     * If the underlying {@code InputStream} throws an {@link IOException}
     * during the stream traversal, it will be rethrown as
     * {@link UncheckedIOException}.
     * 
     * <p>
     * When the returned {@code IntStreamEx} is closed the original
     * {@code InputStream} is closed as well. If {@link InputStream#close()}
     * method throws an {@code IOException}, it will be rethrown as
     * {@link UncheckedIOException}.
     * 
     * @param is an {@code InputStream} to create an {@code IntStreamEx} on.
     * @return the new stream
     * @see #asByteInputStream()
     * @since 0.6.1
     */
    public static IntStreamEx of(InputStream is) {
        Spliterator.OfInt spliterator = new AbstractIntSpliterator(Long.MAX_VALUE, Spliterator.ORDERED
            | Spliterator.NONNULL) {
            @Override
            public boolean tryAdvance(IntConsumer action) {
                try {
                    int next = is.read();
                    if (next == -1)
                        return false;
                    action.accept(next);
                    return true;
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            }
        };
        return of(spliterator).onClose(() -> {
            try {
                is.close();
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        });
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} whose elements are the
     * unboxed elements of supplied array.
     *
     * @param array the array to create the stream from.
     * @return the new stream
     * @see Arrays#stream(Object[])
     * @since 0.5.0
     */
    public static IntStreamEx of(Integer[] array) {
        return seq(Arrays.stream(array).mapToInt(Integer::intValue));
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} whose elements are the
     * values in the supplied {@link java.nio.IntBuffer}.
     * 
     * <p>
     * The resulting stream covers only a portion of {@code IntBuffer} content
     * which starts with {@linkplain Buffer#position() position} (inclusive) and
     * ends with {@linkplain Buffer#limit() limit} (exclusive). Changes in
     * position and limit after the stream creation don't affect the stream.
     * 
     * <p>
     * The resulting stream does not change the internal {@code IntBuffer}
     * state.
     * 
     * @param buf the {@code IntBuffer} to create a stream from
     * @return the new stream
     * @since 0.6.2
     */
    public static IntStreamEx of(java.nio.IntBuffer buf) {
        return range(buf.position(), buf.limit()).map(buf::get);
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} containing all the
     * indices of the supplied list.
     *
     * @param <T> list element type
     * @param list list to get the stream of its indices
     * @return a sequential {@code IntStreamEx} for the range of {@code int}
     *         elements starting from 0 to (not inclusive) list.size()
     * @since 0.1.1
     */
    public static <T> IntStreamEx ofIndices(List<T> list) {
        return range(list.size());
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} containing all the
     * indices of the supplied list elements which match given predicate.
     * 
     * <p>
     * The list elements are accessed using {@link List#get(int)}, so the list
     * should provide fast random access. The list is assumed to be unmodifiable
     * during the stream operations.
     *
     * @param <T> list element type
     * @param list list to get the stream of its indices
     * @param predicate a predicate to test list elements
     * @return a sequential {@code IntStreamEx} of the matched list indices
     * @since 0.1.1
     */
    public static <T> IntStreamEx ofIndices(List<T> list, Predicate<T> predicate) {
        return seq(IntStream.range(0, list.size()).filter(i -> predicate.test(list.get(i))));
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} containing all the
     * indices of the supplied array.
     *
     * @param <T> array element type
     * @param array array to get the stream of its indices
     * @return a sequential {@code IntStreamEx} for the range of {@code int}
     *         elements starting from 0 to (not inclusive) array.length
     * @since 0.1.1
     */
    public static <T> IntStreamEx ofIndices(T[] array) {
        return range(array.length);
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} containing all the
     * indices of the supplied array elements which match given predicate.
     *
     * @param <T> array element type
     * @param array array to get the stream of its indices
     * @param predicate a predicate to test array elements
     * @return a sequential {@code IntStreamEx} of the matched array indices
     * @since 0.1.1
     */
    public static <T> IntStreamEx ofIndices(T[] array, Predicate<T> predicate) {
        return seq(IntStream.range(0, array.length).filter(i -> predicate.test(array[i])));
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} containing all the
     * indices of supplied array.
     *
     * @param array array to get the stream of its indices
     * @return a sequential {@code IntStreamEx} for the range of {@code int}
     *         elements starting from 0 to (not inclusive) array.length
     * @since 0.1.1
     */
    public static IntStreamEx ofIndices(int[] array) {
        return range(array.length);
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} containing all the
     * indices of the supplied array elements which match given predicate.
     *
     * @param array array to get the stream of its indices
     * @param predicate a predicate to test array elements
     * @return a sequential {@code IntStreamEx} of the matched array indices
     * @since 0.1.1
     */
    public static IntStreamEx ofIndices(int[] array, IntPredicate predicate) {
        return seq(IntStream.range(0, array.length).filter(i -> predicate.test(array[i])));
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} containing all the
     * indices of supplied array.
     *
     * @param array array to get the stream of its indices
     * @return a sequential {@code IntStreamEx} for the range of {@code int}
     *         elements starting from 0 to (not inclusive) array.length
     * @since 0.1.1
     */
    public static IntStreamEx ofIndices(long[] array) {
        return range(array.length);
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} containing all the
     * indices of the supplied array elements which match given predicate.
     *
     * @param array array to get the stream of its indices
     * @param predicate a predicate to test array elements
     * @return a sequential {@code IntStreamEx} of the matched array indices
     * @since 0.1.1
     */
    public static IntStreamEx ofIndices(long[] array, LongPredicate predicate) {
        return seq(IntStream.range(0, array.length).filter(i -> predicate.test(array[i])));
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} containing all the
     * indices of supplied array.
     *
     * @param array array to get the stream of its indices
     * @return a sequential {@code IntStreamEx} for the range of {@code int}
     *         elements starting from 0 to (not inclusive) array.length
     * @since 0.1.1
     */
    public static IntStreamEx ofIndices(double[] array) {
        return range(array.length);
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} containing all the
     * indices of the supplied array elements which match given predicate.
     *
     * @param array array to get the stream of its indices
     * @param predicate a predicate to test array elements
     * @return a sequential {@code IntStreamEx} of the matched array indices
     * @since 0.1.1
     */
    public static IntStreamEx ofIndices(double[] array, DoublePredicate predicate) {
        return seq(IntStream.range(0, array.length).filter(i -> predicate.test(array[i])));
    }

    /**
     * Returns an {@code IntStreamEx} object which wraps given {@link IntStream}
     * .
     * 
     * <p>
     * The supplied stream must not be consumed or closed when this method is
     * called. No operation must be performed on the supplied stream after it's
     * wrapped.
     * 
     * @param stream original stream
     * @return the wrapped stream
     * @since 0.0.8
     */
    public static IntStreamEx of(IntStream stream) {
        return stream instanceof IntStreamEx ? (IntStreamEx) stream : new IntStreamEx(stream, StreamContext.of(stream));
    }

    /**
     * Returns a sequential {@link IntStreamEx} created from given
     * {@link java.util.Spliterator.OfInt}.
     * 
     * @param spliterator a spliterator to create the stream from.
     * @return the new stream
     * @since 0.3.4
     */
    public static IntStreamEx of(Spliterator.OfInt spliterator) {
        return new IntStreamEx(spliterator, StreamContext.SEQUENTIAL);
    }

    /**
     * Returns a sequential, ordered {@link IntStreamEx} created from given
     * {@link java.util.PrimitiveIterator.OfInt}.
     *
     * <p>
     * This method is roughly equivalent to
     * {@code IntStreamEx.of(Spliterators.spliteratorUnknownSize(iterator, ORDERED))}
     * , but may show better performance for parallel processing.
     * 
     * <p>
     * Use this method only if you cannot provide better Stream source.
     *
     * @param iterator an iterator to create the stream from.
     * @return the new stream
     * @since 0.5.1
     */
    public static IntStreamEx of(PrimitiveIterator.OfInt iterator) {
        return of(new UnknownSizeSpliterator.USOfInt(iterator));
    }

    /**
     * Returns a sequential {@code IntStreamEx} containing an
     * {@link OptionalInt} value, if present, otherwise returns an empty
     * {@code IntStreamEx}.
     *
     * @param optional the optional to create a stream of
     * @return a stream with an {@code OptionalInt} value if present, otherwise
     *         an empty stream
     * @since 0.1.1
     */
    public static IntStreamEx of(OptionalInt optional) {
        return optional.isPresent() ? of(optional.getAsInt()) : empty();
    }

    /**
     * Returns an {@code IntStreamEx} of indices for which the specified
     * {@link BitSet} contains a bit in the set state. The indices are returned
     * in order, from lowest to highest. The size of the stream is the number of
     * bits in the set state, equal to the value returned by the
     * {@link BitSet#cardinality()} method.
     *
     * <p>
     * The bit set must remain constant during the execution of the terminal
     * stream operation. Otherwise, the result of the terminal stream operation
     * is undefined.
     *
     * @param bitSet a {@link BitSet} to produce the stream from
     * @return a stream of integers representing set indices
     * @see BitSet#stream()
     */
    public static IntStreamEx of(BitSet bitSet) {
        return seq(bitSet.stream());
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} whose elements are the
     * unboxed elements of supplied collection.
     *
     * @param collection the collection to create the stream from.
     * @return the new stream
     * @see Collection#stream()
     */
    public static IntStreamEx of(Collection<Integer> collection) {
        return seq(collection.stream().mapToInt(Integer::intValue));
    }

    /**
     * Returns an effectively unlimited stream of pseudorandom {@code int}
     * values produced by given {@link Random} object.
     *
     * <p>
     * A pseudorandom {@code int} value is generated as if it's the result of
     * calling the method {@link Random#nextInt()}.
     *
     * @param random a {@link Random} object to produce the stream from
     * @return a stream of pseudorandom {@code int} values
     * @see Random#ints()
     */
    public static IntStreamEx of(Random random) {
        return seq(random.ints());
    }

    /**
     * Returns a stream producing the given {@code streamSize} number of
     * pseudorandom {@code int} values.
     *
     * <p>
     * A pseudorandom {@code int} value is generated as if it's the result of
     * calling the method {@link Random#nextInt()}.
     *
     * @param random a {@link Random} object to produce the stream from
     * @param streamSize the number of values to generate
     * @return a stream of pseudorandom {@code int} values
     * @see Random#ints(long)
     */
    public static IntStreamEx of(Random random, long streamSize) {
        return seq(random.ints(streamSize));
    }

    /**
     * Returns an effectively unlimited stream of pseudorandom {@code int}
     * values, each conforming to the given origin (inclusive) and bound
     * (exclusive).
     *
     * @param random a {@link Random} object to produce the stream from
     * @param randomNumberOrigin the origin (inclusive) of each random value
     * @param randomNumberBound the bound (exclusive) of each random value
     * @return a stream of pseudorandom {@code int} values
     * @see Random#ints(long, int, int)
     */
    public static IntStreamEx of(Random random, int randomNumberOrigin, int randomNumberBound) {
        return seq(random.ints(randomNumberOrigin, randomNumberBound));
    }

    /**
     * Returns a stream producing the given {@code streamSize} number of
     * pseudorandom {@code int} values, each conforming to the given origin
     * (inclusive) and bound (exclusive).
     *
     * @param random a {@link Random} object to produce the stream from
     * @param streamSize the number of values to generate
     * @param randomNumberOrigin the origin (inclusive) of each random value
     * @param randomNumberBound the bound (exclusive) of each random value
     * @return a stream of pseudorandom {@code int} values
     * @see Random#ints(long, int, int)
     */
    public static IntStreamEx of(Random random, long streamSize, int randomNumberOrigin, int randomNumberBound) {
        return seq(random.ints(streamSize, randomNumberOrigin, randomNumberBound));
    }

    /**
     * Returns an {@code IntStreamEx} of {@code int} zero-extending the
     * {@code char} values from the supplied {@link CharSequence}. Any char
     * which maps to a surrogate code point is passed through uninterpreted.
     *
     * <p>
     * If the sequence is mutated while the stream is being read, the result is
     * undefined.
     *
     * @param seq sequence to read characters from
     * @return an IntStreamEx of char values from the sequence
     * @see CharSequence#chars()
     */
    public static IntStreamEx ofChars(CharSequence seq) {
        return of(VER_SPEC.ofChars(seq));
    }

    /**
     * Returns an {@code IntStreamEx} of code point values from the supplied
     * {@link CharSequence}. Any surrogate pairs encountered in the sequence are
     * combined as if by {@linkplain Character#toCodePoint
     * Character.toCodePoint} and the result is passed to the stream. Any other
     * code units, including ordinary BMP characters, unpaired surrogates, and
     * undefined code units, are zero-extended to {@code int} values which are
     * then passed to the stream.
     *
     * <p>
     * If the sequence is mutated while the stream is being read, the result is
     * undefined.
     *
     * @param seq sequence to read code points from
     * @return an IntStreamEx of Unicode code points from this sequence
     * @see CharSequence#codePoints()
     */
    public static IntStreamEx ofCodePoints(CharSequence seq) {
        return of(seq.codePoints());
    }

    /**
     * Returns an infinite sequential ordered {@code IntStreamEx} produced by
     * iterative application of a function {@code f} to an initial element
     * {@code seed}, producing a stream consisting of {@code seed},
     * {@code f(seed)}, {@code f(f(seed))}, etc.
     *
     * <p>
     * The first element (position {@code 0}) in the {@code IntStreamEx} will be
     * the provided {@code seed}. For {@code n > 0}, the element at position
     * {@code n}, will be the result of applying the function {@code f} to the
     * element at position {@code n - 1}.
     *
     * @param seed the initial element
     * @param f a function to be applied to to the previous element to produce a
     *        new element
     * @return A new sequential {@code IntStream}
     * @see #iterate(int, IntPredicate, IntUnaryOperator)
     */
    public static IntStreamEx iterate(final int seed, final IntUnaryOperator f) {
        return iterate(seed, x -> true, f);
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} produced by iterative
     * application of a function to an initial element, conditioned on
     * satisfying the supplied predicate. The stream terminates as soon as the
     * predicate function returns false.
     *
     * <p>
     * {@code IntStreamEx.iterate} should produce the same sequence of elements
     * as produced by the corresponding for-loop:
     * 
     * <pre>{@code
     *     for (int index=seed; predicate.test(index); index = f.apply(index)) { 
     *         ... 
     *     }
     * }</pre>
     *
     * <p>
     * The resulting sequence may be empty if the predicate does not hold on the
     * seed value. Otherwise the first element will be the supplied seed value,
     * the next element (if present) will be the result of applying the function
     * f to the seed value, and so on iteratively until the predicate indicates
     * that the stream should terminate.
     *
     * @param seed the initial element
     * @param predicate a predicate to apply to elements to determine when the
     *        stream must terminate.
     * @param f a function to be applied to the previous element to produce a
     *        new element
     * @return a new sequential {@code IntStreamEx}
     * @see #iterate(int, IntUnaryOperator)
     * @since 0.6.0
     */
    public static IntStreamEx iterate(int seed, IntPredicate predicate, IntUnaryOperator f) {
        Objects.requireNonNull(f);
        Objects.requireNonNull(predicate);
        Spliterator.OfInt spliterator = new Spliterators.AbstractIntSpliterator(Long.MAX_VALUE, Spliterator.ORDERED
            | Spliterator.IMMUTABLE | Spliterator.NONNULL) {
            int prev;
            boolean started, finished;

            @Override
            public boolean tryAdvance(IntConsumer action) {
                Objects.requireNonNull(action);
                if (finished)
                    return false;
                int t;
                if (started)
                    t = f.applyAsInt(prev);
                else {
                    t = seed;
                    started = true;
                }
                if (!predicate.test(t)) {
                    finished = true;
                    return false;
                }
                action.accept(prev = t);
                return true;
            }

            @Override
            public void forEachRemaining(IntConsumer action) {
                Objects.requireNonNull(action);
                if (finished)
                    return;
                finished = true;
                int t = started ? f.applyAsInt(prev) : seed;
                while (predicate.test(t)) {
                    action.accept(t);
                    t = f.applyAsInt(t);
                }
            }
        };
        return of(spliterator);
    }

    /**
     * Returns an infinite sequential unordered stream where each element is
     * generated by the provided {@code IntSupplier}. This is suitable for
     * generating constant streams, streams of random elements, etc.
     *
     * @param s the {@code IntSupplier} for generated elements
     * @return a new infinite sequential unordered {@code IntStreamEx}
     * @see IntStream#generate(IntSupplier)
     */
    public static IntStreamEx generate(IntSupplier s) {
        return seq(IntStream.generate(s));
    }

    /**
     * Return an ordered stream produced by consecutive calls of the supplied
     * producer until it returns false.
     * 
     * <p>
     * The producer function may call the passed consumer any number of times
     * and return true if the producer should be called again or false
     * otherwise. It's guaranteed that the producer will not be called anymore,
     * once it returns false.
     * 
     * <p>
     * This method is particularly useful when producer changes the mutable
     * object which should be left in known state after the full stream
     * consumption. Note however that if a short-circuiting operation is used,
     * then the final state of the mutable object cannot be guaranteed.
     * 
     * @param producer a predicate which calls the passed consumer to emit
     *        stream element(s) and returns true if it producer should be
     *        applied again.
     * @return the new stream
     * @since 0.6.0
     */
    public static IntStreamEx produce(Predicate<IntConsumer> producer) {
        Box<IntEmitter> box = new Box<>();
        return (box.a = action -> producer.test(action) ? box.a : null).stream();
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} from 0 (inclusive) to
     * {@code Integer.MAX_VALUE} (exclusive) by an incremental step of {@code 1}
     * .
     *
     * @return a sequential {@code IntStreamEx} for the range of {@code int}
     *         elements
     * @see #range(int, int)
     * @since 0.5.5
     */
    public static IntStreamEx ints() {
        return seq(IntStream.range(0, Integer.MAX_VALUE));
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} from 0 (inclusive) to
     * {@code endExclusive} (exclusive) by an incremental step of {@code 1}.
     *
     * @param endExclusive the exclusive upper bound
     * @return a sequential {@code IntStreamEx} for the range of {@code int}
     *         elements
     * @see #range(int, int)
     * @since 0.1.1
     */
    public static IntStreamEx range(int endExclusive) {
        return seq(IntStream.range(0, endExclusive));
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} from
     * {@code startInclusive} (inclusive) to {@code endExclusive} (exclusive) by
     * an incremental step of {@code 1}.
     *
     * @param startInclusive the (inclusive) initial value
     * @param endExclusive the exclusive upper bound
     * @return a sequential {@code IntStreamEx} for the range of {@code int}
     *         elements
     * @see IntStream#range(int, int)
     */
    public static IntStreamEx range(int startInclusive, int endExclusive) {
        return seq(IntStream.range(startInclusive, endExclusive));
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} from
     * {@code startInclusive} (inclusive) to {@code endExclusive} (exclusive) by
     * the specified incremental step. The negative step values are also
     * supported. In this case the {@code startInclusive} should be greater than
     * {@code endExclusive}.
     *
     * @param startInclusive the (inclusive) initial value
     * @param endExclusive the exclusive upper bound
     * @param step the non-zero value which designates the difference between
     *        the consecutive values of the resulting stream.
     * @return a sequential {@code IntStreamEx} for the range of {@code int}
     *         elements
     * @throws IllegalArgumentException if step is zero
     * @see IntStreamEx#range(int, int)
     * @since 0.4.0
     */
    public static IntStreamEx range(int startInclusive, int endExclusive, int step) {
        int endInclusive = endExclusive - Integer.signum(step);
        if (endInclusive > endExclusive && step > 0 || endInclusive < endExclusive && step < 0)
            return empty();
        return rangeClosed(startInclusive, endInclusive, step);
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} from
     * {@code startInclusive} (inclusive) to {@code endInclusive} (inclusive) by
     * an incremental step of {@code 1}.
     *
     * @param startInclusive the (inclusive) initial value
     * @param endInclusive the inclusive upper bound
     * @return a sequential {@code IntStreamEx} for the range of {@code int}
     *         elements
     * @see IntStream#rangeClosed(int, int)
     */
    public static IntStreamEx rangeClosed(int startInclusive, int endInclusive) {
        return seq(IntStream.rangeClosed(startInclusive, endInclusive));
    }

    /**
     * Returns a sequential ordered {@code IntStreamEx} from
     * {@code startInclusive} (inclusive) to {@code endInclusive} (inclusive) by
     * the specified incremental step. The negative step values are also
     * supported. In this case the {@code startInclusive} should be greater than
     * {@code endInclusive}.
     * 
     * <p>
     * Note that depending on the step value the {@code endInclusive} bound may
     * still not be reached. For example
     * {@code IntStreamEx.rangeClosed(0, 5, 2)} will yield the stream of three
     * numbers: 0, 2 and 4.
     *
     * @param startInclusive the (inclusive) initial value
     * @param endInclusive the inclusive upper bound
     * @param step the non-zero value which designates the difference between
     *        the consecutive values of the resulting stream.
     * @return a sequential {@code IntStreamEx} for the range of {@code int}
     *         elements
     * @throws IllegalArgumentException if step is zero
     * @see IntStreamEx#rangeClosed(int, int)
     * @since 0.4.0
     */
    public static IntStreamEx rangeClosed(int startInclusive, int endInclusive, int step) {
        if (step == 0)
            throw new IllegalArgumentException("step = 0");
        if (step == 1)
            return seq(IntStream.rangeClosed(startInclusive, endInclusive));
        if (step == -1) {
            // Handled specially as number of elements can exceed
            // Integer.MAX_VALUE
            int sum = endInclusive + startInclusive;
            return seq(IntStream.rangeClosed(endInclusive, startInclusive).map(x -> sum - x));
        }
        if (endInclusive > startInclusive ^ step > 0)
            return empty();
        int limit = (endInclusive - startInclusive) * Integer.signum(step);
        limit = Integer.divideUnsigned(limit, Math.abs(step));
        return seq(IntStream.rangeClosed(0, limit).map(x -> x * step + startInclusive));
    }

    /**
     * Returns a sequential unordered {@code IntStreamEx} of given length which
     * elements are equal to supplied value.
     * 
     * @param value the constant value
     * @param length the length of the stream
     * @return a new {@code IntStreamEx}
     * @since 0.1.2
     */
    public static IntStreamEx constant(int value, long length) {
        return of(new ConstSpliterator.OfInt(value, length, false));
    }

    /**
     * Returns a sequential {@code IntStreamEx} containing the results of
     * applying the given function to the corresponding pairs of values in given
     * two arrays.
     * 
     * @param first the first array
     * @param second the second array
     * @param mapper a non-interfering, stateless function to apply to each pair
     *        of the corresponding array elements.
     * @return a new {@code IntStreamEx}
     * @throws IllegalArgumentException if length of the arrays differs.
     * @since 0.2.1
     */
    public static IntStreamEx zip(int[] first, int[] second, IntBinaryOperator mapper) {
        return of(new RangeBasedSpliterator.ZipInt(0, checkLength(first.length, second.length), mapper, first, second));
    }

    /**
     * A helper interface to build a new stream by emitting elements and
     * creating new emitters in a chain.
     * 
     * <p>
     * Using this interface it's possible to create custom sources which cannot
     * be easily expressed using
     * {@link IntStreamEx#iterate(int, IntUnaryOperator)} or
     * {@link IntStreamEx#generate(IntSupplier)}. For example, the following
     * method generates a Collatz sequence starting from given number:
     * 
     * <pre>{@code
     * public static IntEmitter collatz(int start) {
     *    return action -> {
     *       action.accept(start);
     *       return start == 1 ? null : collatz(start % 2 == 0 ? start / 2 : start * 3 + 1);
     *    };
     * }}</pre>
     * 
     * <p>
     * Now you can use {@code collatz(17).stream()} to get the stream of Collatz
     * numbers.
     * 
     * @author Tagir Valeev
     *
     * @since 0.6.0
     */
    @FunctionalInterface
    public interface IntEmitter {
        /**
         * Calls the supplied consumer zero or more times to emit some elements,
         * then returns the next emitter which will emit more, or null if
         * nothing more to emit.
         * 
         * <p>
         * Normally one element is emitted during the {@code next()} method
         * call. However, it's not restricted: you may emit as many elements as
         * you want, though in some cases if many elements were emitted they
         * might be buffered consuming additional memory.
         * 
         * <p>
         * It's allowed not to emit anything (don't call the consumer). However
         * if you do this and return new emitter which also does not emit
         * anything, you will end up in endless loop.
         * 
         * @param action consumer to be called to emit elements
         * @return next emitter or null
         */
        IntEmitter next(IntConsumer action);

        /**
         * Returns the spliterator which covers all the elements emitted by this
         * emitter.
         * 
         * @return the new spliterator
         */
        default Spliterator.OfInt spliterator() {
            return new EmitterSpliterator.OfInt(this);
        }

        /**
         * Returns the stream which covers all the elements emitted by this
         * emitter.
         * 
         * @return the new stream
         */
        default IntStreamEx stream() {
            return of(spliterator());
        }
    }
}
