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

import lombok.experimental.UtilityClass;
import net.ranides.assira.collection.iterators.IteratorUtils;
import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.functional.Predicates.EachPredicate;
import net.ranides.assira.generic.Ref;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Spliterator;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;

/**
 * This is internal utility class not supposed to use by clients.
 * It has default implementations of query operations using only Stream contract.
 */
@UtilityClass
@SuppressWarnings("MissingJavadoc")
public class BaseStream {

    public static <T> Iterator<T> iterator(CQuery<T> that) {
        return IteratorUtils.withCause(that.stream().iterator(), that.message());
    }

    public static <T> Spliterator<T> spliterator(CQuery<T> that) {
        return that.stream().spliterator();
    }

    public static <T> int size(CQuery<T> that) {
        return that.stream().mapToInt(e -> 1).sum();
    }

    public static <T, A, R> R collect(CQuery<T> that, Collector<? super T, A, R> collector) {
        return that.stream().collect(collector);
    }

    public static <T, A> A[] array(CQuery<T> that, IntFunction<A[]> generator) {
        return that.stream().toArray(generator);
    }

    public static <T> List<T> list(CQuery<T> that) {
        return that.stream().collect(Collectors.toList());
    }

    public static <T> Set<T> set(CQuery<T> that) {
        return that.stream().collect(Collectors.toSet());
    }

    public static <T, K> Map<K, T> group(CQuery<T> that, Function<? super T, ? extends K> key) {
        return that.stream().collect(Collectors.toMap(key, v -> v));
    }

    public static <T, K, V> Map<K, V> group(CQuery<T> that, Function<? super T, ? extends K> key, Function<? super T, ? extends V> value) {
        return that.stream().collect(Collectors.toMap(key, value));
    }

    public static <T> T reduce(CQuery<T> that, T identity, BinaryOperator<T> accumulator) {
        return that.stream().reduce(identity, accumulator);
    }

    public static <T> T reduce(CQuery<T> that, BinaryOperator<T> accumulator) {
        return that.stream().reduce(accumulator).orElse(null);
    }

    public static <T> boolean matchAny(CQuery<T> that, Predicate<? super T> predicate) {
        return that.stream().anyMatch(predicate);
    }

    public static <T> Ref<T> first(CQuery<T> that) {
        return Ref.ofOptional(that.stream().findFirst(), that.message());
    }

    public static <T> boolean whileEach(CQuery<T> that, Predicate<? super T> consumer) {
        return that.stream().allMatch(v -> consumer.test(v));
    }

    public static <T> boolean whileEach(CQuery<T> that, EachPredicate<? super T> consumer) {
        BaseState.Index index = BaseState.newIndex(that);
        return that.stream().allMatch(v -> consumer.test(index.next(), v));
    }

    public static <T> int indexOf(CQuery<T> that, EachPredicate<? super T> predicate) {
        BaseState.Holder<Integer> out = BaseState.newHolder(that, -1);
        whileEach(that, (i,v) -> {
            if(predicate.test(i,v)) {
                out.set(i);
                return false;
            }
            return true;
        });
        return out.get();
    }

    public static <T> int lastIndexOf(CQuery<T> that, EachPredicate<? super T> predicate) {
        BaseState.Holder<Integer> out = BaseState.newHolder(that, -1);
        BaseState.Index index = BaseState.newIndex(that);
        that.stream().forEach(v -> {
            int let_index = index.value();
            if(predicate.test(let_index,v)) {
                out.set(let_index);
            }
            index.next();
        });
        return out.get();
    }
}
