package org.infinispan.functional.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;

import org.infinispan.functional.Traversable;
import org.infinispan.commons.util.CloseableIterator;
import org.infinispan.commons.util.Closeables;

public final class Traversables {

   public static <T> Traversable<T> of(Stream<T> stream) {
      return new StreamTraversable<>(stream);
   }

   public static <T> CloseableIterator<T> asIterator(Traversable<T> traversable) {
      if (traversable instanceof StreamTraversable)
         return Closeables.iterator(((StreamTraversable<T>) traversable).stream);
      else {
         List<T> collected = traversable.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
         return Closeables.iterator(collected.iterator());
      }
   }

   private Traversables() {
      // Cannot be instantiated, it's just a holder class
   }

   // TODO: Attention! This is a very rudimentary/simplistic implementation!
   private static final class StreamTraversable<T> implements Traversable<T> {
      final Stream<T> stream;

      private StreamTraversable(Stream<T> stream) {
         this.stream = stream;
      }

      @Override
      public Traversable<T> filter(Predicate<? super T> p) {
         return new StreamTraversable<>(stream.filter(p));
      }

      @Override
      public <R> Traversable<R> map(Function<? super T, ? extends R> f) {
         return new StreamTraversable<>(stream.map(f));
      }

      @Override
      public <R> Traversable<R> flatMap(Function<? super T, ? extends Traversable<? extends R>> f) {
         Function<? super T, ? extends Stream<? extends R>> mapper = new Function<T, Stream<? extends R>>() {
            @Override
            public Stream<? extends R> apply(T t) {
               StreamTraversable<? extends R> applied = (StreamTraversable<? extends R>) f.apply(t);
               return applied.stream;
            }
         };

         return new StreamTraversable<>(stream.flatMap(mapper));
      }

      @Override
      public void forEach(Consumer<? super T> c) {
         stream.forEach(c);
      }

      @Override
      public T reduce(T z, BinaryOperator<T> folder) {
         return stream.reduce(z, folder);
      }

      @Override
      public Optional<T> reduce(BinaryOperator<T> folder) {
         return stream.reduce(folder);
      }

      @Override
      public <U> U reduce(U z, BiFunction<U, ? super T, U> mapper, BinaryOperator<U> folder) {
         return stream.reduce(z, mapper, folder);
      }

      @Override
      public <R> R collect(Supplier<R> s, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner) {
         return stream.collect(s, accumulator, combiner);
      }

      @Override
      public <R, A> R collect(Collector<? super T, A, R> collector) {
         return stream.collect(collector);
      }

      @Override
      public long count() {
         return stream.count();
      }

      @Override
      public boolean anyMatch(Predicate<? super T> p) {
         return stream.anyMatch(p);
      }

      @Override
      public boolean allMatch(Predicate<? super T> p) {
         return stream.allMatch(p);
      }

      @Override
      public boolean noneMatch(Predicate<? super T> predicate) {
         return stream.noneMatch(predicate);
      }

      @Override
      public Optional<T> findAny() {
         return stream.findAny();
      }
   }

}
