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

import java.util.*;
import java.util.function.*;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import net.ranides.assira.collection.HashFunction;
import net.ranides.assira.collection.arrays.ArrayUtils;
import net.ranides.assira.collection.iterators.IteratorUtils;
import net.ranides.assira.collection.sets.CustomSet;
import net.ranides.assira.collection.sets.HashSet;
import net.ranides.assira.collection.sets.RBTreeSet;
import net.ranides.assira.functional.EachFunction;
import net.ranides.assira.functional.Fold;
import net.ranides.assira.generic.CompareUtils;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public abstract class CQueryAbstract<T> implements CQuery<T> {
    
    @Override
    public final <A, R> R collect(Collector<? super T, A, R> collector) {
        return stream().collect(collector);
    }

    @Override
    public final Object[] array() {
        return stream().toArray();
    }

    @Override
    public final <A> A[] array(IntFunction<A[]> generator) {
        return stream().toArray(generator);
    }

    @Override
    public final List<T> list() {
        return stream().collect(Collectors.toList());
    }

    @Override
    public final Set<T> set() {
        return stream().collect(Collectors.toSet());
    }

    @Override
    public CQuery<T> limit(Predicate<? super T> p) {
        return new CQIterable<>(() -> IteratorUtils.limit(iterator(), p));
    }
    
    @Override
    public CQuery<T> limit(int n) {
        return new CQBounded<>(this, 0, n);
    }

    @Override
    public CQuery<T> skip(int n) {
        return new CQBounded<>(this, n, Integer.MAX_VALUE);
    }
    
    @Override
    public CQuery<T> slice(int begin, int end) {
        return new CQBounded<>(this, begin, end-begin);
    }
    
    @Override
    public final <K> Map<K, T> group(Function<? super T, ? extends K> key) {
        return stream().collect(Collectors.toMap(key, v -> v));
    }

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

    @Override
    public final boolean isEmpty() {
        return !iterator().hasNext();
    }

    @Override
    public final CQuery<T> filter(Predicate<? super T> p) {
        return new CQIterable<>(() -> IteratorUtils.filter(iterator(), p));
    }

    @Override
    public final <R> CQuery<R> map(Function<? super T, ? extends R> f) {
        return new CQIterable<>(() -> IteratorUtils.map(iterator(), f));
    }
    
    @Override
    public final <R> CQuery<R> map(EachFunction<? super T, ? extends R> f) {
        return new CQIterable<>(() -> IteratorUtils.map(iterator(), f));
    }

    @Override
    public final <R> CQuery<R> unfold(Function<? super T, CQuery<R>> f) {
        return new CQIterable<>(() -> IteratorUtils.flat(IteratorUtils.map(iterator(), (v) -> f.apply(v).iterator())));
    }

    @Override
    public <R> CQuery<R> split(Function<? super T, R[]> f) {
        return unfold(v -> CQueryBuilder.fromArray(f.apply(v)) );
    }

    @Override
    public final T fold(T identity, BinaryOperator<T> accumulator) {
        Iterator<T> i = iterator();
        return i.hasNext() ? Fold.apply(i, accumulator::apply) : identity;
    }

    @Override
    public final T fold(BinaryOperator<T> accumulator) {
        return fold(null, accumulator);
    }

    @Override
    public final T min(Comparator<? super T> cmp) {
        return fold((a, b) -> cmp.compare(a, b) < 0 ? a : b);
    }

    @Override
    public final T max(Comparator<? super T> cmp) {
        return fold((a, b) -> cmp.compare(a, b) > 0 ? a : b);
    }

    @Override
    public final Optional<T> first() {
        return IteratorUtils.first(iterator());
    }

    @Override
    public final Optional<T> last() {
        return IteratorUtils.last(iterator());
    }

    @Override
    public final CQuery<T> distinct() {
        return new CQCollection<>(() -> new HashSet<>(iterator()));
    }

    @Override
    public final CQuery<T> distinct(HashFunction<T> eq) {
        return new CQCollection<>(() -> new CustomSet<>(iterator(), eq));
    }

    @Override
    public final CQuery<T> distinct(Comparator<? super T> cmp) {
        return new CQCollection<>(() -> new RBTreeSet<>(iterator(), cmp));
    }

    @Override
    public final CQuery<T> sort() {
        return new CQCollection<>(() -> {
            List<T> list = list();
            list.sort(CompareUtils.comparator());
            return list;
        });
    }

    @Override
    public final CQuery<T> sort(Comparator<? super T> cmp) {
        return new CQCollection<>(() -> {
            List<T> list = list();
            list.sort(cmp);
            return list;
        });
    }

    @Override
    public final void each(Consumer<? super T> consumer) {
        stream().forEach(consumer);
    }

    @Override
    public void into(Collection<? super T> collection) {
        stream().forEach(collection::add);
    }

    @Override
    public final boolean matchAny(Predicate<? super T> predicate) {
        return stream().anyMatch(predicate);
    }

    @Override
    public final boolean matchAll(Predicate<? super T> predicate) {
        return stream().allMatch(predicate);
    }

    @Override
    public final boolean matchNone(Predicate<? super T> predicate) {
        return stream().noneMatch(predicate);
    }

    protected static class CQCollection<T> extends CQueryAbstract<T> {
        
        private final Supplier<Collection<T>> source;

        public CQCollection(Supplier<Collection<T>> source) {
            this.source = source;
        }
        
        public CQCollection(Collection<T> source) {
            this(() -> source);
        }

        @Override
        public Iterator<T> iterator() {
            return source.get().iterator();
        }

        @Override
        public Spliterator<T> spliterator() {
            return source.get().spliterator();
        }

        @Override
        public Stream<T> stream() {
            return source.get().stream();
        }

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

    protected static class CQArray<T> extends CQueryAbstract<T> {
        
        private final Supplier<T[]> source;
        private final int offset;
        private final int limit;
        
        public CQArray(Supplier<T[]> source) {
            this(source, 0, Integer.MAX_VALUE);
        }
        
        public CQArray(Supplier<T[]> source, int offset, int limit) {
            this.source = source;
            this.offset = offset;
            this.limit = limit;
        }
        
        public CQArray(T[] source) {
            this(source, 0, source.length);
        }
        
        public CQArray(T[] source, int offset, int limit) {
            this(() -> source, offset,limit);
        }

        @Override
        public Iterator<T> iterator() {
            T[] array = source.get();
            return ArrayUtils.iterator(array, offset, Math.min(array.length,offset+limit));
        }

        @Override
        public Spliterator<T> spliterator() {
            T[] array = source.get();
            return Arrays.spliterator(array, offset, Math.min(array.length,offset+limit));
        }

        @Override
        public Stream<T> stream() {
            T[] array = source.get();
            return Arrays.stream(array, offset, Math.min(array.length,offset+limit));
        }

        @Override
        public int size() {
            return Math.min(limit, source.get().length);
        }
        
        @Override
        public CQuery<T> limit(int n) {
            return new CQArray<>(source, offset, Math.min(limit, n));
        }

        @Override
        public CQuery<T> skip(int n) {
            return new CQArray<>(source, offset+n, limit);
        }
        
    }
        
    protected static class CQIterable<T> extends CQueryAbstract<T> {
        
        private final Supplier<Iterable<T>> source;

        public CQIterable(Supplier<Iterable<T>> source) {
            this.source = source;
        }
        
        public CQIterable(Iterable<T> source) {
            this(() -> source);
        }

        @Override
        public Iterator<T> iterator() {
            return source.get().iterator();
        }

        @Override
        public Spliterator<T> spliterator() {
            return source.get().spliterator();
        }

        @Override
        public Stream<T> stream() {
            return StreamSupport.stream(source.get().spliterator(), false);
        }

        @Override
        public int size() {
            return IteratorUtils.size(source.get().iterator());
        }
       
    }
    
    protected static class CQQuery<T> extends CQueryAbstract<T> {
        
        private final Supplier<CQuery<T>> source;

        public CQQuery(Supplier<CQuery<T>> source) {
            this.source = source;
        }

        @Override
        public Iterator<T> iterator() {
            return source.get().iterator();
        }

        @Override
        public Spliterator<T> spliterator() {
            return source.get().spliterator();
        }

        @Override
        public Stream<T> stream() {
            return source.get().stream();
        }

        @Override
        public int size() {
            return source.get().size();
        }
       
    }
    
    protected static class CQBounded<T> extends CQueryAbstract<T> {
        
        private final CQuery<T> stream;
        private final int begin;
        private final int size;

        public CQBounded(CQuery<T> stream, int begin, int size) {
            this.stream = stream;
            this.begin = begin;
            this.size = size;
        }
        
        @Override
        public CQuery<T> limit(int n) {
            return new CQBounded<>(stream, begin, Math.min(n, size));
        }

        @Override
        public CQuery<T> skip(int n) {
            return new CQBounded<>(stream, begin+n, size);
        }

        @Override
        public Iterator<T> iterator() {
            Iterator<T> i = stream.iterator();
            IteratorUtils.next(i, begin);
            return new BIterator<>(i, size);
        }

        @Override
        public Spliterator<T> spliterator() {
            return stream().spliterator();
        }

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

        @Override
        public int size() {
            return Math.min(stream.size(), size);
        }
        
    }
    
    private static final class BIterator<T> implements Iterator<T> {
        
        private final Iterator<T> src;
        private final int limit;
        private int count;

        public BIterator(Iterator<T> src, int limit) {
            this.src = src;
            this.limit = limit;
        }

        @Override
        public boolean hasNext() {
            return count<limit && src.hasNext();
        }

        @Override
        public T next() {
            if(count>=limit) {
                throw new NoSuchElementException();
            }
            count++;
            return src.next();
        }
        
    }
    
}
