package com.github.simy4.xpath.expr;

import com.github.simy4.xpath.XmlBuilderException;
import com.github.simy4.xpath.expr.axis.AxisResolver;
import com.github.simy4.xpath.navigator.Node;
import com.github.simy4.xpath.util.FilteringIterator;
import com.github.simy4.xpath.util.Function;
import com.github.simy4.xpath.util.Predicate;
import com.github.simy4.xpath.util.TransformingIterator;
import com.github.simy4.xpath.view.IterableNodeView;
import com.github.simy4.xpath.view.NodeSetView;
import com.github.simy4.xpath.view.NodeView;
import com.github.simy4.xpath.view.View;
import com.github.simy4.xpath.view.ViewContext;

import java.util.Iterator;

public class AxisStepExpr implements StepExpr {

    private final AxisResolver axisResolver;
    private final Iterable<Expr> predicates;

    public AxisStepExpr(AxisResolver axisResolver, Iterable<Expr> predicates) {
        this.axisResolver = axisResolver;
        this.predicates = predicates;
    }

    @Override
    public final <N extends Node> IterableNodeView<N> resolve(ViewContext<N> context) throws XmlBuilderException {
        IterableNodeView<N> result = axisResolver.resolveAxis(context);
        for (Expr predicate : predicates) {
            result = resolvePredicate(context, result, predicate);
        }
        return result;
    }

    private <N extends Node> IterableNodeView<N> resolvePredicate(final ViewContext<N> context,
                                                                  final IterableNodeView<N> filtered,
                                                                  final Expr predicate) throws XmlBuilderException {
        final PredicateContext<N> predicateContext = new PredicateContext<N>();
        IterableNodeView<N> result = new NodeSetView<N>(new Iterable<NodeView<N>>() {
            @Override
            public Iterator<NodeView<N>> iterator() {
                final Iterator<NodeView<N>> iterator = filtered.iterator();
                return new FilteringIterator<NodeView<N>>(
                        new TransformingIterator<NodeView<N>, NodeView<N>>(iterator, predicateContext),
                        new PredicateResolver<N>(context, iterator, predicate));
            }
        });
        if (context.isGreedy() && !context.hasNext() && !result.toBoolean()) {
            final NodeView<N> last = predicateContext.last;
            final int position = predicateContext.position;
            final NodeView<N> newNode;
            final ViewContext<N> newContext;
            if (null != last && last.isNew()) {
                newNode = last;
                newContext = new ViewContext<N>(context.getNavigator(), last, true, false, position);
            } else {
                newNode = axisResolver.createAxisNode(context);
                newContext = new ViewContext<N>(context.getNavigator(), newNode, true, false, position + 1);
            }
            final View<N> resolve = predicate.resolve(newContext);
            if (!resolve.toBoolean()) {
                throw new XmlBuilderException("Unable to satisfy expression predicate: " + predicate);
            }
            result = newNode;
        }
        return result;
    }

    @Override
    public String toString() {
        final StringBuilder stringBuilder = new StringBuilder(axisResolver.toString());
        for (Expr predicate : predicates) {
            stringBuilder.append(predicate);
        }
        return stringBuilder.toString();
    }

    private static final class PredicateResolver<T extends Node> implements Predicate<NodeView<T>> {

        private final ViewContext<T> parentContext;
        private final Iterator<NodeView<T>> iterator;
        private final Expr predicate;
        private int position = 1;

        private PredicateResolver(ViewContext<T> parentContext, Iterator<NodeView<T>> iterator, Expr predicate) {
            this.parentContext = parentContext;
            this.iterator = iterator;
            this.predicate = predicate;
        }

        @Override
        public boolean test(NodeView<T> view) {
            final ViewContext<T> context = new ViewContext<T>(parentContext.getNavigator(), view, false,
                    iterator.hasNext(), position++);
            return predicate.resolve(context).toBoolean();
        }

    }

    private static final class PredicateContext<T extends Node> implements Function<NodeView<T>, NodeView<T>> {

        private NodeView<T> last;
        private int position;

        @Override
        public NodeView<T> apply(NodeView<T> view) {
            if (null == last || !last.isNew()) {
                last = view;
                position++;
            }
            return view;
        }

    }

}

@@ -275,119 +275,17 @@
 til.
-Function;%0Aimport com.github.simy4.xpath.util.Predicate;%0Aimport com.github.simy4.xpath.util.TransformingIterator
+Predicate
 ;%0Aim
@@ -1900,111 +1900,16 @@
 N%3E%3E(
-%0A                        new TransformingIterator%3CNodeView%3CN%3E, NodeView%3CN%3E%3E(iterator, predicateContext)
+iterator
 ,%0A  
@@ -1963,16 +1963,34 @@
 context,
+ predicateContext,
  iterato
@@ -3424,32 +3424,92 @@
  parentContext;%0A
+        private final PredicateContext%3CT%3E predicateContext;%0A
         private 
@@ -3682,16 +3682,88 @@
 Context,
+ PredicateContext%3CT%3E predicateContext,%0A                                 
  Iterato
@@ -3845,32 +3845,86 @@
  parentContext;%0A
+            this.predicateContext = predicateContext;%0A
             this
@@ -4054,32 +4054,229 @@
 View%3CT%3E view) %7B%0A
+            if (null == predicateContext.last %7C%7C !predicateContext.last.isNew()) %7B%0A                predicateContext.last = view;%0A                predicateContext.position = position;%0A            %7D%0A
             fina
@@ -4561,54 +4561,8 @@
 ode%3E
- implements Function%3CNodeView%3CT%3E, NodeView%3CT%3E%3E
  %7B%0A%0A
@@ -4630,235 +4630,8 @@
 n;%0A%0A
-        @Override%0A        public NodeView%3CT%3E apply(NodeView%3CT%3E view) %7B%0A            if (null == last %7C%7C !last.isNew()) %7B%0A                last = view;%0A                position++;%0A            %7D%0A            return view;%0A        %7D%0A%0A
     

Simplify predicate resolution by putting PredicateContext directly into PredicateResolver class.