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

import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.collection.query.base.CQAbstractFilter;
import net.ranides.assira.collection.query.support.BaseIterable;
import net.ranides.assira.generic.Ref;

import java.util.Iterator;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * Adapter query: it calculates new value for every item or skips it.
 * Its behaviour is more like "CQFilter" than "CQMap"
 * Used by {@link CQuery#mapOptional(Function)} (Function)} operation.
 *
 * It supports stateless mappings only.
 *
 * Supports fast iterator and stream if source supports it.
 * Does not support other fast operations.
 *
 * @param <T> T
 * @param <R> R
 */
public class CQMapOptional<T, R> extends CQAbstractFilter<T, R> {

    private final Function<? super T, Ref<R>> f;

    /**
     * Creates new query with values computed by provided function
     *
     * @param source source
     * @param f f
     */
    public CQMapOptional(CQuery<T> source, Function<? super T, Ref<R>> f) {
        super(source);
        this.f = f;
    }

    @Override
    public CQuery<R> parallel() {
        return isParallel() ? this : new CQMapOptional<>(source.parallel(), f);
    }

    @Override
    public CQuery<R> sequential() {
        return isParallel() ? new CQMapOptional<>(source.sequential(), f) : this;
    }

    @Override
    public Stream<R> stream() {
        return source.stream().map(f).filter(Ref::isPresent).map(Ref::get);
    }

    @Override
    public Iterator<R> iterator() {
        return BaseIterable.mapOptional(source, f);
    }

    @Override
    public boolean whileEach(Predicate<? super R> consumer) {
        return source.whileEach(v -> {
            Ref<R> opt = f.apply(v);
            return !opt.isPresent() || consumer.test(opt.get());
        });
    }

}
