package cyclops.companion.vavr;

import com.oath.cyclops.anym.AnyMSeq;
import com.oath.cyclops.hkt.Higher;
import cyclops.companion.CompletableFutures;
import cyclops.companion.Optionals;
import cyclops.control.Either;
import cyclops.control.Eval;
import cyclops.control.Maybe;
import cyclops.control.Reader;
import cyclops.conversion.vavr.FromCyclops;
import cyclops.data.tuple.Tuple2;
import cyclops.function.Function3;
import cyclops.function.Function4;
import cyclops.function.Monoid;
import cyclops.monads.*;
import cyclops.monads.VavrWitness.array;
import cyclops.monads.VavrWitness.lazy;
import cyclops.monads.VavrWitness.queue;
import cyclops.monads.VavrWitness.tryType;
import cyclops.monads.Witness.*;
import cyclops.reactive.ReactiveSeq;
import cyclops.typeclasses.InstanceDefinitions;
import cyclops.typeclasses.Pure;
import cyclops.typeclasses.comonad.Comonad;
import cyclops.typeclasses.foldable.Foldable;
import cyclops.typeclasses.foldable.Unfoldable;
import cyclops.typeclasses.functor.Functor;
import cyclops.typeclasses.instances.General;
import cyclops.typeclasses.monad.*;
import io.vavr.Lazy;
import io.vavr.collection.*;
import io.vavr.concurrent.Future;
import io.vavr.control.Try;
import lombok.experimental.UtilityClass;

import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Function;



public class Queues {


    public static  <W1 extends WitnessType<W1>,T> XorM<W1,queue,T> xorM(Queue<T> type){
        return XorM.right(anyM(type));
    }
    public static  <W1 extends WitnessType<W1>,T> XorM<W1,queue,T> xorM(T... values){
        return xorM(Queue.of(values));
    }

    public static <T> AnyMSeq<queue,T> anyM(Queue<T> option) {
        return AnyM.ofSeq(option, queue.INSTANCE);
    }

    public static  <T,R> Queue<R> tailRec(T initial, Function<? super T, ? extends Queue<? extends io.vavr.control.Either<T, R>>> fn) {
        Queue<io.vavr.control.Either<T, R>> next = Queue.of(io.vavr.control.Either.left(initial));

        boolean newValue[] = {true};
        for(;;){

            next = next.flatMap(e -> e.fold(s -> {
                        newValue[0]=true;
                        return fn.apply(s); },
                    p -> {
                        newValue[0]=false;
                        return Queue.of(e);
                    }));
            if(!newValue[0])
                break;

        }

        return next.filter(io.vavr.control.Either::isRight).map(io.vavr.control.Either::get);
    }
    public static  <T,R> Queue<R> tailRecEither(T initial, Function<? super T, ? extends Queue<? extends Either<T, R>>> fn) {
        Queue<Either<T, R>> next = Queue.of(Either.left(initial));

        boolean newValue[] = {true};
        for(;;){

            next = next.flatMap(e -> e.visit(s -> {
                        newValue[0]=true;
                        return fn.apply(s); },
                    p -> {
                        newValue[0]=false;
                        return Queue.of(e);
                    }));
            if(!newValue[0])
                break;

        }

        return next.filter(Either::isRight).map(e->e.orElse(null));
    }


    /**
     * Perform a For Comprehension over a Queue, accepting 3 generating functions.
     * This results in a four level nested internal iteration over the provided Publishers.
     *
     *  <pre>
     * {@code
     *
     *   import static cyclops.Queues.forEach4;
     *
    forEach4(IntQueue.range(1,10).boxed(),
    a-> Queue.iterate(a,i->i+1).limit(10),
    (a,b) -> Queue.<Integer>of(a+b),
    (a,b,c) -> Queue.<Integer>just(a+b+c),
    Tuple::tuple)
     *
     * }
     * </pre>
     *
     * @param value1 top level Queue
     * @param value2 Nested Queue
     * @param value3 Nested Queue
     * @param value4 Nested Queue
     * @param yieldingFunction  Generates a result per combination
     * @return Queue with an element per combination of nested publishers generated by the yielding function
     */
    public static <T1, T2, T3, R1, R2, R3, R> Queue<R> forEach4(Queue<? extends T1> value1,
                                                               Function<? super T1, ? extends Queue<R1>> value2,
                                                               BiFunction<? super T1, ? super R1, ? extends Queue<R2>> value3,
                                                               Function3<? super T1, ? super R1, ? super R2, ? extends Queue<R3>> value4,
                                                               Function4<? super T1, ? super R1, ? super R2, ? super R3, ? extends R> yieldingFunction) {


        return value1.flatMap(in -> {

            Queue<R1> a = value2.apply(in);
            return a.flatMap(ina -> {
                Queue<R2> b = value3.apply(in,ina);
                return b.flatMap(inb -> {
                    Queue<R3> c = value4.apply(in,ina,inb);
                    return c.map(in2 -> yieldingFunction.apply(in, ina, inb, in2));
                });

            });

        });

    }

    /**
     * Perform a For Comprehension over a Queue, accepting 3 generating function.
     * This results in a four level nested internal iteration over the provided Publishers.
     * <pre>
     * {@code
     *
     *  import static com.oath.cyclops.reactor.Queuees.forEach4;
     *
     *  forEach4(IntQueue.range(1,10).boxed(),
    a-> Queue.iterate(a,i->i+1).limit(10),
    (a,b) -> Queue.<Integer>just(a+b),
    (a,b,c) -> Queue.<Integer>just(a+b+c),
    (a,b,c,d) -> a+b+c+d <100,
    Tuple::tuple);
     *
     * }
     * </pre>
     *
     * @param value1 top level Queue
     * @param value2 Nested Queue
     * @param value3 Nested Queue
     * @param value4 Nested Queue
     * @param filterFunction A filtering function, keeps values where the predicate holds
     * @param yieldingFunction Generates a result per combination
     * @return Queue with an element per combination of nested publishers generated by the yielding function
     */
    public static <T1, T2, T3, R1, R2, R3, R> Queue<R> forEach4(Queue<? extends T1> value1,
                                                                 Function<? super T1, ? extends Queue<R1>> value2,
                                                                 BiFunction<? super T1, ? super R1, ? extends Queue<R2>> value3,
                                                                 Function3<? super T1, ? super R1, ? super R2, ? extends Queue<R3>> value4,
                                                                 Function4<? super T1, ? super R1, ? super R2, ? super R3, Boolean> filterFunction,
                                                                 Function4<? super T1, ? super R1, ? super R2, ? super R3, ? extends R> yieldingFunction) {


        return value1.flatMap(in -> {

            Queue<R1> a = value2.apply(in);
            return a.flatMap(ina -> {
                Queue<R2> b = value3.apply(in,ina);
                return b.flatMap(inb -> {
                    Queue<R3> c = value4.apply(in,ina,inb);
                    return c.filter(in2->filterFunction.apply(in,ina,inb,in2))
                            .map(in2 -> yieldingFunction.apply(in, ina, inb, in2));
                });

            });

        });
    }

    /**
     * Perform a For Comprehension over a Queue, accepting 2 generating function.
     * This results in a three level nested internal iteration over the provided Publishers.
     *
     * <pre>
     * {@code
     *
     * import static Queues.forEach3;
     *
     * forEach(IntQueue.range(1,10).boxed(),
    a-> Queue.iterate(a,i->i+1).limit(10),
    (a,b) -> Queue.<Integer>of(a+b),
    Tuple::tuple);
     *
     * }
     * </pre>
     *
     *
     * @param value1 top level Queue
     * @param value2 Nested Queue
     * @param value3 Nested Queue
     * @param yieldingFunction Generates a result per combination
     * @return Queue with an element per combination of nested publishers generated by the yielding function
     */
    public static <T1, T2, R1, R2, R> Queue<R> forEach3(Queue<? extends T1> value1,
                                                         Function<? super T1, ? extends Queue<R1>> value2,
                                                         BiFunction<? super T1, ? super R1, ? extends Queue<R2>> value3,
                                                         Function3<? super T1, ? super R1, ? super R2, ? extends R> yieldingFunction) {

        return value1.flatMap(in -> {

            Queue<R1> a = value2.apply(in);
            return a.flatMap(ina -> {
                Queue<R2> b = value3.apply(in,ina);
                return b.map(in2 -> yieldingFunction.apply(in, ina, in2));
            });


        });


    }

    /**
     * Perform a For Comprehension over a Queue, accepting 2 generating function.
     * This results in a three level nested internal iteration over the provided Publishers.
     * <pre>
     * {@code
     *
     * import static Queues.forEach;
     *
     * forEach(IntQueue.range(1,10).boxed(),
    a-> Queue.iterate(a,i->i+1).limit(10),
    (a,b) -> Queue.<Integer>of(a+b),
    (a,b,c) ->a+b+c<10,
    Tuple::tuple)
    .toQueueX();
     * }
     * </pre>
     *
     * @param value1 top level Queue
     * @param value2 Nested publisher
     * @param value3 Nested publisher
     * @param filterFunction A filtering function, keeps values where the predicate holds
     * @param yieldingFunction Generates a result per combination
     * @return
     */
    public static <T1, T2, R1, R2, R> Queue<R> forEach3(Queue<? extends T1> value1,
                                                         Function<? super T1, ? extends Queue<R1>> value2,
                                                         BiFunction<? super T1, ? super R1, ? extends Queue<R2>> value3,
                                                         Function3<? super T1, ? super R1, ? super R2, Boolean> filterFunction,
                                                         Function3<? super T1, ? super R1, ? super R2, ? extends R> yieldingFunction) {


        return value1.flatMap(in -> {

            Queue<R1> a = value2.apply(in);
            return a.flatMap(ina -> {
                Queue<R2> b = value3.apply(in,ina);
                return b.filter(in2->filterFunction.apply(in,ina,in2))
                        .map(in2 -> yieldingFunction.apply(in, ina, in2));
            });



        });
    }

    /**
     * Perform a For Comprehension over a Queue, accepting an additonal generating function.
     * This results in a two level nested internal iteration over the provided Publishers.
     *
     * <pre>
     * {@code
     *
     *  import static Queues.forEach2;
     *  forEach(IntQueue.range(1, 10).boxed(),
     *          i -> Queue.range(i, 10), Tuple::tuple)
    .forEach(System.out::println);

    //(1, 1)
    (1, 2)
    (1, 3)
    (1, 4)
    ...
     *
     * }</pre>
     *
     * @param value1 top level Queue
     * @param value2 Nested publisher
     * @param yieldingFunction Generates a result per combination
     * @return
     */
    public static <T, R1, R> Queue<R> forEach2(Queue<? extends T> value1,
                                                Function<? super T, Queue<R1>> value2,
                                                BiFunction<? super T, ? super R1, ? extends R> yieldingFunction) {


        return value1.flatMap(in -> {

            Queue<R1> a = value2.apply(in);
            return a.map(in2 -> yieldingFunction.apply(in,  in2));
        });

    }

    /**
     *
     * <pre>
     * {@code
     *
     *   import static Queues.forEach2;
     *
     *   forEach(IntQueue.range(1, 10).boxed(),
     *           i -> Queue.range(i, 10),
     *           (a,b) -> a>2 && b<10,
     *           Tuple::tuple)
    .forEach(System.out::println);

    //(3, 3)
    (3, 4)
    (3, 5)
    (3, 6)
    (3, 7)
    (3, 8)
    (3, 9)
    ...

     *
     * }</pre>
     *
     *
     * @param value1 top level Queue
     * @param value2 Nested publisher
     * @param filterFunction A filtering function, keeps values where the predicate holds
     * @param yieldingFunction Generates a result per combination
     * @return
     */
    public static <T, R1, R> Queue<R> forEach2(Queue<? extends T> value1,
                                                Function<? super T, ? extends Queue<R1>> value2,
                                                BiFunction<? super T, ? super R1, Boolean> filterFunction,
                                                BiFunction<? super T, ? super R1, ? extends R> yieldingFunction) {


        return value1.flatMap(in -> {

            Queue<R1> a = value2.apply(in);
            return a.filter(in2->filterFunction.apply(in,in2))
                    .map(in2 -> yieldingFunction.apply(in,  in2));
        });
    }


}
