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

import net.ranides.assira.collection.iterators.IteratorUtils;
import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.collection.query.CQueryAbstract;
import net.ranides.assira.collection.query.support.BaseState;
import net.ranides.assira.functional.Predicates;
import net.ranides.assira.math.MathUtils;

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

/**
 * Adapter query: it returns only specified fragment of source query.
 * Used by {@link CQuery#slice(int, int)} operation.
 *
 * It reports all features supported by source except "each".
 * Fast "each" is reported only, if selected slice does not skip more elements than "FAST_SKIP_LIMIT"
 *
 * @param <T> T
 */
public class CQSlice<T> extends CQueryAbstract<T> {

    private static final int FAST_SKIP_LIMIT = 1;

    private final CQuery<T> source;
    private final int begin;
    private final int end;

    /**
     * Creates new slice with provided range.
     *
     * @param source source
     * @param begin begin
     * @param end end
     */
    public CQSlice(CQuery<T> source, int begin, int end) {
        this.source = source;
        this.begin = Math.max(0, begin);
        this.end =  Math.max(begin, end);
    }

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

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

    @Override
    public boolean hasFastEach() {
        return source.features().hasFastEach() && begin <= FAST_SKIP_LIMIT;
    }

    @Override
    public boolean hasFastStream() {
        return source.features().hasFastStream();
    }

    @Override
    public boolean hasFastIterator() {
        return source.features().hasFastIterator();
    }

    @Override
    public boolean hasFastLength() {
        return source.features().hasFastLength();
    }

    @Override
    public boolean hasFastList() {
        return source.features().hasFastList();
    }

    @Override
    public boolean isParallel() {
        return source.features().isParallel();
    }

    @Override
    public CQuery<T> parallel() {
        return isParallel() ? this : new CQSlice<>(source.parallel(), begin, end);
    }

    @Override
    public CQuery<T> sequential() {
        return isParallel() ? new CQSlice<>(source.sequential(), begin, end) : this;
    }

    @Override
    public Iterator<T> iterator() {
        return IteratorUtils.limit(source.iterator(), begin, end);
    }

    @Override
    public Stream<T> stream() {
        return source.stream().skip(begin).limit(end-begin);
    }

    @Override
    public List<T> list() {
        if(hasFastList()) {
            return source.list().subList(begin, Math.min(source.size(), end));
        }
        return super.list();
    }

    @Override
    public int size() {
        return Math.max(0, Math.min(source.size(), end) - begin);
    }

    @Override
    public boolean whileEach(Predicates.EachPredicate<? super T> consumer) {
        if(!hasFastEach()) {
            return super.whileEach(consumer);
        }
        BaseState.Terminator terminator = BaseState.newTerminator(this);
        boolean canceled = source.whileEach((i,v) -> {
            if(i < begin) {
                return true;
            }
            if(!consumer.test(i - begin, v)) {
                return false;
            }
            if(i + 1 == end) {
                terminator.stop();
                return false;
            }
            return true;
        });
        return canceled || terminator.stopped();
    }

    @Override
    public CQuery<T> slice(int begin, int end) {
        int b = MathUtils.adds(this.begin, begin, 0, this.end);
        int e = MathUtils.adds(this.begin, end, b, this.end);
        return new CQSlice<>(source, b, e);
    }

}