/*
 * Copyright 2017 Palantir Technologies, Inc. All rights reserved.
 *
 * 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 com.palantir.common.streams;

import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.palantir.common.streams.BufferingSpliterator.InSourceOrder;
import com.palantir.common.streams.BufferingSpliterator.InCompletionOrder;

import static java.util.Spliterators.spliteratorUnknownSize;

import java.util.Iterator;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * Utility methods for Java 8 {@link Stream}.
 */
public class MoreStreams {

    private static final boolean NOT_PARALLEL = false;

    /**
     * Given a stream of listenable futures, this function will return a blocking stream of the completed
     * futures in completion order, looking at most {@code maxParallelism} futures ahead in the stream.
     */
    public static <T, F extends ListenableFuture<T>> Stream<F> inCompletionOrder(
            Stream<F> futures, int maxParallelism) {
        return StreamSupport.stream(new BufferingSpliterator<>(
                InCompletionOrder.INSTANCE, futures.spliterator(), maxParallelism), NOT_PARALLEL);
    }

    /**
     * A convenient variant of {@link #inCompletionOrder(Stream, int)} in which the user passes in a
     * function and an executor to run it on.
     */
    public static <U, V> Stream<V> inCompletionOrder(
            Stream<U> arguments, Function<U, V> mapper, Executor executor, int maxParallelism) {
        return inCompletionOrder(
                arguments.map(x -> Futures.transform(Futures.immediateFuture(x), mapper::apply, executor)),
                maxParallelism)
                .map(Futures::getUnchecked);
    }

    /**
     * This function will return a blocking stream that waits for each future to complete before returning it,
     * but which looks ahead {@code maxParallelism} futures to ensure a fixed parallelism rate.
     */
    public static <T, F extends ListenableFuture<T>> Stream<F> blockingStreamWithParallelism(
            Stream<F> futures, int maxParallelism) {
        return StreamSupport.stream(new BufferingSpliterator<>(
                InSourceOrder.INSTANCE, futures.spliterator(), maxParallelism), NOT_PARALLEL)
                .map(MoreFutures::blockUntilCompletion);
    }


    /**
     * A convenient variant of {@link #blockingStreamWithParallelism(Stream, int)} in which the user passes in a
     * function and an executor to run it on.
     */
    public static <U, V> Stream<V> blockingStreamWithParallelism(
            Stream<U> arguments, Function<U, V> mapper, Executor executor, int maxParallelism) {
        return blockingStreamWithParallelism(
                arguments.map(x -> Futures.transform(Futures.immediateFuture(x), mapper::apply, executor)),
                maxParallelism)
                .map(Futures::getUnchecked);
    }

    /**
     * Returns a stream of the values returned by {@code iterable}.
     *
     * @deprecated Use {@link com.google.common.collect.Streams#stream(Iterable)}, available in Guava 21+
     */
    @Deprecated
    public static <T> Stream<T> stream(Iterable<? extends T> iterable) {
        @SuppressWarnings("unchecked")
        Stream<T> stream = (Stream<T>) StreamSupport.stream(iterable.spliterator(), NOT_PARALLEL);
        return stream;
    }

    /**
     * Returns a stream containing the value held by {@code optionalValue}, or an empty stream
     * if {@code optionalValue} is empty.
     *
     * @deprecated Use {@link com.google.common.collect.Streams#stream(Optional)}, available in Guava 21+
     */
    @Deprecated
    public static <T> Stream<T> stream(Optional<T> optionalValue) {
        return optionalValue.map(Stream::of).orElse(Stream.of());
    }

    /**
     * Returns a stream from the provided iterator, preserving the iteration order.
     *
     * @param iterator Iterator for which a stream needs to be created
     * @param <T> Type parameter for the iterator
     * @return A stream for the iterator
     *
     * @deprecated Use {@link com.google.common.collect.Streams#stream(Iterator)}, available in Guava 21+
     */
    @Deprecated
    public static <T> Stream<T> stream(Iterator<T> iterator) {
        return StreamSupport.stream(spliteratorUnknownSize(iterator, 0), NOT_PARALLEL);
    }

    private MoreStreams() {}
}