package com.atlassian.bitbucket.util;

import javax.annotation.Nonnull;
import java.util.Enumeration;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static java.util.Objects.requireNonNull;

/**
 * Additional utilities for creating {@link Stream streams}.
 */
public class MoreStreams {

    private MoreStreams() {
        throw new UnsupportedOperationException(getClass().getName() +
                " is a utility class and should not be instantiated");
    }

    /**
     * Converts an {@code Enumeration} to a {@code Stream}.
     *
     * @param enumeration the {@code Enumeration} to stream
     * @param <T>         the type of element in the {@code Enumeration}
     * @return a {@code Stream} over the provided {@code enumeration}
     * @throws NullPointerException if the provided {@code enumeration} is {@code null}
     * @since 4.9
     */
    @Nonnull
    public static <T> Stream<T> streamEnumeration(@Nonnull Enumeration<T> enumeration) {
        requireNonNull(enumeration, "enumeration");

        return StreamSupport.stream(new Spliterators.AbstractSpliterator<T>(Long.MAX_VALUE, Spliterator.ORDERED) {

            //Overridden to avoid the extra tryAdvance indirection from the base implementation
            @Override
            public void forEachRemaining(Consumer<? super T> action) {
                while (enumeration.hasMoreElements()) {
                    action.accept(enumeration.nextElement());
                }
            }

            @Override
            public boolean tryAdvance(Consumer<? super T> action) {
                if (enumeration.hasMoreElements()) {
                    action.accept(enumeration.nextElement());

                    return true;
                }

                return false;
            }
        }, false);
    }

    /**
     * Converts an {@code Iterable} to a {@code Stream}.
     *
     * @param iterable the {@code Iterable} to stream
     * @param <T>      the type of element in the {@code Iterable}
     * @return a {@code Stream} over the provided {@code iterable}
     * @throws NullPointerException if the provided {@code iterable} is {@code null}
     */
    @Nonnull
    public static <T> Stream<T> streamIterable(@Nonnull Iterable<T> iterable) {
        return StreamSupport.stream(requireNonNull(iterable, "iterable").spliterator(), false);
    }

    /**
     * Converts an {@code Optional} to a single-element or empty {@code Stream}.
     *
     * @param optional the {@code Optional} to stream
     * @param <T>      the type of element in the {@code Optional}
     * @return a single-element {@code Stream} if the {@code Optional} is present; otherwise an empty {@code Stream}
     * @throws NullPointerException if the provided {@code optional} is {@code null}
     */
    @Nonnull
    @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
    public static <T> Stream<T> streamOptional(@Nonnull Optional<T> optional) {
        return requireNonNull(optional, "optional").map(Stream::of).orElseGet(Stream::empty);
    }
}
