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

import lombok.RequiredArgsConstructor;
import net.ranides.assira.collection.iterators.ForwardIterator;
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.BaseEach;
import net.ranides.assira.collection.query.support.BaseIterable;
import net.ranides.assira.functional.Consumers;
import net.ranides.assira.functional.Predicates;

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;

/**
 * Stream implementation for tree traversal.
 * It uses BFS algorithm.
 *
 * It can visit any tree, as long as it can provide 3 operations:
 *   - root node
 *   - list of children of provided node
 *   - predicate returning true, if node is terminal
 *
 * @param <T> T
 */
@RequiredArgsConstructor
public class CQTreeBFS<T> extends CQueryAbstract<T> {

    protected final Supplier<T> root;

    protected final Function<T, CQuery<T>> children;

    protected final Predicate<T> isTerm;

    protected final Supplier<? extends RuntimeException> message;

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

    @Override
    public CQuery<T> message(Supplier<? extends RuntimeException> message) {
        return new CQTreeBFS<>(root, children, isTerm, message);
    }

    @Override
    public CQuery<T> parallel() {
        return this;
    }

    @Override
    public CQuery<T> sequential() {
        return this;
    }

    @Override
    public boolean isParallel() {
        return false;
    }

    @Override
    public Stream<T> stream() {
        return BaseIterable.stream(this);
    }

    @Override
    public Iterator<T> iterator() {
        return IteratorUtils.withCause(new TreeIterator(root.get()), message);
    }

    @Override
    public void forEach(Consumer<? super T> consumer) {
        BaseIterable.forEach(this, consumer);
    }

    @Override
    public void forEach(Consumers.EachConsumer<? super T> consumer) {
        BaseIterable.forEach(this, consumer);
    }

    @Override
    public boolean whileEach(Predicate<? super T> consumer) {
        return BaseIterable.whileEach(this, consumer);
    }

    @Override
    public boolean whileEach(Predicates.EachPredicate<? super T> consumer) {
        return BaseIterable.whileEach(this, consumer);
    }

    @Override
    public int size() {
        return BaseEach.size(this);
    }

    private final class TreeIterator extends ForwardIterator<T> {

        private final LinkedList<Iterator<T>> window;

        public TreeIterator(T node) {
            this.window = new LinkedList<>();
            this.window.add(Collections.singletonList(node).iterator());
        }

        @Override
        protected boolean next(Consumer<? super T> action) {
            while(!window.isEmpty()) {
                Iterator<T> nodes = window.peekFirst();
                if(nodes.hasNext()) {
                    T node = nodes.next();
                    action.accept(node);
                    if(!isTerm.test(node)) {
                        window.addLast(children.apply(node).iterator());
                    }
                    return true;
                } else {
                    window.removeFirst();
                }
            }
            return false;
        }
    }

}
