package edu.stanford.nlp.parser.shiftreduce;

import java.util.List;

import edu.stanford.nlp.ling.CoreLabel;
import edu.stanford.nlp.parser.common.ParserConstraint;
import edu.stanford.nlp.trees.LabeledScoredTreeNode;
import edu.stanford.nlp.trees.Tree;
import edu.stanford.nlp.trees.TreeCoreAnnotations;
import edu.stanford.nlp.util.TreeShapedStack;

/**
 * Transition that makes a unary parse node in a partially finished tree.
 */
public class UnaryTransition implements Transition {
  public final String label;

  /** root transitions are illegal in the middle of the tree, naturally */
  public final boolean isRoot;

  public UnaryTransition(String label, boolean isRoot) {
    this.label = label;
    this.isRoot = isRoot;
  }

  /**
   * Legal as long as there is at least one item on the state's stack.
   */
  public boolean isLegal(State state, List<ParserConstraint> constraints) {
    if (state.finished) {
      return false;
    }
    if (state.stack.size() == 0) {
      return false;
    }
    Tree top = state.stack.peek();
    if (top.label().value().equals(label)) {
      // Disallow unary transitions where the label doesn't change
      return false;
    }
    if (top.label().value().startsWith("@") && !label.equals(top.label().value().substring(1))) {
      return false;
    }
    if (top.children().length == 1) {
      Tree child = top.children()[0];
      if (child.children().length == 1) {
        Tree grandChild = child.children()[0];
        if (grandChild.children().length == 1) {
          // Three consecutive unary trees.  Not legal to keep adding unaries.
          // TODO: do preterminals count in that equation?
          return false;
        }
      }
    }
    if (isRoot && (state.stack.size() > 1 || !state.endOfQueue())) {
      return false;
    }
    // UnaryTransition actually doesn't care about the constraints.
    // If the constraint winds up unsatisfied, we'll get stuck and
    // have to do an "emergency transition" to fix the situation.
    return true;
  }

  /**
   * Add a unary node to the existing node on top of the stack
   */
  public State apply(State state) {
    return apply(state, 0.0);
  }

  static Tree addUnaryNode(Tree top, String label) {
    if (!(top.label() instanceof CoreLabel)) {
      throw new IllegalArgumentException("Stack should have CoreLabel nodes");
    }
    Tree newTop = createNode(top, label, top);
    return newTop;
  }

  static Tree createNode(Tree top, String label, Tree ... children) {
    CoreLabel headLabel = (CoreLabel) top.label();
    CoreLabel production = new CoreLabel();
    production.setValue(label);
    production.set(TreeCoreAnnotations.HeadWordLabelAnnotation.class, headLabel.get(TreeCoreAnnotations.HeadWordLabelAnnotation.class));
    production.set(TreeCoreAnnotations.HeadTagLabelAnnotation.class, headLabel.get(TreeCoreAnnotations.HeadTagLabelAnnotation.class));
    Tree newTop = new LabeledScoredTreeNode(production);
    for (Tree child : children) {
      newTop.addChild(child);
    }
    return newTop;
  }

  /**
   * Add a unary node to the existing node on top of the stack
   */
  public State apply(State state, double scoreDelta) {
    Tree top = state.stack.peek();
    Tree newTop = addUnaryNode(top, label);

    TreeShapedStack<Tree> stack = state.stack.pop();
    stack = stack.push(newTop);
    return new State(stack, state.transitions.push(this), state.separators, state.sentence, state.tokenPosition, state.score + scoreDelta, false);    
  }

  @Override
  public boolean equals(Object o) {
    if (o == this) {
      return true;
    }
    if (!(o instanceof UnaryTransition)) {
      return false;
    }
    String otherLabel = ((UnaryTransition) o).label;
    return label.equals(otherLabel);
  }

  @Override
  public int hashCode() {
    return 29467607 ^ label.hashCode();
  }

  @Override
  public String toString() {
    return "Unary" + (isRoot ? "*" : "") + "(" + label + ")";
  }

  private static final long serialVersionUID = 1;  
}

