package com.github.simy4.xpath.expr;

import com.github.simy4.xpath.XmlBuilderException;
import com.github.simy4.xpath.navigator.Navigator;
import com.github.simy4.xpath.navigator.Node;
import com.github.simy4.xpath.util.Predicate;
import com.github.simy4.xpath.view.IterableNodeView;
import com.github.simy4.xpath.view.NodeView;
import com.github.simy4.xpath.view.ViewContext;

import javax.annotation.Nonnegative;
import javax.annotation.concurrent.NotThreadSafe;
import java.util.Iterator;

abstract class AbstractStepExpr extends AbstractExpr implements StepExpr {

    private final Iterable<Predicate<ViewContext<?>>> predicates;

    AbstractStepExpr(Iterable<Predicate<ViewContext<?>>> predicates) {
        this.predicates = predicates;
    }

    @Override
    public final <N extends Node> IterableNodeView<N> resolve(ViewContext<N> context) throws XmlBuilderException {
        IterableNodeView<N> result = traverseStep(context.getNavigator(), context.getCurrent());
        Iterator<Predicate<ViewContext<?>>> predicateIterator = predicates.iterator();
        final Counter counter;
        if (predicateIterator.hasNext()) {
            Predicate<ViewContext<?>> predicate = predicateIterator.next();
            final CountingPredicate countingPredicate = new CountingPredicate(predicate);
            counter = countingPredicate;
            result = result.filter(context.getNavigator(), false, countingPredicate);
            while (predicateIterator.hasNext()) {
                result = result.filter(context.getNavigator(), false, predicateIterator.next());
            }
        } else {
            counter = Counter.NO_OP;
        }

        if (context.isGreedy() && !context.hasNext() && !result.toBoolean()) {
            result = new NodeView<N>(createStepNode(context.getNavigator(), context.getCurrent()));
            predicateIterator = predicates.iterator();
            if (predicateIterator.hasNext()) {
                final int count = counter.count();
                result = result.filter(context.getNavigator(), true, count + 1, predicateIterator.next());
                while (predicateIterator.hasNext()) {
                    result = result.filter(context.getNavigator(), true, predicateIterator.next());
                }
            }
        }
        return result;
    }

    /**
     * Traverses XML nodes for the nodes that matches this step expression.
     *
     * @param navigator  XML model navigator
     * @param parentView XML node to traverse
     * @param <N>        XML node type
     * @return ordered set of matching nodes
     */
    abstract <N extends Node> IterableNodeView<N> traverseStep(Navigator<N> navigator, NodeView<N> parentView);

    /**
     * Creates new node of this step type.
     *
     * @param navigator  XML model navigator
     * @param parentView XML node modify
     * @param <N>        XML node type
     * @return newly created node
     * @throws XmlBuilderException if error occur during XML node creation
     */
    abstract <N extends Node> N createStepNode(Navigator<N> navigator, NodeView<N> parentView)
            throws XmlBuilderException;

    @Override
    public String toString() {
        final StringBuilder stringBuilder = new StringBuilder();
        for (Predicate predicate : predicates) {
            stringBuilder.append('[').append(predicate).append(']');
        }
        return stringBuilder.toString();
    }

    private interface Counter {

        Counter NO_OP = new Counter() {
            @Override
            public int count() {
                return 0;
            }
        };

        @Nonnegative
        int count();

    }

    @NotThreadSafe
    private static final class CountingPredicate implements Predicate<ViewContext<?>>, Counter {

        private final Predicate<ViewContext<?>> predicate;
        private int count;

        private CountingPredicate(Predicate<ViewContext<?>> predicate) {
            this.predicate = predicate;
        }

        @Override
        public boolean test(ViewContext<?> context) {
            count += 1;
            return predicate.test(context);
        }

        @Override
        public int count() {
            return count;
        }

    }

}

@@ -376,45 +376,8 @@
 t;%0A%0A
-import javax.annotation.Nonnegative;%0A
 impo
@@ -448,16 +448,66 @@
 terator;
+%0Aimport java.util.concurrent.atomic.AtomicInteger;
 %0A%0Aabstra
@@ -1090,21 +1090,20 @@
 nal 
-Count
+Numb
 er count
 er;%0A
@@ -1098,18 +1098,16 @@
 er count
-er
 ;%0A      
@@ -1313,49 +1313,8 @@
 e);%0A
-            counter = countingPredicate;%0A
     
@@ -1548,32 +1548,79 @@
 ;%0A            %7D%0A
+            count = countingPredicate.count();%0A
         %7D else %7B
@@ -1641,26 +1641,12 @@
 ount
-er
  = 
-Counter.NO_OP
+0
 ;%0A  
@@ -1939,59 +1939,8 @@
 ) %7B%0A
-                final int count = counter.count();%0A
     
@@ -2009,16 +2009,27 @@
 e, count
+.intValue()
  + 1, pr
@@ -3392,238 +3392,8 @@
  %7D%0A%0A
-    private interface Counter %7B%0A%0A        Counter NO_OP = new Counter() %7B%0A            @Override%0A            public int count() %7B%0A                return 0;%0A            %7D%0A        %7D;%0A%0A        @Nonnegative%0A        int count();%0A%0A    %7D%0A%0A
     
@@ -3496,17 +3496,8 @@
 %3C?%3E%3E
-, Counter
  %7B%0A%0A
@@ -3571,25 +3571,66 @@
 private 
+f
 in
-t count
+al AtomicInteger counter = new AtomicInteger(0)
 ;%0A%0A     
@@ -3841,13 +3841,28 @@
 ount
- += 1
+er.incrementAndGet()
 ;%0A  
@@ -3926,36 +3926,22 @@
     
-@Override%0A        public int
+private Number
  cou
@@ -3967,24 +3967,26 @@
 return count
+er
 ;%0A        %7D%0A

Simplify node counting for step expressions