// Dstl (c) Crown Copyright 2017
package uk.gov.dstl.baleen.uima.utils.select;

import java.util.ArrayList;
import java.util.Collection;

import org.apache.commons.lang3.StringUtils;

/** Base combining (and, or) evaluator. */
abstract class CombiningEvaluator<T> extends Evaluator<T> {
  final ArrayList<Evaluator<T>> evaluators;
  int num = 0;

  /** Construct an empty combining evaluator */
  CombiningEvaluator() {
    super();
    evaluators = new ArrayList<>();
  }

  /**
   * Construct a combining evaluator over the given evaluators.
   *
   * @param evaluators the evaluators to combine
   */
  CombiningEvaluator(Collection<Evaluator<T>> evaluators) {
    this();
    this.evaluators.addAll(evaluators);
    updateNumEvaluators();
  }

  /**
   * Construct a combining evaluator over the given evaluators.
   *
   * @param left the first evaluator to conbine
   * @param right the second evaluator to combine
   */
  CombiningEvaluator(Evaluator<T> left, Evaluator<T> right) {
    this();
    this.evaluators.add(left);
    this.evaluators.add(right);
    updateNumEvaluators();
  }

  protected Evaluator<T> rightMostEvaluator() {
    return num > 0 ? evaluators.get(num - 1) : null;
  }

  protected void replaceRightMostEvaluator(Evaluator<T> replacement) {
    evaluators.set(num - 1, replacement);
  }

  protected void updateNumEvaluators() {
    // used so we don't need to bash on size() for every match test
    num = evaluators.size();
  }

  /**
   * And combining evaluator.
   *
   * <p>All evaluators must pass
   */
  static final class And<T> extends CombiningEvaluator<T> {

    /**
     * Construct an AND evaluator over the given evaluators.
     *
     * @param evaluators the evaluators to AND
     */
    And(Collection<Evaluator<T>> evaluators) {
      super(evaluators);
    }

    /**
     * Construct an AND evaluator over the given evaluators.
     *
     * @param left the first evaluator to AND
     * @param right the second evaluator to AND
     */
    And(Evaluator<T> left, Evaluator<T> right) {
      super(left, right);
    }

    @Override
    public boolean matches(Node<T> root, Node<T> node) {
      for (int i = 0; i < num; i++) {
        Evaluator<T> s = evaluators.get(i);
        if (!s.matches(root, node)) {
          return false;
        }
      }
      return true;
    }

    @Override
    public String toString() {
      return StringUtils.join(evaluators, " ");
    }
  }

  /**
   * And combining evaluator.
   *
   * <p>Any of the evaluators must pass.
   *
   * <p>This is a short cutting orperation, so the right evaluator will not be evaluated if the left
   * evaluator is satisfied.
   */
  static final class Or<T> extends CombiningEvaluator<T> {

    /**
     * Create a new Or evaluator. The initial evaluators are ANDed together and used as the first
     * clause of the OR.
     *
     * @param evaluators initial OR clause (these are wrapped into an AND evaluator).
     */
    Or(Collection<Evaluator<T>> evaluators) {
      super();
      if (num > 1) {
        this.evaluators.add(new And<>(evaluators));
      } else {
        this.evaluators.addAll(evaluators);
      }
      updateNumEvaluators();
    }

    /** Create a new empty Or evaluator. */
    Or() {
      super();
    }

    /**
     * Create a new Or evaluator with the given leaf and right parts of the clause.
     *
     * @param left the left clause to evaluate (first)
     * @param right the right clause to evaluate
     */
    Or(Evaluator<T> left, Evaluator<T> right) {
      super(left, right);
    }

    /**
     * Add an evaluator to the or clause
     *
     * @param e the evaluator to add
     */
    public void add(Evaluator<T> e) {
      evaluators.add(e);
      updateNumEvaluators();
    }

    @Override
    public boolean matches(Node<T> root, Node<T> node) {
      for (int i = 0; i < num; i++) {
        Evaluator<T> s = evaluators.get(i);
        if (s.matches(root, node)) {
          return true;
        }
      }
      return false;
    }

    @Override
    public String toString() {
      return String.format(":or%s", evaluators);
    }
  }
}
