package cyclops.collections.vavr;

import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;

import com.oath.cyclops.data.collections.extensions.CollectionX;

import com.oath.cyclops.data.collections.extensions.lazy.immutable.LazyPQueueX;
import com.oath.cyclops.types.Unwrapable;
import com.oath.cyclops.types.foldable.Evaluation;
import com.oath.cyclops.types.persistent.PersistentList;
import com.oath.cyclops.types.persistent.PersistentQueue;
import cyclops.control.Option;
import cyclops.function.Reducer;
import cyclops.reactive.ReactiveSeq;
import cyclops.data.tuple.Tuple2;


import cyclops.reactive.collections.immutable.PersistentQueueX;
import io.vavr.collection.Queue;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.experimental.Wither;

@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class VavrQueueX<T>  implements PersistentQueue<T>, Unwrapable {
    @Wither
    private final Queue<T> list;

    public static <T> PersistentQueueX<T> queueX(ReactiveSeq<T> stream){
        return fromStream(stream);
    }

    public static <T> PersistentQueueX<T> copyFromCollection(CollectionX<T> vec) {

        return VavrQueueX.<T>empty()
                .plusAll(vec);

    }
    public static <T> PersistentQueueX<T> from(Queue<T> q) {
        return fromPersistentQueue(new VavrQueueX<>(q), toPersistentQueue());
    }
    /**
     * Create a LazyPQueueX from a Stream
     *
     * @param stream to construct a LazyQueueX from
     * @return LazyPQueueX
     */
    public static <T> LazyPQueueX<T> fromStream(Stream<T> stream) {
        return new LazyPQueueX<T>(null, ReactiveSeq.fromStream(stream),toPersistentQueue(), Evaluation.LAZY);
    }

    /**
     * Create a LazyPQueueX that contains the Integers between start and end
     *
     * @param start
     *            Number of range to start from
     * @param end
     *            Number for range to end at
     * @return Range ListX
     */
    public static LazyPQueueX<Integer> range(int start, int end) {
        return fromStream(ReactiveSeq.range(start, end));
    }

    /**
     * Create a LazyPQueueX that contains the Longs between start and end
     *
     * @param start
     *            Number of range to start from
     * @param end
     *            Number for range to end at
     * @return Range ListX
     */
    public static LazyPQueueX<Long> rangeLong(long start, long end) {
        return fromStream(ReactiveSeq.rangeLong(start, end));
    }

    /**
     * Unfold a function into a ListX
     *
     * <pre>
     * {@code
     *  LazyPQueueX.unfold(1,i->i<=6 ? Optional.of(Tuple.tuple(i,i+1)) : Optional.empty());
     *
     * //(1,2,3,4,5)
     *
     * }</pre>
     *
     * @param seed Initial value
     * @param unfolder Iteratively applied function, terminated by an empty Optional
     * @return ListX generated by unfolder function
     */
    public static <U, T> LazyPQueueX<T> unfold(U seed, Function<? super U, Option<Tuple2<T, U>>> unfolder) {
        return fromStream(ReactiveSeq.unfold(seed, unfolder));
    }

    /**
     * Generate a LazyPQueueX from the provided Supplier up to the provided limit number of times
     *
     * @param limit Max number of elements to generate
     * @param s Supplier to generate ListX elements
     * @return ListX generated from the provided Supplier
     */
    public static <T> LazyPQueueX<T> generate(long limit, Supplier<T> s) {

        return fromStream(ReactiveSeq.generate(s)
                                      .limit(limit));
    }

    /**
     * Create a LazyPQueueX by iterative application of a function to an initial element up to the supplied limit number of times
     *
     * @param limit Max number of elements to generate
     * @param seed Initial element
     * @param f Iteratively applied to each element to generate the next element
     * @return ListX generated by iterative application
     */
    public static <T> LazyPQueueX<T> iterate(long limit, final T seed, final UnaryOperator<T> f) {
        return fromStream(ReactiveSeq.iterate(seed, f)
                                      .limit(limit));
    }

    /**
     * <pre>
     * {@code
     * PersistentQueue<Integer> q = VavrQueueX.<Integer>toPersistentQueue()
                                          .mapReduce(Stream.of(1,2,3,4));
     *
     * }
     * </pre>
     * @return Reducer for PersistentQueue
     */
    public static <T> Reducer<PersistentQueue<T>,T> toPersistentQueue() {
        return Reducer.<PersistentQueue<T>,T> of(VavrQueueX.emptyPersistentQueue(), (final PersistentQueue<T> a) -> b -> a.plusAll(b), (final T x) -> VavrQueueX.singleton(x));
    }
    public static <T> VavrQueueX<T> emptyPersistentQueue(){
        return new VavrQueueX<>(Queue.empty());

    }
    public static <T> LazyPQueueX<T> empty(){
        return fromPersistentQueue(new VavrQueueX<>(Queue.empty()),toPersistentQueue());

    }
    private static <T> LazyPQueueX<T> fromPersistentQueue(PersistentQueue<T> ts, Reducer<PersistentQueue<T>,T> pQueueReducer) {
        return new LazyPQueueX<T>(ts,null,pQueueReducer, Evaluation.LAZY);
    }
    public static <T> LazyPQueueX<T> singleton(T t){
        return  fromPersistentQueue(new VavrQueueX<>(Queue.of(t)),toPersistentQueue());
    }
    public static <T> LazyPQueueX<T> of(T... t){
       return  fromPersistentQueue(new VavrQueueX<>(Queue.of(t)),toPersistentQueue());
    }
    public static <T> LazyPQueueX<T> ofAll(T... t){
        return  fromPersistentQueue(new VavrQueueX<>(Queue.of(t)),toPersistentQueue());
    }
    public static <T> LazyPQueueX<T> PersistentQueue(Queue<T> q) {
        return fromPersistentQueue(new VavrQueueX<>(q), toPersistentQueue());
    }
    @SafeVarargs
    public static <T> LazyPQueueX<T> PersistentQueue(T... elements){
        return fromPersistentQueue(of(elements),toPersistentQueue());
    }

    @Override
    public <R> R unwrap() {
        return (R)this.list;
    }

    @Override
    public VavrQueueX<T> plus(T e) {
        return withList(list.prepend(e));
    }

    @Override
    public VavrQueueX<T> minus() {
      if(list.size()>0)
        return withList(list.removeAt(0));
      return this;
    }

    @Override
    public VavrQueueX<T> plusAll(Iterable<? extends T> it) {
      return withList(list.enqueueAll(it));
    }

    @Override
    public VavrQueueX<T> removeValue(T e) {
      return withList(list.remove(e));
    }

    @Override
    public VavrQueueX<T> removeAll(Iterable<? extends T> it) {
      return withList(list.removeAll(it));
    }

  @Override
    public int size() {
        return list.size();
    }


    @Override
    public Iterator<T> iterator() {
        return list.iterator();
    }


  @Override
  public Option<T> get(int index) {
    if(index>=0 && index<list.size())
      return Option.some(list.get(index));
    return Option.none();
  }
  @Override
  public T getOrElse(int index, T alt) {
    return get(index).orElse(alt);
  }
  @Override
  public T getOrElseGet(int index, Supplier<? extends T> alt) {
    return get(index).orElseGet(alt);
  }

  @Override
  public String toString() {
    return "VavrQueueX["  + list + "]";
  }
  @Override
  public boolean equals(Object o) {

    if (this == o) return true;
    if(o instanceof PersistentList){
      PersistentQueue<T> x = (PersistentQueue<T>)o;
      return o.equals(this);
    }
    return false;
  }

  @Override
  public int hashCode() {
    return Objects.hash(list);
  }
}
