package org.infinispan.objectfilter.impl.predicateindex.be;

import java.util.List;

import org.infinispan.objectfilter.impl.predicateindex.FilterEvalContext;
import org.infinispan.objectfilter.impl.predicateindex.IntervalPredicate;
import org.infinispan.objectfilter.impl.predicateindex.Predicate;

/**
 * A PredicateNode is a leaf node in a BETree that holds a Predicate instance. A PredicateNode instance is never reused
 * inside the same BETree or shared between multiple BETrees, but an entire BETree could be shared by multiple filters.
 * Multiple PredicateNodes could share the same Predicate instance.
 *
 * @author anistor@redhat.com
 * @since 7.0
 */
public final class PredicateNode<AttributeId extends Comparable<AttributeId>> extends BENode {

   // the predicate can be shared by multiple PredicateNodes
   private final Predicate<?> predicate;

   /**
    * Indicates if the Predicate's condition is negated. This can be true only for condition predicates, never for
    * interval predicates.
    */
   private final boolean isNegated;

   private final List<AttributeId> attributePath;

   public PredicateNode(BENode parent, Predicate<?> predicate, boolean isNegated, List<AttributeId> attributePath) {
      super(parent);
      if (isNegated && predicate instanceof IntervalPredicate) {
         throw new IllegalArgumentException("Interval predicates should not be negated");
      }
      this.predicate = predicate;
      this.isNegated = isNegated;
      this.attributePath = attributePath;
   }

   public Predicate<?> getPredicate() {
      return predicate;
   }

   public boolean isNegated() {
      return isNegated;
   }

   public List<AttributeId> getAttributePath() {
      return attributePath;
   }

   @Override
   public void handleChildValue(BENode child, boolean childValue, FilterEvalContext evalContext) {
      if (child != null) {
         throw new IllegalArgumentException("Predicates have value but do not have children");
      }

      final int value = childValue ? BETree.EXPR_TRUE : BETree.EXPR_FALSE;

      if (isEvaluationComplete(evalContext)) {
         if (predicate.isRepeated() && evalContext.treeCounters[startIndex] == value) {
            // receiving the same value multiple times is fine if this is a repeated condition
            return;
         }
         throw new IllegalStateException("This should never be called again if the state of this node was previously decided.");
      }

      if (parent == null) {
         evalContext.treeCounters[startIndex] = value;
         suspendSubscription(evalContext);
      } else {
         parent.handleChildValue(this, childValue, evalContext);
      }
   }

   @Override
   public void suspendSubscription(FilterEvalContext ctx) {
      if (predicate.isRepeated()) {
         ctx.matcherContext.addSuspendedSubscription(predicate);
      }
   }

   @Override
   public String toString() {
      return "PredicateNode{" +
            "attributePath=" + attributePath +
            ", isNegated=" + isNegated +
            ", predicate=" + predicate +
            '}';
   }
}
