// Path: strategy/src/main/java/com/msd/gin/halyard/strategy/HalyardEvaluationStrategy.java
// public static final class ServiceRoot extends QueryRoot {
//     private static final long serialVersionUID = 7052207623408379003L;
// 
//     public final TupleExpr originalServiceArgs;
// 
//     public ServiceRoot(TupleExpr serviceArgs) {
//         super(serviceArgs.clone());
//         this.originalServiceArgs = serviceArgs;
//     }
// }


import com.msd.gin.halyard.strategy.HalyardEvaluationStrategy.ServiceRoot;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
import org.eclipse.rdf4j.common.iteration.ConvertingIteration;
import org.eclipse.rdf4j.common.iteration.FilterIteration;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.model.vocabulary.SESAME;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.Dataset;
import org.eclipse.rdf4j.query.QueryEvaluationException;
import org.eclipse.rdf4j.query.algebra.Filter;
import org.eclipse.rdf4j.query.algebra.LeftJoin;
import org.eclipse.rdf4j.query.algebra.QueryModelNode;
import org.eclipse.rdf4j.query.algebra.Service;
import org.eclipse.rdf4j.query.algebra.StatementPattern;
import org.eclipse.rdf4j.query.algebra.Var;
import org.eclipse.rdf4j.query.algebra.evaluation.QueryBindingSet;
import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource;
import org.eclipse.rdf4j.query.algebra.helpers.AbstractQueryModelVisitor;

    private final Dataset dataset;
    private final TripleSource tripleSource;
    //a map of query model nodes and their priority
    private static final Map<IdentityWrapper<QueryModelNode>, Integer> PRIORITY_MAP_CACHE = Collections.synchronizedMap(new WeakHashMap<>());
    private static final PriorityQueue<PipeAndIteration> PRIORITY_QUEUE = new PriorityQueue<>();

    /**
     * Queues a binding set and {@code QueryModelNode} for evaluation using the current priority.
     * @param pipe the pipe that evaluation results are returned on
     * @param iter
     * @param node an implementation of any {@QueryModelNode} sub-type, typically a {@code ValueExpression}, {@Code UpdateExpression} or {@TupleExpression}
     */
    static void enqueue(HalyardTupleExprEvaluation.BindingSetPipe pipe,  CloseableIteration<BindingSet, QueryEvaluationException> iter, QueryModelNode node) {
        int priority = getPriorityForNode(node);
        PRIORITY_QUEUE.put(priority, new PipeAndIteration(pipe, iter, priority));
    }

    /**
     * Get the priority of this node from the PRIORITY_MAP_CACHE or determine the priority and then cache it. Also caches priority for sub-nodes of {@code node}
     * @param node the node that you want the priority for
     * @return the priority of the node, a count of the number of child nodes of {@code node}.
     */
    private static int getPriorityForNode(final QueryModelNode node) {
        Integer p = PRIORITY_MAP_CACHE.get(new IdentityWrapper<>(node));
        if (p != null) {
            return p;
        } else {
            QueryModelNode root = node;
            while (root.getParentNode() != null) root = root.getParentNode(); //traverse to the root of the query model
// Path: strategy/src/main/java/com/msd/gin/halyard/strategy/HalyardEvaluationStrategy.java
// public static final class ServiceRoot extends QueryRoot {
//     private static final long serialVersionUID = 7052207623408379003L;
// 
//     public final TupleExpr originalServiceArgs;
// 
//     public ServiceRoot(TupleExpr serviceArgs) {
//         super(serviceArgs.clone());
//         this.originalServiceArgs = serviceArgs;
//     }
// }


// Path: strategy/src/main/java/com/msd/gin/halyard/strategy/HalyardStatementPatternEvaluation.java
import com.msd.gin.halyard.strategy.HalyardEvaluationStrategy.ServiceRoot;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
import org.eclipse.rdf4j.common.iteration.ConvertingIteration;
import org.eclipse.rdf4j.common.iteration.FilterIteration;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.model.vocabulary.SESAME;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.Dataset;
import org.eclipse.rdf4j.query.QueryEvaluationException;
import org.eclipse.rdf4j.query.algebra.Filter;
import org.eclipse.rdf4j.query.algebra.LeftJoin;
import org.eclipse.rdf4j.query.algebra.QueryModelNode;
import org.eclipse.rdf4j.query.algebra.Service;
import org.eclipse.rdf4j.query.algebra.StatementPattern;
import org.eclipse.rdf4j.query.algebra.Var;
import org.eclipse.rdf4j.query.algebra.evaluation.QueryBindingSet;
import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource;
import org.eclipse.rdf4j.query.algebra.helpers.AbstractQueryModelVisitor;


    private final Dataset dataset;
    private final TripleSource tripleSource;
    //a map of query model nodes and their priority
    private static final Map<IdentityWrapper<QueryModelNode>, Integer> PRIORITY_MAP_CACHE = Collections.synchronizedMap(new WeakHashMap<>());
    private static final PriorityQueue<PipeAndIteration> PRIORITY_QUEUE = new PriorityQueue<>();

    /**
     * Queues a binding set and {@code QueryModelNode} for evaluation using the current priority.
     * @param pipe the pipe that evaluation results are returned on
     * @param iter
     * @param node an implementation of any {@QueryModelNode} sub-type, typically a {@code ValueExpression}, {@Code UpdateExpression} or {@TupleExpression}
     */
    static void enqueue(HalyardTupleExprEvaluation.BindingSetPipe pipe,  CloseableIteration<BindingSet, QueryEvaluationException> iter, QueryModelNode node) {
        int priority = getPriorityForNode(node);
        PRIORITY_QUEUE.put(priority, new PipeAndIteration(pipe, iter, priority));
    }

    /**
     * Get the priority of this node from the PRIORITY_MAP_CACHE or determine the priority and then cache it. Also caches priority for sub-nodes of {@code node}
     * @param node the node that you want the priority for
     * @return the priority of the node, a count of the number of child nodes of {@code node}.
     */
    private static int getPriorityForNode(final QueryModelNode node) {
        Integer p = PRIORITY_MAP_CACHE.get(new IdentityWrapper<>(node));
        if (p != null) {
            return p;
        } else {
            QueryModelNode root = node;
            while (root.getParentNode() != null) root = root.getParentNode(); //traverse to the root of the query model
            final AtomicInteger counter = new AtomicInteger(root instanceof ServiceRoot ? getPriorityForNode(((ServiceRoot)root).originalServiceArgs) : 0); //starting priority for ServiceRoot must be evaluated from the original service args node