package edu.stanford.nlp.trees;

import edu.stanford.nlp.ling.HasIndex;
import edu.stanford.nlp.ling.Label;
import edu.stanford.nlp.util.XMLUtils;

/**
 * An individual dependency between a head and a dependent. The dependency
 * is associated with the token indices of the lexical items.
 * <p>
 * A key difference between this class and UnnamedDependency is the equals()
 * method. Equality of two UnnamedConcreteDependency objects is defined solely
 * with respect to the indices. The surface forms are not considered. This permits 
 * a use case in which dependencies in two different parse trees have slightly different 
 * pre-processing, possibly due to pre-processing.
 * 
 * @author Spence Green
 * 
 */
public class UnnamedConcreteDependency extends UnnamedDependency {

  private static final long serialVersionUID = -8836949694741145222L;

  private final int headIndex;
  private final int depIndex;

  public UnnamedConcreteDependency(String regent, int regentIndex, String dependent, int dependentIndex) {
    super(regent, dependent);

    headIndex = regentIndex;
    depIndex = dependentIndex;
  }
  
  public UnnamedConcreteDependency(Label regent, int regentIndex, Label dependent, int dependentIndex) {
    super(regent, dependent);
    
    headIndex = regentIndex;
    depIndex = dependentIndex;
  }

  public UnnamedConcreteDependency(Label regent, Label dependent) {
    super(regent, dependent);

    if (governor() instanceof HasIndex) {
      headIndex = ((HasIndex) governor()).index();
    } else {
      throw new IllegalArgumentException("Label argument lacks IndexAnnotation.");
    }
    if (dependent() instanceof HasIndex) {
      depIndex = ((HasIndex) dependent()).index();
    } else {
      throw new IllegalArgumentException("Label argument lacks IndexAnnotation.");
    }
  }
  
  public int getGovernorIndex() { return headIndex; }
  
  public int getDependentIndex() { return depIndex; }
  
  @Override
  public int hashCode() {
    return headIndex * (depIndex << 16);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    } else if( !(o instanceof UnnamedConcreteDependency)) {
      return false;
    }
    UnnamedConcreteDependency d = (UnnamedConcreteDependency) o;
    return headIndex == d.headIndex && depIndex == d.depIndex;
  }

  @Override
  public String toString() {
    String headWord = getText(governor());
    String depWord = getText(dependent());
    return String.format("%s [%d] --> %s [%d]", headWord, headIndex, depWord, depIndex);
  }

  /**
   * Provide different printing options via a String keyword.
   * The recognized options are currently "xml", and "predicate".
   * Otherwise the default toString() is used.
   */
  @Override
  public String toString(String format) {
    switch (format) {
      case "xml":
        String govIdxStr = " idx=\"" + headIndex + "\"";
        String depIdxStr = " idx=\"" + depIndex + "\"";
        return "  <dep>\n    <governor" + govIdxStr + ">" + XMLUtils.escapeXML(governor().value()) + "</governor>\n    <dependent" + depIdxStr + ">" + XMLUtils.escapeXML(dependent().value()) + "</dependent>\n  </dep>";
      case "predicate":
        return "dep(" + governor() + "," + dependent() + ")";
      default:
        return toString();
    }
  }

  @Override
  public DependencyFactory dependencyFactory() {
    return DependencyFactoryHolder.df;
  }
  
  public static DependencyFactory factory() {
    return DependencyFactoryHolder.df;
  }

  // extra class guarantees correct lazy loading (Bloch p.194)
  private static class DependencyFactoryHolder {
    private static final DependencyFactory df = new UnnamedConcreteDependencyFactory();
  }

  /**
   * A <code>DependencyFactory</code> acts as a factory for creating objects
   * of class <code>Dependency</code>
   */
  private static class UnnamedConcreteDependencyFactory implements DependencyFactory {
    /**
     * Create a new <code>Dependency</code>.
     */
    public Dependency<Label, Label, Object> newDependency(Label regent, Label dependent) {
      return newDependency(regent, dependent, null);
    }

    /**
     * Create a new <code>Dependency</code>.
     */
    public Dependency<Label, Label, Object> newDependency(Label regent, Label dependent, Object name) {
      return new UnnamedConcreteDependency(regent, dependent);
    }
  }
}
