/*
 * Copyright 2015, 2024 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 org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

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.Internals.*;

/**
 * An {@link IntStream} implementation with additional functionality.
 */
@NullMarked
public final 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);
    }

    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();
        int size = intSize(spliterator);
        A intermediate;
        if (size != -1) {
            intermediate = sizedSupplier.apply(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 to 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>
     * The 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 extends @Nullable Object> 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 extends @Nullable Object, 
            V extends @Nullable Object> 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 @Nullable 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 @Nullable 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 @Nullable 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 extends @Nullable Object> StreamEx<R> flatMapToObj(IntFunction<? extends @Nullable 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
     * a 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 the 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 the 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 the 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 the 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
     * the 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 a short-circuiting
     * operation downstream that 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
     * the 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 a 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);
    }

    @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's 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();
        int size = intSize(spliterator);
        IntBuffer buf = new IntBuffer(size >= 0 ? 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 a 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 extends @Nullable Object> 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 a supplier,
     * an accumulator, and a merger function 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 extends @Nullable Object, R extends @Nullable Object> R collect(IntCollector<A, R> collector) {
        if (collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH))
