package edu.stanford.nlp.ling.tokensregex;

import edu.stanford.nlp.util.Comparators;
import java.util.function.Function;
import edu.stanford.nlp.util.HasInterval;
import edu.stanford.nlp.util.Interval;

import java.util.Comparator;
import java.util.List;
import java.util.regex.MatchResult;

/**
 * The result of a match against a sequence.
 * <p>
 * Similar to Java's {@link MatchResult} except it is for sequences
 * over arbitrary types T instead of just characters.
 * </p>
 * <p>This interface contains query methods used to determine the
 * results of a match against a regular expression against an sequence.
 * The match boundaries, groups and group boundaries can be seen
 * but not modified through a {@link SequenceMatchResult}.
 *
 * @author Angel Chang
 * @see SequenceMatcher
 */
public interface SequenceMatchResult<T> extends MatchResult, HasInterval<Integer> {
  // TODO: Need to be careful with GROUP_BEFORE_MATCH/GROUP_AFTER_MATCH
  public static int GROUP_BEFORE_MATCH = Integer.MIN_VALUE;  // Special match groups (before match)
  public static int GROUP_AFTER_MATCH = Integer.MIN_VALUE+1;   // Special match groups (after match)

  public double score();
  public double priority();

  /**
   * Returns the original sequence the match was performed on.
   * @return The list that the match was performed on
   */
  public List<? extends T> elements();

  /**
   * Returns pattern used to create this sequence match result
   * @return the SequencePattern against which this sequence match result was matched
   */
  public SequencePattern<T> pattern();

  /**
   * Returns the entire matched subsequence as a list.
   * @return the matched subsequence as a list
   * @throws  IllegalStateException
   *          If no match has yet been attempted,
   *          or if the previous match operation failed
   */
  public List<? extends T> groupNodes();

  /**
   * Returns the matched group as a list.
   * @param  group
   *         The index of a capturing group in this matcher's pattern
   * @return the matched group as a list
   * @throws  IllegalStateException
   *          If no match has yet been attempted,
   *          or if the previous match operation failed
   *
   * @throws  IndexOutOfBoundsException
   *          If there is no capturing group in the pattern
   *          with the given index
   */
  public List<? extends T> groupNodes(int group);

  public BasicSequenceMatchResult<T> toBasicSequenceMatchResult();

  // String lookup versions using variables

  /**
   * Returns the matched group as a list.
   * @param  groupVar
   *         The name of the capturing group in this matcher's pattern
   * @return the matched group as a list
   *         or <tt>null</tt> if there is no capturing group in the pattern
   *         with the given name
   * @throws  IllegalStateException
   *          If no match has yet been attempted,
   *          or if the previous match operation failed
   */
  public List<? extends T> groupNodes(String groupVar);

  /**
   * Returns the <code>String</code> representing the matched group.
   * @param  groupVar
   *         The name of the capturing group in this matcher's pattern
   * @return the matched group as a <code>String</code>
   *         or <tt>null</tt> if there is no capturing group in the pattern
   *         with the given name
   * @throws  IllegalStateException
   *          If no match has yet been attempted,
   *          or if the previous match operation failed
   */
  public String group(String groupVar);

  /**
   * Returns the start index of the subsequence captured by the given group
   * during this match.
   * @param  groupVar
   *         The name of the capturing group in this matcher's pattern
   * @return the index of the first element captured by the group,
   *         or <tt>-1</tt> if the match was successful but the group
   *         itself did not match anything
   *         or if there is no capturing group in the pattern
   *         with the given name
   * @throws  IllegalStateException
   *          If no match has yet been attempted,
   *          or if the previous match operation failed
   */
  public int start(String groupVar);

  /**
   * Returns the index of the next element after the subsequence captured by the given group
   * during this match.
   * @param  groupVar
   *         The name of the capturing group in this matcher's pattern
   * @return the index of the next element after the subsequence captured by the group,
   *         or <tt>-1</tt> if the match was successful but the group
   *         itself did not match anything
   *         or if there is no capturing group in the pattern
   *         with the given name
   * @throws  IllegalStateException
   *          If no match has yet been attempted,
   *          or if the previous match operation failed
   */
  public int end(String groupVar);

  public int getOrder();

  /**
   * Returns an Object representing the result for the match for a particular node.
   * (actual Object returned depends on the type T of the nodes.  For instance,
   *  for a CoreMap, the match result is returned as a {@code Map<Class, Object>}, while
   *  for String, the match result is typically a MatchResult.
   *
   * @param  index The index of the element in the original sequence.
   * @return The match result associated with the node at the given index.
   * @throws  IllegalStateException
   *          If no match has yet been attempted,
   *          or if the previous match operation failed
   * @throws  IndexOutOfBoundsException
   *          If the index is out of range
   */
  public Object nodeMatchResult(int index);

  /**
   * Returns an Object representing the result for the match for a particular node in a group.
   * (actual Object returned depends on the type T of the nodes.  For instance,
   *  for a CoreMap, the match result is returned as a {@code Map<Class, Object>}, while
   *  for String, the match result is typically a MatchResult.
   *
   * @param  groupid The index of a capturing group in this matcher's pattern
   * @param  index The index of the element in the captured subsequence.
   * @return the match result associated with the node
   *         at the given index for the captured group.
   * @throws  IllegalStateException
   *          If no match has yet been attempted,
   *          or if the previous match operation failed
   * @throws  IndexOutOfBoundsException
   *          If there is no capturing group in the pattern
   *          with the given groupid or if the index is out of range
   */
  public Object groupMatchResult(int groupid, int index);

  /**
   * Returns an Object representing the result for the match for a particular node in a group.
   * (actual Object returned depends on the type T of the nodes.  For instance,
   *  for a CoreMap, the match result is returned as a {@code Map<Class, Object>}, while
   *  for String, the match result is typically a MatchResult.
   *
   * @param  groupVar The name of the capturing group in this matcher's pattern
   * @param  index The index of the element in the captured subsequence.
   * @return the match result associated with the node
   *         at the given index for the captured group.
   * @throws  IllegalStateException
   *          If no match has yet been attempted,
   *          or if the previous match operation failed
   * @throws  IndexOutOfBoundsException
   *          if the index is out of range
   */
  public Object groupMatchResult(String groupVar, int index);

  /**
   * Returns a list of Objects representing the match results for the entire sequence.
   *
   * @return the list of match results associated with the entire sequence
   * @throws  IllegalStateException
   *          If no match has yet been attempted,
   *          or if the previous match operation failed
   */
  public List<Object> groupMatchResults();

  /**
   * Returns a list of Objects representing the match results for the nodes in the group.
   *
   * @param  group
   *         The index of a capturing group in this matcher's pattern
   * @return the list of match results associated with the nodes
   *         for the captured group.
   * @throws  IllegalStateException
   *          If no match has yet been attempted,
   *          or if the previous match operation failed
   * @throws  IndexOutOfBoundsException
   *          If there is no capturing group in the pattern
   *          with the given index
   */
  public List<Object> groupMatchResults(int group);

  /**
   * Returns a list of Objects representing the match results for the nodes in the group.
   *
   * @param  groupVar
   *         The name of the capturing group in this matcher's pattern
   * @return the list of match results associated with the nodes
   *         for the captured group.
   * @throws  IllegalStateException
   *          If no match has yet been attempted,
   *          or if the previous match operation failed
   */
  public List<Object> groupMatchResults(String groupVar);

  /**
   * Returns the value (some Object) associated with the entire matched sequence.
   *
   * @return value associated with the matched sequence.
   * @throws  IllegalStateException
   *          If no match has yet been attempted,
   *          or if the previous match operation failed
   */
  public Object groupValue();

  /**
   * Returns the value (some Object) associated with the captured group.
   *
   * @param  group
   *         The index of a capturing group in this matcher's pattern
   * @return value associated with the captured group.
   * @throws  IllegalStateException
   *          If no match has yet been attempted,
   *          or if the previous match operation failed
   */
  public Object groupValue(int group);

  /**
   * Returns the value (some Object) associated with the captured group.
   *
   * @param  var
   *         The name of the capturing group in this matcher's pattern
   * @return value associated with the captured group.
   * @throws  IllegalStateException
   *          If no match has yet been attempted,
   *          or if the previous match operation failed
   */
  public Object groupValue(String var);

  public MatchedGroupInfo<T> groupInfo();
  public MatchedGroupInfo<T> groupInfo(int group);
  public MatchedGroupInfo<T> groupInfo(String var);

  public static final GroupToIntervalFunc TO_INTERVAL = new GroupToIntervalFunc(0);
  public static class GroupToIntervalFunc<MR extends MatchResult> implements Function<MR, Interval<Integer>> {
    int group;
    public GroupToIntervalFunc(int group) { this.group = group; }
    public Interval<Integer> apply(MR in) {
      return Interval.toInterval(in.start(group), in.end(group), Interval.INTERVAL_OPEN_END);
    }
  }

  public final static Comparator<MatchResult> PRIORITY_COMPARATOR = (e1, e2) -> {
    double s1 = 0;
    if (e1 instanceof SequenceMatchResult) { s1 =  ((SequenceMatchResult) e1).priority(); }
    double s2 = 0;
    if (e2 instanceof SequenceMatchResult) { s2 =  ((SequenceMatchResult) e2).priority(); }
    if (s1 == s2) {
      return 0;
    } else {
      return (s1 > s2)? -1:1;
    }
  };

  public final static Comparator<MatchResult> SCORE_COMPARATOR = (e1, e2) -> {
    double s1 = 0;
    if (e1 instanceof SequenceMatchResult) { s1 =  ((SequenceMatchResult) e1).score(); }
    double s2 = 0;
    if (e2 instanceof SequenceMatchResult) { s2 =  ((SequenceMatchResult) e2).score(); }
    if (s1 == s2) {
      return 0;
    } else {
      return (s1 > s2)? -1:1;
    }
  };

  public final static Comparator<MatchResult> ORDER_COMPARATOR =
      (e1, e2) -> {
        int o1 = 0;
        if (e1 instanceof SequenceMatchResult) {o1 =  ((SequenceMatchResult) e1).getOrder(); }
        int o2 = 0;
        if (e2 instanceof SequenceMatchResult) {o2 =  ((SequenceMatchResult) e2).getOrder(); }
        if (o1 == o2) {
          return 0;
        } else {
          return (o1 < o2)? -1:1;
        }
      };

  // Compares two match results.
  // Use to order match results by:
  //    length (longest first),
  public final static Comparator<MatchResult> LENGTH_COMPARATOR =
      (e1, e2) -> {
        int len1 = e1.end() - e1.start();
        int len2 = e2.end() - e2.start();
        if (len1 == len2) {
          return 0;
        } else {
          return (len1 > len2)? -1:1;
        }
      };

  public final static Comparator<MatchResult> OFFSET_COMPARATOR =
      (e1, e2) -> {
        if (e1.start() == e2.start()) {
          if (e1.end() == e2.end()) {
            return 0;
          } else {
            return (e1.end() < e2.end())? -1:1;
          }
        } else {
          return (e1.start() < e2.start())? -1:1;
        }
      };

  // Compares two match results.
  // Use to order match results by:
  //   priority (highest first),
  //    score (highest first),
  //    length (longest first),
  //       and then beginning token offset (smaller offset first)
  //    original order (smaller first)
  public final static Comparator<MatchResult> PRIORITY_SCORE_LENGTH_ORDER_OFFSET_COMPARATOR =
          Comparators.chain(PRIORITY_COMPARATOR, SCORE_COMPARATOR, LENGTH_COMPARATOR, ORDER_COMPARATOR, OFFSET_COMPARATOR);
  public final static Comparator<? super MatchResult> DEFAULT_COMPARATOR = PRIORITY_SCORE_LENGTH_ORDER_OFFSET_COMPARATOR;
  public final static Function<MatchResult, Double> SCORER = in -> {
    if (in instanceof SequenceMatchResult) { return  ((SequenceMatchResult) in).score(); }
    else return 0.0;
  };

  /**
   * Information about a matched group
   * @param <T>
   */
  public final static class MatchedGroupInfo<T> {
    public final String text;
    public final List<? extends T> nodes;
    public final List<Object> matchResults;
    public final Object value;
    public final String varName;

    public MatchedGroupInfo(String text, List<? extends T> nodes, List<Object> matchResults, Object value, String varName) {
      this.text = text;
      this.nodes = nodes;
      this.matchResults = matchResults;
      this.value = value;
      this.varName = varName;
    }
  }
}
