package edu.stanford.nlp.semgraph.semgrex;

import java.util.*;

import edu.stanford.nlp.ling.IndexedWord;
import edu.stanford.nlp.semgraph.SemanticGraph;

/** @author Chloe Kiddon */
public class CoordinationPattern extends SemgrexPattern {

  /**
   * 
   */
  private static final long serialVersionUID = -3122330899634961002L;
  private boolean isConj;
  private boolean isNodeCoord;
  private List<SemgrexPattern> children;

  /* if isConj is true, then it is an "AND" ; if it is false, it is an "OR".*/
  /* if isNodeCoord is true, then it is a node coordination conj; if it is false, then
           * 	it is a relation coordination conj. */
  public CoordinationPattern(boolean isNodeCoord, List<SemgrexPattern> children, boolean isConj) {
    if (children.size() < 2) {
      throw new RuntimeException("Coordination node must have at least 2 children.");
    }
    this.children = children;
    this.isConj = isConj;
    this.isNodeCoord = isNodeCoord;
  }

  public boolean isNodeCoord() { return isNodeCoord; }

  @Override
  public void setChild(SemgrexPattern child) {
    if (isNodeCoord) {
      for (Object c : children) {
        if (c instanceof NodePattern)
          ((NodePattern)c).setChild(child);
      }
    } else {

    }
  }

  public void addRelnToNodeCoord(SemgrexPattern child) {
    if (isNodeCoord) {
      for (SemgrexPattern c : children) {
        List<SemgrexPattern> newChildren = new ArrayList<SemgrexPattern>();
        newChildren.addAll(c.getChildren());
        newChildren.add(child);
        c.setChild(new CoordinationPattern(false, newChildren, true));
      }
    }
  }

  @Override
  public List<SemgrexPattern> getChildren() {
    return children;
  }

  @Override
  public String localString() {
    StringBuilder sb = new StringBuilder();
    if (isNegated()) {
      sb.append('!');
    }
    if (isOptional()) {
      sb.append('?');
    }
    sb.append((isConj ? "and" : "or"));
    sb.append(" ");
    sb.append((isNodeCoord ? "node coordination" : "reln coordination"));
    return sb.toString();
  }

  @Override
  public String toString() {
    return toString(true);
  }

  @Override
  public String toString(boolean hasPrecedence) {
    StringBuilder sb = new StringBuilder();
    if (isConj) {
      for (Iterator<SemgrexPattern> iter = children.iterator(); iter.hasNext();) {
        SemgrexPattern node = iter.next();
        sb.append(node.toString());
      }
    } else {
      sb.append('[');
      for (Iterator<SemgrexPattern> iter = children.iterator(); iter.hasNext();) {
        SemgrexPattern node = iter.next();
        sb.append(node.toString());
        if (iter.hasNext()) {
          sb.append(" |");
        }
      }
      sb.append(']');
    }
    return sb.toString();
  }

  @Override
  public SemgrexMatcher matcher(SemanticGraph sg, IndexedWord node,
                                Map<String, IndexedWord> namesToNodes, 
                                Map<String, String> namesToRelations,
                                VariableStrings variableStrings, 
                                boolean ignoreCase) {
    return new CoordinationMatcher(this, sg, null, null, true, node, 
                                   namesToNodes, namesToRelations,
                                   variableStrings, ignoreCase);
  }

  @Override
  public SemgrexMatcher matcher(SemanticGraph sg, 
                                Alignment alignment, SemanticGraph sg_align,
                                boolean hypToText, IndexedWord node, 
                                Map<String, IndexedWord> namesToNodes,
                                Map<String, String> namesToRelations, 
                                VariableStrings variableStrings, 
                                boolean ignoreCase) {
    return new CoordinationMatcher(this, sg, alignment, sg_align, 
                                   hypToText, node,
                                   namesToNodes, namesToRelations, 
                                   variableStrings, ignoreCase);
  }


  private static class CoordinationMatcher extends SemgrexMatcher {
    private SemgrexMatcher[] children;
    private final CoordinationPattern myNode;
    private int currChild;
    private final boolean considerAll;
    private IndexedWord nextNodeMatch = null;
    // do all con/dis-juncts have to be considered to determine a match?
    // i.e. true if conj and not negated or disj and negated

    public CoordinationMatcher(CoordinationPattern c, SemanticGraph sg, Alignment alignment,
                               SemanticGraph sg_align, boolean hypToText, IndexedWord n,
                               Map<String, IndexedWord> namesToNodes,
                               Map<String, String> namesToRelations, VariableStrings variableStrings,
                               boolean ignoreCase) {
      super(sg, alignment, sg_align, hypToText, n, namesToNodes, namesToRelations, variableStrings);
      myNode = c;
      children = new SemgrexMatcher[myNode.children.size()];
      for (int i = 0; i < children.length; i++) {
        SemgrexPattern node = myNode.children.get(i);
        children[i] = node.matcher(sg, alignment, sg_align, hypToText,
                                   n, namesToNodes,
                                   namesToRelations, variableStrings, ignoreCase);
      }
      currChild = 0;
      considerAll = myNode.isConj ^ myNode.isNegated();
    }

    @Override
    void resetChildIter() {
      currChild = 0;
      for (int i = 0; i < children.length; i++) {
        children[i].resetChildIter();
      }
      nextNodeMatch = null;
    }

    @Override
    void resetChildIter(IndexedWord node) {
      // this.tree = node;
      currChild = 0;
      for (int i = 0; i < children.length; i++) {
        children[i].resetChildIter(node);
      }
    }

    // find the next local match
    @Override
    public boolean matches() {  // also known as "FUN WITH LOGIC"

      //System.err.println(myNode.toString());
      //System.err.println("consider all: " + considerAll);
      if (considerAll) {
        // these are the cases where all children must be considered to match
        if (currChild < 0) {
          // a past call to this node either got that it failed
          // matching or that it was a negative match that succeeded,
          // which we only want to accept once
          return myNode.isOptional();
        }

        // we must have happily reached the end of a match the last
        // time we were here
        if (currChild == children.length) {
          --currChild;
        }

        while (true) {
          if (myNode.isNegated() != children[currChild].matches()) {
            // This node is set correctly.  Move on to the next node
            ++currChild;

            if (currChild == children.length) {
              // yay, all nodes matched.
              if (myNode.isNegated()) {
                // a negated node should only match once (before being reset)
                currChild = -1;
              } else if (myNode.isNodeCoord) {
                nextNodeMatch = children[0].getMatch();
              }
              return true;
            }
          } else {
            // oops, this didn't work.
            children[currChild].resetChildIter();
            // go backwards to see if we can continue matching from an
            // earlier location.
            // TODO: perhaps there should be a version where we only
            // care about new assignments to the root, or new
            // assigments to the root and variables, in which case we
            // could make use of getChangesVariables() to optimize how
            // many nodes we can skip past
            --currChild;
            if (currChild < 0) {
              return myNode.isOptional();
            }
          }
        }
      } else { // these are the cases where a single child node can make you match
        for (; currChild < children.length; currChild++) {
          //   	namesToNodes.putAll(namesToNodesOld);
          //    namesToRelations.putAll(namesToRelationsOld);
          if (myNode.isNegated() != children[currChild].matches()) {
            // a negated node should only match once (before being reset)
            if (myNode.isNegated()) {
              currChild = children.length;
            }
            if (myNode.isNodeCoord)
              nextNodeMatch = children[currChild].getMatch();
            //    this.namesToNodes.putAll(children[currChild].namesToNodes);
            //   this.namesToRelations.putAll(children[currChild].namesToRelations);
            return true;
          }
          children[currChild].resetChildIter();
        }
        if (myNode.isNegated()) {
          currChild = children.length;
        }
        return myNode.isOptional();
      }
    }

    @Override
    public IndexedWord getMatch() {
      if (myNode.isNodeCoord && !myNode.isNegated()) {
        return nextNodeMatch;
      } else {
        throw new UnsupportedOperationException();
      }
    }

    @Override
    public String toString() {
      String ret = "coordinate matcher for: ";
      for (SemgrexMatcher child : children)
        ret += child.toString() + " ";
      return ret;
    }

  }



}
