package fr.univamu.helpers;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public class Iterations {

  public static Iterator<Integer> range(int fromIncluded, int toExcluded) {
    return new Iterator<Integer>() {
      private int current;

      @Override
      public boolean hasNext() {
        return current < toExcluded;
      }

      @Override
      public Integer next() {
        if (!hasNext()) {
          throw new NoSuchElementException();
        }
        return current++;
      }
    };
  }


  public static <S,T> Iterator<T> map(Iterator<S> iterator, Function<S,T> f) {
    return new Iterator<T>() {

      @Override
      public boolean hasNext() {
        return iterator.hasNext();
      }

      @Override
      public T next() {
        return f.apply(iterator.next());
      }
    };
  }

  public static <T> Stream<T> toStream(Iterable<T> iterable) {
    return StreamSupport.stream(iterable.spliterator(), false);
  }

  public static <T> Iterable<T> empty() {
    return () -> new Iterator<T>() {
      @Override
      public boolean hasNext() {
        return false;
      }

      @Override
      public T next() {
        throw new NoSuchElementException();
      }
    };
  }

  public static <T> Iterable<T> single(T element) {
    return () -> new Iterator<T> () {
      private boolean hasNext = true;
      @Override
      public boolean hasNext() {
        return hasNext;
      }

      @Override
      public T next() {
        if (!hasNext()) {
          throw new NoSuchElementException();
        }
        hasNext = false;
        return element;
      }
    };
  }

  public static <T> Iterable<T> append(Iterable<T> iter1, Iterable<T> iter2) {
    return concat(List.of(iter1, iter2));
  }

  public static <T> Iterable<T> concat(Iterable<? extends Iterable<T>> iterables) {
    return () -> new Iterator<T>() {

      private final Iterator<? extends Iterable<T>> futureIterators = iterables.iterator();
      private Iterator<T> currentIterator;

      { prepareNext(); }

      private void prepareNext() {
        while (currentIterator == null
            || !currentIterator.hasNext() && futureIterators.hasNext()) {
          currentIterator = futureIterators.next().iterator();
        }
      }

      @Override
      public boolean hasNext() {
        return currentIterator.hasNext();
      }

      @Override
      public T next() {
        if (!currentIterator.hasNext()) {
          throw new NoSuchElementException();
        }
        T value = currentIterator.next();
        prepareNext();
        return value;
      }
    };
  }

  public static <T> Iterable<T> reverse(Iterable<T> bfsIterable) {
    List<T> list = new ArrayList<>();
    bfsIterable.forEach(list::add);
    return () -> new Iterator<T>() {
      private int index = list.size()-1;
      @Override
      public boolean hasNext() {
        return index >= 0;
      }

      @Override
      public T next() {
        if (!hasNext()) {
          throw new NoSuchElementException();
        }
        return list.get(index--);
      }
    };
  }
}