/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.collection.query;

import net.ranides.assira.collection.HashFunction;
import net.ranides.assira.functional.Consumers.EachConsumer;
import net.ranides.assira.functional.Functions.EachFunction;
import net.ranides.assira.functional.Predicates.EachPredicate;
import net.ranides.assira.functional.checked.CheckedFunction;
import net.ranides.assira.generic.Ref;
import net.ranides.assira.reflection.*;

import java.nio.file.Path;
import java.util.Comparator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * This interface represents prepared query pipeline which can be executed against any "source query" to create
 * query configurared ahead of time.
 *
 * @param <S> S
 * @param <T> T
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public interface CQueryFunction<S, T> extends Function<CQuery<S>, CQuery<T>> {

    /**
     * Starts query building.
     * Returned prepared query does not modify source.
     *
     * @param <R> R
     * @return prepared query
     */
    static <R> CQueryFunction<R,R> prepare() {
        return s -> s;
    }

    /**
     * This method inserts into pipepilen provided operation to call on source query.
     *
     * Should be used if we want to insert complex, non-standard behaviour into PreparedQuery.
     *
     * @param after after
     * @param <Q> Q
     * @return prepared query
     */
    default <Q> CQueryFunction<S,Q> andQuery(Function<? super CQuery<T>, ? extends CQuery<Q>> after) {
        return s -> after.apply(apply(s));
    }

    /**
     * Inserts into pipeline {@link CQuery#fetch()} operation
     *
     * @return prepared query
     */
    default CQueryFunction<S, T> fetch() {
        return andQuery(s -> s.fetch());
    }

    /**
     * Inserts into pipeline {@link CQuery#prefetch()} operation
     *
     * @return prepared query
     */
    default CQueryFunction<S, T> prefetch() {
        return andQuery(s -> s.prefetch());
    }

    /**
     * Inserts into pipeline {@link CQuery#cache(Path)} operation
     *
     * @param filename filename
     * @return prepared query
     */
    default CQueryFunction<S, T> cache(Path filename) {
        return andQuery(CheckedFunction.sneaky(s -> s.cache(filename)));
    }

    /**
     * Inserts into pipeline {@link CQuery#append(CQuery)} operation
     *
     * @param next next
     * @return prepared query
     */
    default CQueryFunction<S, T> append(CQuery<T> next) {
        return andQuery(s -> s.append(next));
    }

    /**
     * Inserts into pipeline {@link CQuery#limit(Predicate)} operation
     *
     * @param p p
     * @return prepared query
     */
    default CQueryFunction<S, T> limit(Predicate<? super T> p) {
        return andQuery(s -> s.limit(p));
    }

    /**
     * Inserts into pipeline {@link CQuery#limit(int)} operation
     *
     * @param n n
     * @return prepared query
     */
    default CQueryFunction<S, T> limit(int n) {
        return andQuery(s -> s.limit(n));
    }

    /**
     * Inserts into pipeline {@link CQuery#skip(int)} operation
     *
     * @param n n
     * @return prepared query
     */
    default CQueryFunction<S, T> skip(int n) {
        return andQuery(s -> s.skip(n));
    }

    /**
     * Inserts into pipeline {@link CQuery#slice(int, int)} operation
     *
     * @param begin begin
     * @param end end
     * @return prepared query
     */
    default CQueryFunction<S, T> slice(int begin, int end) {
        return andQuery(s -> s.slice(begin, end));
    }

    /**
     * Inserts into pipeline {@link CQuery#filter(Predicate)} operation
     *
     * @param p p
     * @return prepared query
     */
    default CQueryFunction<S, T> filter(Predicate<? super T> p) {
        return andQuery(s -> s.filter(p));
    }

    /**
     * Inserts into pipeline {@link CQuery#filterEach(EachPredicate)} operation
     *
     * @param p p
     * @return prepared query
     */
    default CQueryFunction<S, T> filterEach(EachPredicate<? super T> p) {
        return andQuery(s -> s.filterEach(p));
    }

    /**
     * Inserts into pipeline {@link CQuery#filter(Class)} operation
     *
     * @param p p
     * @param <Q> Q
     * @return prepared query
     */
    default <Q> CQueryFunction<S, Q> filter(Class<Q> p) {
        return andQuery(s -> s.filter(p));
    }

    /**
     * Inserts into pipeline {@link CQuery#filter(IClass)} operation
     *
     * @param p p
     * @param <Q> Q
     * @return prepared query
     */
    default <Q> CQueryFunction<S, Q> filter(IClass<Q> p) {
        return andQuery(s -> s.filter(p));
    }

    /**
     * Inserts into pipeline {@link CQuery#discard(Predicate)} operation
     *
     * @param p p
     * @return prepared query
     */
    default CQueryFunction<S, T> discard(Predicate<? super T> p) {
        return andQuery(s -> s.discard(p));
    }

    /**
     * Inserts into pipeline {@link CQuery#discardEach(EachPredicate)} operation
     *
     * @param p p
     * @return prepared query
     */
    default CQueryFunction<S, T> discardEach(EachPredicate<? super T> p) {
        return andQuery(s -> s.discardEach(p));
    }

    /**
     * Inserts into pipeline {@link CQuery#discard(Class)} operation
     *
     * @param p p
     * @return prepared query
     */
    default CQueryFunction<S, T> discard(Class<?> p) {
        return andQuery(s -> s.discard(p));
    }

    /**
     * Inserts into pipeline {@link CQuery#discard(IClass)} operation
     *
     * @param p p
     * @return prepared query
     */
    default CQueryFunction<S, T> discard(IClass<?> p) {
        return andQuery(s -> s.discard(p));
    }

    /**
     * Inserts into pipeline {@link CQuery#map(Function)} operation
     *
     * @param f f
     * @param <R> R
     * @return prepared query
     */
    default <R> CQueryFunction<S,R> map(Function<? super T, R> f) {
        return andQuery(s -> s.map(f));
    }

    /**
     * Inserts into pipeline {@link CQuery#mapOptional(Function)} operation
     *
     * @param f f
     * @param <R> R
     * @return prepared query
     */
    default <R> CQueryFunction<S,R> mapOptional(Function<? super T, Ref<R>> f) {
        return andQuery(s -> s.mapOptional(f));
    }

    /**
     * Inserts into pipeline {@link CQuery#mapEach(EachFunction)} operation
     *
     * @param f f
     * @param <R> R
     * @return prepared query
     */
    default <R> CQueryFunction<S,R> mapEach(EachFunction<? super T, R> f) {
        return andQuery(s -> s.mapEach(f));
    }

    /**
     * Inserts into pipeline {@link CQuery#flat(Function)} operation
     *
     * @param f f
     * @param <R> R
     * @return prepared query
     */
    default <R> CQueryFunction<S,R> flat(Function<? super T, CQuery<R>> f) {
        return andQuery(s -> s.flat(f));
    }

    /**
     * Inserts into pipeline {@link CQuery#flatIterable(Function)} operation
     *
     * @param f f
     * @param <R> R
     * @return prepared query
     */
    default <R> CQueryFunction<S,R> flatIterable(Function<? super T, Iterable<R>> f) {
        return andQuery(s -> s.flatIterable(f));
    }

    /**
     * Inserts into pipeline {@link CQuery#flatArray(Function)} operation
     *
     * @param f f
     * @param <R> R
     * @return prepared query
     */
    default <R> CQueryFunction<S,R> flatArray(Function<? super T, R[]> f) {
        return andQuery(s -> s.flatArray(f));
    }

    /**
     * Inserts into pipeline {@link CQuery#mapIf(Predicate, Function)} operation
     *
     * @param p p
     * @param f f
     * @return prepared query
     */
    default CQueryFunction<S, T> mapIf(Predicate<? super T> p, Function<? super T, T> f) {
        return andQuery(s -> s.mapIf(p,f));
    }

    /**
     * Inserts into pipeline {@link CQuery#mapEachIf(EachPredicate, EachFunction)} operation
     *
     * @param p p
     * @param f f
     * @return prepared query
     */
    default CQueryFunction<S, T> mapEachIf(EachPredicate<? super T> p, EachFunction<? super T, T> f) {
        return andQuery(s -> s.mapEachIf(p,f));
    }

    /**
     * Inserts into pipeline {@link CQuery#mapOptionalIf(Predicate, Function)} operation
     *
     * @param p p
     * @param f f
     * @return prepared query
     */
    default CQueryFunction<S, T> mapOptionalIf(Predicate<? super T> p, Function<? super T, Ref<T>> f) {
        return andQuery(s -> s.mapOptionalIf(p,f));
    }

    /**
     * Inserts into pipeline {@link CQuery#flatIf(Predicate, Function)} operation
     *
     * @param p p
     * @param f f
     * @return prepared query
     */
    default CQueryFunction<S, T> flatIf(Predicate<? super T> p, Function<? super T, CQuery<T>> f) {
        return andQuery(s -> s.flatIf(p, f));
    }

    /**
     * Inserts into pipeline {@link CQuery#flatIterableIf(Predicate, Function)} operation
     *
     * @param p p
     * @param f f
     * @return prepared query
     */
    default CQueryFunction<S, T> flatIterableIf(Predicate<? super T> p, Function<? super T, Iterable<T>> f) {
        return andQuery(s -> s.flatIterableIf(p, f));
    }

    /**
     * Inserts into pipeline {@link CQuery#flatArrayIf(Predicate, Function)} operation
     *
     * @param p p
     * @param f f
     * @return prepared query
     */
    default CQueryFunction<S, T> flatArrayIf(Predicate<? super T> p, Function<? super T, T[]> f) {
        return andQuery(s -> s.flatArrayIf(p, f));
    }

    /**
     * Inserts into pipeline {@link CQuery#distinct()} operation
     *
     * @return prepared query
     */
    default CQueryFunction<S, T> distinct() {
        return andQuery(s -> s.distinct());
    }

    /**
     * Inserts into pipeline {@link CQuery#distinct(HashFunction)} operation
     *
     * @param eq eq
     * @return prepared query
     */
    default CQueryFunction<S, T> distinct(HashFunction<T> eq) {
        return andQuery(s -> s.distinct(eq));
    }

    /**
     * Inserts into pipeline {@link CQuery#distinct(Comparator)} operation
     *
     * @param cmp cmp
     * @return prepared query
     */
    default CQueryFunction<S, T> distinct(Comparator<? super T> cmp) {
        return andQuery(s -> s.distinct(cmp));
    }

    /**
     * Inserts into pipeline {@link CQuery#sort()} operation
     *
     * @return prepared query
     */
    default CQueryFunction<S, T> sort() {
        return andQuery(s -> s.sort());
    }

    /**
     * Inserts into pipeline {@link CQuery#sort(Comparator)} operation
     *
     * @param cmp cmp
     * @return prepared query
     */
    default CQueryFunction<S, T> sort(Comparator<? super T> cmp) {
        return andQuery(s -> s.sort(cmp));
    }

    /**
     * Inserts into pipeline {@link CQuery#reverse()} operation
     *
     * @return prepared query
     */
    default CQueryFunction<S, T> reverse() {
        return andQuery(s -> s.reverse());
    }

    /**
     * Inserts into pipeline {@link CQuery#onEach(Consumer)} operation
     *
     * @param consumer consumer
     * @return prepared query
     */
    default CQueryFunction<S, T> onEach(Consumer<? super T> consumer) {
        return andQuery(s -> s.onEach(consumer));
    }

    /**
     * Inserts into pipeline {@link CQuery#onEach(EachConsumer)} operation
     *
     * @param consumer consumer
     * @return prepared query
     */
    default CQueryFunction<S, T> onEach(EachConsumer<? super T> consumer) {
        return andQuery(s -> s.onEach(consumer));
    }

}
