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.LazyLinkedListX;
import com.oath.cyclops.data.collections.extensions.lazy.immutable.LazyPVectorX;
import com.oath.cyclops.types.Unwrapable;
import com.oath.cyclops.types.foldable.Evaluation;
import com.oath.cyclops.types.persistent.PersistentList;
import cyclops.control.Option;
import cyclops.function.Reducer;
import cyclops.reactive.ReactiveSeq;
import cyclops.reactive.collections.immutable.VectorX;
import io.vavr.collection.List;
import cyclops.data.tuple.Tuple2;


import io.vavr.collection.Vector;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.experimental.Wither;

@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class VavrVectorX<T> implements PersistentList<T>, Unwrapable {

  public static <T> VectorX<T> vectorX(ReactiveSeq<T> stream) {
    return fromStream(stream);
  }

  @Override
  public <R> R unwrap() {
    return (R) vector;
  }

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

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

  }

  /**
   * Create a LazyPVectorX from a Stream
   *
   * @param stream to construct a LazyQueueX from
   * @return LazyPVectorX
   */
  public static <T> LazyPVectorX<T> fromStream(Stream<T> stream) {
    return new LazyPVectorX<T>(null, ReactiveSeq.fromStream(stream), toPersistentList(), Evaluation.LAZY);
  }

  /**
   * Create a LazyPVectorX 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 LazyPVectorX<Integer> range(int start, int end) {
    return fromStream(ReactiveSeq.range(start, end));
  }

  /**
   * Create a LazyPVectorX 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 LazyPVectorX<Long> rangeLong(long start, long end) {
    return fromStream(ReactiveSeq.rangeLong(start, end));
  }

  /**
   * Unfold a function into a ListX
   * <p>
   * <pre>
   * {@code
   *  LazyPVectorX.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> LazyPVectorX<T> unfold(U seed, Function<? super U, Option<Tuple2<T, U>>> unfolder) {
    return fromStream(ReactiveSeq.unfold(seed, unfolder));
  }

  /**
   * Generate a LazyPVectorX 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> LazyPVectorX<T> generate(long limit, Supplier<T> s) {

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

  /**
   * Create a LazyPVectorX 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> LazyPVectorX<T> iterate(long limit, final T seed, final UnaryOperator<T> f) {
    return fromStream(ReactiveSeq.iterate(seed, f)
      .limit(limit));
  }

  /**
   * <pre>
   * {@code
   * PersistentList<Integer> q = JSPersistentList.<Integer>toPersistentList()
   * .mapReduce(Stream.of(1,2,3,4));
   *
   * }
   * </pre>
   *
   * @return Reducer for PersistentList
   */
  public static <T> Reducer<PersistentList<T>, T> toPersistentList() {
    return Reducer.<PersistentList<T>, T>of(VavrVectorX.emptyPersistentList(), (final PersistentList<T> a) -> b -> a.plusAll(b), (final T x) -> VavrVectorX.singleton(x));
  }

  public static <T> VavrVectorX<T> emptyPersistentList() {
    return new VavrVectorX<>(Vector.empty());
  }

  public static <T> LazyPVectorX<T> empty() {
    return fromPersistentList(new VavrVectorX<>(Vector.empty()), toPersistentList());
  }

  private static <T> LazyPVectorX<T> fromPersistentList(PersistentList<T> vec, Reducer<PersistentList<T>, T> pVectorReducer) {
    return new LazyPVectorX<T>(vec, null, pVectorReducer, Evaluation.LAZY);
  }

  public static <T> LazyPVectorX<T> singleton(T t) {
    return fromPersistentList(new VavrVectorX<>(Vector.of(t)), toPersistentList());
  }

  public static <T> LazyPVectorX<T> of(T... t) {
    return fromPersistentList(new VavrVectorX<>(Vector.of(t)), toPersistentList());
  }

  public static <T> LazyPVectorX<T> ofAll(Vector<T> t) {
    return fromPersistentList(new VavrVectorX<>(t), toPersistentList());
  }

  public static <T> LazyPVectorX<T> PersistentList(Vector<T> q) {
    return fromPersistentList(new VavrVectorX<T>(q), toPersistentList());
  }

  public static <T> LazyPVectorX<T> from(Vector<T> q) {
    return PersistentList(q);
  }

  @SafeVarargs
  public static <T> LazyPVectorX<T> PersistentList(T... elements) {
    return fromPersistentList(of(elements), toPersistentList());
  }

  @Wither
  private final Vector<T> vector;

  @Override
  public VavrVectorX<T> plus(T e) {
    return withVector(vector.append(e));
  }

  @Override
  public VavrVectorX<T> plusAll(Iterable<? extends T> list) {
    return withVector(vector.appendAll(list));
  }

  @Override
  public VavrVectorX<T> updateAt(int i, T e) {
    return withVector(vector.update(i, e));
  }

  @Override
  public VavrVectorX<T> insertAt(int i, T e) {
    return withVector(vector.insert(i, e));
  }

  @Override
  public VavrVectorX<T> insertAt(int i, Iterable<? extends T> list) {
    return withVector(vector.insertAll(i, list));
  }

  @Override
  public VavrVectorX<T> removeValue(T e) {
    return withVector(vector.remove(e));
  }

  @Override
  public VavrVectorX<T> removeAll(Iterable<? extends T> list) {
    return withVector(vector.removeAll(list));
  }

  @Override
  public VavrVectorX<T> removeAt(int i) {
    return withVector(vector.removeAt(i));
  }


  @Override
  public Option<T> get(int index) {
    if (index >= 0 && index < vector.size())
      return Option.some(vector.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 int size() {
    return vector.size();
  }


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

  @Override
  public String toString() {
    return "VavrVectorX[" + vector + ']';
  }

  @Override
  public boolean equals(Object o) {

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

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