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

import net.ranides.assira.collection.iterators.IteratorUtils;
import net.ranides.assira.collection.lists.CopyList;
import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.collection.query.CQueryAbstract;
import net.ranides.assira.collection.query.support.BaseList;
import net.ranides.assira.functional.Consumers;
import net.ranides.assira.functional.Predicates;
import net.ranides.assira.math.MathUtils;

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

/**
 * Stream implementation which uses list generator as source of data.
 *
 * If provided generator creates list fast, then  it implements fast:
 * list, stream, iterator and length.
 *
 * @param <T>
 */
public abstract class CQListSupplier<T> extends CQueryAbstract<T> {

    protected boolean parallel;

    protected Supplier<? extends RuntimeException> message;

    public CQListSupplier(Supplier<? extends RuntimeException> message) {
        this.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<List<T>> source) {
        return from(false, source, null);
    }

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

    /**
     * Creates new stream which supports almost all fast operations, if generator is fast.
     *
     * @param fast fast
     * @param source source
     * @param <T> T
     * @return stream
     */
    public static <T> CQuery<T> from(boolean fast, Supplier<List<T>> source, Supplier<? extends RuntimeException> message) {
        return new CQListSupplier<T>(message) {
            @Override
            public List<T> list() {
                return new CopyList<>(source.get());
            }

            @Override
            public boolean hasFastList() {
                return fast;
            }
        };
    }

    @Override
    public abstract List<T> list();

    @Override
    public abstract boolean hasFastList();

    @Override
    public boolean hasFastEach() {
        return hasFastList();
    }

    @Override
    public boolean hasFastStream() {
        return hasFastList();
    }

    @Override
    public boolean hasFastIterator() {
        return hasFastList();
    }

    @Override
    public boolean hasFastLength() {
        return hasFastList();
    }

    @Override
    public Supplier<? extends RuntimeException> message() {
        return message;
    }

    @Override
    public CQuery<T> message(Supplier<? extends RuntimeException> message) {
        this.message = message;
        return this;
    }

    @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 isParallel() ? list().parallelStream() : list().stream();
    }

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

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

    @Override
    public CQuery<T> slice(int begin, int end) {
        return from(
            hasFastList(), () -> {
                List<T> data = list();
                int b = MathUtils.clip(begin, 0, data.size());
                int e = MathUtils.clip(end, b, data.size());
                return data.subList(b,e);
            },
            message
        );
    }

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

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

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

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