package com.codepoetics.protonpack.stateful;

import com.codepoetics.protonpack.Indexed;

import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public final class Statefully {

  private static final Consumer<?> NO_OP = o -> {};
  private static <O> Consumer<O> noOp() {
    return (Consumer<O>) NO_OP;
  }

  public static <S, I, O> S traverse(Stream<I> input, StateMachine<S, I, O> stateMachine) {
    StatefulSpliterator<S, I, O> spliterator = StatefulSpliterator.over(input, stateMachine);
    StreamSupport.stream(spliterator, false).onClose(input::close).forEach(noOp());
    return spliterator.getState();
  }

  public static <S, I, O> Stream<O> transform(Stream<I> input, StateMachine<S, I, O> stateMachine) {
    StatefulSpliterator<S, I, O> spliterator = StatefulSpliterator.over(input, stateMachine);
    return StreamSupport.stream(spliterator, false).flatMap(s -> s).onClose(input::close);
  }

  public static <S, I, O> boolean terminates(Stream<I> input, StateMachine<S, I, O> stateMachine) {
    return stateMachine.isTerminal(traverse(input, stateMachine));
  }

  public static <T, R> Stream<R> window(Stream<T> input, int size, Function<Stream<T>, R> reducer) {
    return Statefully.transform(input, StateMachines.windowingStateMachine(size, reducer));
  }

  public static <T> Stream<Indexed<T>> index(Stream<T> input) {
    return Statefully.transform(
        input,
        StateMachine.create(() -> 0L, (index, item) -> Transition.to(index + 1, Indexed.index(index, item)))
    );
  }

  public static <S, T> Stream<TaggedValue<S, T>> tagging(Stream<T> input, S initialState, BiFunction<S, T, S> stateFunction) {
    return tagging(input, initialState, stateFunction, s -> false);
  }

  public static <S, T> Stream<TaggedValue<S, T>> tagging(Stream<T> input, S initialState, BiFunction<S, T, S> stateFunction, Predicate<S> isTerminal) {
    return Statefully.transform(input, StateMachines.tagging(initialState, stateFunction, isTerminal));
  }

  public static <T> Optional<T> findLastMatching(Stream<T> input, Predicate<T> condition) {
    return Statefully.transform(input, StateMachines.lastMatchingFinder(condition)).findFirst();
  }

  public static <T> boolean terminatingForEach(Stream<T> input, Function<T, Boolean> action) {
    return Statefully.traverse(input, StateMachines.terminatingForEach(action));
  }

  @SafeVarargs
  public static <T> boolean includesItems(Stream<T> input, T...items) {
    return includesItems(input, Stream.of(items).collect(Collectors.toSet()));
  }

  public static <T> boolean includesItems(Stream<T> input, Set<T> subset) {
    return Statefully.terminates(input, StateMachines.checkingSubset(subset));
  }

}