/**
   * Applies this dependency structure with another, adding any new dependencies
   * to @newResolvedDependencies
   */
  public DependencyStructure compose(
      DependencyStructure other, final List<UnlabelledDependency> newResolvedDependencies) {

    other = other.standardizeApart(coindexation.getMaxID() + 1);
    final UnifyingSubstitution substitution =
        UnifyingSubstitution.make(coindexation.right, other.coindexation.left, false);

    final Set<UnresolvedDependency> newUnresolvedDependencies = new HashSet<>();
    updateResolvedDependencies(
        other, substitution, newUnresolvedDependencies, newResolvedDependencies);

    final Coindexation newCoindexationLeft = substitution.applyTo(coindexation.left);
    final Coindexation newCoindexationRight = substitution.applyTo(other.coindexation.right);
    final boolean headIsLeft = !coindexation.left.idOrHead.equals(coindexation.right.idOrHead);
    final Coindexation.IDorHead idOrHead =
        substitution.applyTo(headIsLeft ? coindexation : other.coindexation).idOrHead;

    final Set<UnresolvedDependency> normalizedUnresolvedDependencies =
        new HashSet<>(newUnresolvedDependencies.size());
    final Coindexation normalizedCoindexation =
        normalize(
            new Coindexation(newCoindexationLeft, newCoindexationRight, idOrHead),
            newUnresolvedDependencies,
            normalizedUnresolvedDependencies,
            newResolvedDependencies,
            1);

    return new DependencyStructure(normalizedCoindexation, normalizedUnresolvedDependencies);
  }
  /** Generalized forward composition (to degree 2) */
  public DependencyStructure compose2(
      DependencyStructure other, final List<UnlabelledDependency> newResolvedDependencies) {
    // A/B (B/C)/D ---> (A/C)/D
    other = other.standardizeApart(coindexation.getMaxID() + 1);

    final UnifyingSubstitution substitution =
        UnifyingSubstitution.make(coindexation.right, other.coindexation.left.left, false);

    final Set<UnresolvedDependency> newUnresolvedDependencies = new HashSet<>();

    updateResolvedDependencies(
        other, substitution, newUnresolvedDependencies, newResolvedDependencies);
    final Set<UnresolvedDependency> normalizedUnresolvedDependencies =
        new HashSet<>(newUnresolvedDependencies.size());
    final Coindexation normalizedCoindexation;
    if (coindexation.isModifier()) {
      // X/X X/Y/Z
      normalizedCoindexation =
          normalize(
              substitution.applyTo(other.coindexation),
              newUnresolvedDependencies,
              normalizedUnresolvedDependencies,
              newResolvedDependencies,
              1);
    } else {
      // S\NP/NP NP/PP/PP
      final Coindexation leftWithSubstitution = substitution.applyTo(coindexation);
      final Coindexation rightWithSubstitution = substitution.applyTo(other.coindexation);
      normalizedCoindexation =
          normalize(
              new Coindexation(
                  new Coindexation(
                      leftWithSubstitution.left,
                      rightWithSubstitution.left.right,
                      leftWithSubstitution.idOrHead),
                  rightWithSubstitution.right,
                  leftWithSubstitution.idOrHead),
              newUnresolvedDependencies,
              normalizedUnresolvedDependencies,
              newResolvedDependencies,
              1);
    }

    return new DependencyStructure(normalizedCoindexation, normalizedUnresolvedDependencies);
  }
  /**
   * Applies this dependency structure to another, adding any new dependencies
   * to @newResolvedDependencies
   */
  public DependencyStructure apply(
      DependencyStructure other, final List<UnlabelledDependency> newResolvedDependencies) {
    other = other.standardizeApart(coindexation.getMaxID() + 1);

    final UnifyingSubstitution substitution =
        UnifyingSubstitution.make(coindexation.right, other.coindexation, isConjunction);

    final Set<UnresolvedDependency> newUnresolvedDependencies = new HashSet<>();
    final Coindexation newCoindexation = substitution.applyTo(coindexation.left);
    updateResolvedDependencies(
        other, substitution, newUnresolvedDependencies, newResolvedDependencies);

    final Set<UnresolvedDependency> normalizedUnresolvedDependencies =
        new HashSet<>(newUnresolvedDependencies.size());

    final Coindexation normalizedCoindexation =
        normalize(
            newCoindexation,
            newUnresolvedDependencies,
            normalizedUnresolvedDependencies,
            newResolvedDependencies,
            1);
    return new DependencyStructure(normalizedCoindexation, normalizedUnresolvedDependencies);
  }
  /**
   * Updates the agenda with the result of all combinators that can be applied to leftChild and
   * rightChild.
   */
  private void updateAgenda(
      final PriorityQueue<AgendaItem> agenda,
      final AgendaItem left,
      final AgendaItem right,
      final Model model) {

    final SyntaxTreeNode leftChild = left.getParse();
    final SyntaxTreeNode rightChild = right.getParse();

    if (!seenRules.isSeen(leftChild.getCategory(), rightChild.getCategory())) {
      return;
    }
    final List<RuleProduction> rules = getRules(leftChild.getCategory(), rightChild.getCategory());

    final int size = rules.size();
    for (int i = 0; i < size; i++) {
      final RuleProduction production = rules.get(i);
      // Check if normal-form constraints let us add this rule.
      if (NormalForm.isOk(
          leftChild.getRuleClass(),
          rightChild.getRuleClass(),
          production.getRuleType(),
          leftChild.getCategory(),
          rightChild.getCategory(),
          production.getCategory(),
          left.getStartOfSpan() == 0)) {

        final SyntaxTreeNodeBinary newNode;
        if (usingDependencies) {
          // Update all the information for tracking dependencies.
          final List<UnlabelledDependency> resolvedDependencies = new ArrayList<>();
          final DependencyStructure newDependencies =
              production
                  .getCombinator()
                  .apply(
                      leftChild.getDependencyStructure(),
                      rightChild.getDependencyStructure(),
                      resolvedDependencies);

          final boolean headIsLeft =
              newDependencies.getArbitraryHead()
                  == leftChild.getDependencyStructure().getArbitraryHead();

          newNode =
              new SyntaxTreeNodeBinary(
                  production.getCategory(),
                  leftChild,
                  rightChild,
                  production.getRuleType(),
                  headIsLeft,
                  newDependencies,
                  resolvedDependencies);

        } else {
          // If we're not modeling dependencies, we can save a lot of work.
          newNode =
              new SyntaxTreeNodeBinary(
                  production.getCategory(),
                  leftChild,
                  rightChild,
                  production.getRuleType(),
                  production.isHeadIsLeft(),
                  null,
                  null);
        }

        agenda.add(model.combineNodes(left, right, newNode));
      }
    }
  }