package net.ranides.assira.collection.query.base;

import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.collection.query.CQueryAbstract;
import net.ranides.assira.functional.Consumers;
import net.ranides.assira.functional.Predicates;

import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;

/**
 * Stream implementation which uses query generator as source of data.
 *
 * If provided generator creates query fast, then it ihnerits features from query.
 * If generator is not fast, then all features are reported as slow.
 *
 * @param <T>
 */
public abstract class CQQuerySupplier<T> extends CQueryAbstract<T> {

    protected boolean parallel;

    protected Supplier<? extends RuntimeException> message;

    public CQQuerySupplier(Supplier<? extends RuntimeException> message) {
        this.message = message;
    }

    @Override
    public final Supplier<? extends RuntimeException> message() {
        return message == null ? query().message() : message;
    }

    /**
     * Creates new stream which does not support fast operations at all, because
     * it assummes generator call has very high cost.
     *
     * @param source source
     * @param <T> T
     * @return stream
     */
    public static <T> CQuery<T> from(Supplier<CQuery<T>> source) {
        return from(false, source);
    }

    /**
     * Creates new stream which inherits all fast operations from source, if generator is fast.
     *
     * @param fast fast
     * @param source source
     * @param <T> T
     * @return stream
     */
    public static <T> CQuery<T> from(boolean fast, Supplier<CQuery<T>> source) {
        return from(false, source, null);
    }

    /**
     * Creates new stream which inherits all fast operations from source, if generator is fast.
     *
     * @param fast fast
     * @param source source
     * @param <T> T
     * @return stream
     */
    public static <T> CQuery<T> from(boolean fast, Supplier<CQuery<T>> source, Supplier<? extends RuntimeException> message) {
        return new CQQuerySupplier<T>(message) {

            @Override
            public CQuery<T> message(Supplier<? extends RuntimeException> message) {
                return from(fast, source, message);
            }

            @Override
            protected boolean hasFastQuery() {
                return fast;
            }

            @Override
            protected CQuery<T> query() {
                CQuery<T> out = isParallel() ? source.get().parallel() : source.get().sequential();
                if(message != null) {
                    out = out.message(message);
                }
                return out;
            }
        };
    }

    protected abstract CQuery<T> query();

    protected boolean hasFastQuery() {
        return false;
    }

    @Override
    public boolean hasFastEach() {
        return hasFastQuery() && query().features().hasFastEach();
    }

    @Override
    public boolean hasFastStream() {
        return hasFastQuery() && query().features().hasFastStream();
    }

    @Override
    public boolean hasFastIterator() {
        return hasFastQuery() && query().features().hasFastIterator();
    }

    @Override
    public boolean hasFastLength() {
        return hasFastQuery() && query().features().hasFastLength();
    }

    @Override
    public boolean hasFastList() {
        return hasFastQuery() && query().features().hasFastList();
    }

    @Override
    public boolean isParallel() {
        return parallel;
    }

    @Override
    public CQuery<T> parallel() {
        this.parallel = true;
        return this;
    }

    @Override
    public CQuery<T> sequential() {
        this.parallel = false;
        return this;
    }

    @Override
    public Stream<T> stream() {
        return query().stream();
    }

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

    @Override
    public Spliterator<T> spliterator() {
        return query().spliterator();
    }

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

    @Override
    public List<T> list() {
        return query().list();
    }

    @Override
    public T[] array() {
        return query().array();
    }

    @Override
    public <A> A[] array(IntFunction<A[]> generator) {
        return query().array(generator);
    }

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

    @Override
    public void forEach(Consumers.EachConsumer<? super T> consumer) {
        query().forEach(consumer);
    }

    @Override
    public boolean whileEach(Predicate<? super T> consumer) {
        return query().whileEach(consumer);
    }

    @Override
    public boolean whileEach(Predicates.EachPredicate<? super T> consumer) {
        return query().whileEach(consumer);
    }

}
