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

/** Base structural evaluator. */
abstract class StructuralEvaluator<T> extends Evaluator<T> {
  final Evaluator<T> evaluator;

  public StructuralEvaluator(Evaluator<T> evaluator) {
    this.evaluator = evaluator;
  }

  /** Evaluator<T> matches the given root node */
  static class Root<T> extends Evaluator<T> {
    @Override
    public boolean matches(Node<T> root, Node<T> node) {
      return root == node;
    }
  }

  /** Evaluator<T> matches if the given evaluator matches for the nodes of the current node */
  static class Has<T> extends StructuralEvaluator<T> {
    public Has(Evaluator<T> evaluator) {
      super(evaluator);
    }

    @Override
    public boolean matches(Node<T> root, Node<T> node) {
      Nodes<T> allNodes = node.getAllNodes();
      for (Node<T> e : allNodes) {
        if (e != node && evaluator.matches(root, e)) {
          return true;
        }
      }
      return false;
    }

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

  /** Evaluator<T> matches if the given evaluator does not */
  static class Not<T> extends StructuralEvaluator<T> {
    public Not(Evaluator<T> evaluator) {
      super(evaluator);
    }

    @Override
    public boolean matches(Node<T> root, Node<T> node) {
      return !evaluator.matches(root, node);
    }

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

  /** Evaluator<T> matches if any of the nodes parents matches the given evaluator */
  static class Parent<T> extends StructuralEvaluator<T> {
    public Parent(Evaluator<T> evaluator) {
      super(evaluator);
    }

    @Override
    public boolean matches(Node<T> root, Node<T> node) {
      if (root == node) {
        return false;
      }

      Node<T> parent = node.getParent();
      while (true) {
        if (evaluator.matches(root, parent)) {
          return true;
        }
        if (parent == root) {
          break;
        }
        parent = parent.getParent();
      }
      return false;
    }

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

  /** Evaluator<T> matches if the nodes immediate parent matches the given evaluator */
  static class ImmediateParent<T> extends StructuralEvaluator<T> {
    public ImmediateParent(Evaluator<T> evaluator) {
      super(evaluator);
    }

    @Override
    public boolean matches(Node<T> root, Node<T> node) {
      if (root == node) {
        return false;
      }

      Node<T> parent = node.getParent();
      return parent != null && evaluator.matches(root, parent);
    }

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

  /** Evaluator<T> matches if any of the nodes previous siblings matches the given evaluator */
  static class PreviousSibling<T> extends StructuralEvaluator<T> {
    public PreviousSibling(Evaluator<T> evaluator) {
      super(evaluator);
    }

    @Override
    public boolean matches(Node<T> root, Node<T> node) {
      if (root == node) {
        return false;
      }

      Node<T> prev = node.previousSibling();

      while (prev != null) {
        if (evaluator.matches(root, prev)) {
          return true;
        }

        prev = prev.previousSibling();
      }
      return false;
    }

    @Override
    public String toString() {
      return String.format(":prev*(%s)", evaluator);
    }
  }

  /** Evaluator<T> matches if the nodes immediate previous sibling matches the given evaluator */
  static class ImmediatePreviousSibling<T> extends StructuralEvaluator<T> {
    public ImmediatePreviousSibling(Evaluator<T> evaluator) {
      super(evaluator);
    }

    @Override
    public boolean matches(Node<T> root, Node<T> node) {
      if (root == node) {
        return false;
      }

      Node<T> prev = node.previousSibling();
      return prev != null && evaluator.matches(root, prev);
    }

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