/**
   * {@inheritDoc}
   *
   * @return a {@link String} with the format <code>{p2-p1, p3-p1, ..., p(n-1)-p1}</code>, where
   *     {@code pi} corresponds to the pitch of the i-th note inside the n-gram. The {@link String}
   *     {@code "null"} is returned if the n-gram is {@code null}.
   */
  @Override
  public String getNGramId(NGram g) {
    if (g == null) return "null";

    String res = "{";
    for (int i = 1; i < g.size(); i++) res += (g.get(i).getPitch() - g.get(0).getPitch()) + " ";
    return res.trim() + "}";
  }
  /** {@inheritDoc} */
  @Override
  public double compare(NGram g1, NGram g2) {
    if (g1 == null) g1 = g2.getNullSpan();
    if (g2 == null) g2 = g1.getNullSpan();
    if (g1.size() != 3)
      throw new IllegalArgumentException(this.getName() + " only supports n-grams with 3 notes.");
    if (this.getNGramId(g1).equals(this.getNGramId(g2))) return 0;

    PolynomialFunction p1 = new PolynomialFunction(new double[] {0});
    PolynomialFunction p2 = new PolynomialFunction(new double[] {0});
    for (int i = 1; i < g1.size(); i++) {
      PolynomialFunction basis = UniformBSpline.BASIS_FUNCTIONS[g1.size() - 1][i - 1];
      p1 =
          p1.add(
              basis.multiply(
                  new PolynomialFunction(
                      new double[] {g1.get(g1.size() - i).getPitch() - g1.get(0).getPitch()})));
      p2 =
          p2.add(
              basis.multiply(
                  new PolynomialFunction(
                      new double[] {g2.get(g2.size() - i).getPitch() - g2.get(0).getPitch()})));
    }
    PolynomialFunction p1_ = p1.polynomialDerivative();
    PolynomialFunction p2_ = p2.polynomialDerivative();

    /*
    double p1_0 = Math.signum(p1_.value(0));
    double p1_1 = Math.signum(p1_.value(1));
    double p2_0 = Math.signum(p2_.value(0));
    double p2_1 = Math.signum(p2_.value(1));

    if(p1_0 == p2_0){ // same at 0
    	if(p1_1 == p2_1) // same at 1
    		return -this.dMin;
    	else if(p1_1*p2_1<0) // opposite at 1
    		return -this.dMed;
    	else // some flat at 1
    		return -this.dMed;
    }else if(p1_0*p2_0 < 0) { // opposite at 0
    	if(p1_1 == p2_1) // same at 1
    		return -this.dMed;
    	else if(p1_1*p2_1<0) // opposite at 1
    		return -this.dMax;
    	else // some flat at 1
    		return -this.dMax;
    }else { // some flat at 0
    	if(p1_1 == p2_1) // same at 1
    		return -this.dMed;
    	else if(p1_1*p2_1<0) // opposite at 1
    		return -this.dMax;
    	else // some flat at 1
    		return -this.dMax;
    }*/

    double p1_0 = p1_.value(0);
    double p1_1 = p1_.value(1);
    double p2_0 = p2_.value(0);
    double p2_1 = p2_.value(1);

    // TODO: this similarity function is not symmetric
    if (p1_0 <= 0 && p1_1 >= 0) { // p1 is \/
      if (p2_0 <= 0 && p2_1 >= 0) // p2 is \/
      return -this.dMin;
      else if (p2_0 >= 0 && p2_1 <= 0) // p2 is /\
      return -this.dMax;
      else return -this.dMed;
    }
    if (p1_0 >= 0 && p1_1 <= 0) { // p1 is /\
      if (p2_0 >= 0 && p2_1 <= 0) // p2 is /\
      return -this.dMin;
      else if (p2_0 <= 0 && p2_1 >= 0) // p2 is \/
      return -this.dMax;
      else return -this.dMed;
    }
    if (p1_0 >= 0 && p1_1 >= 0) { // p1 is /
      if (p2_0 >= 0 && p2_1 >= 0) // p2 is /
      return -this.dMin;
      else if (p2_0 <= 0 && p2_1 <= 0) // p2 is \
      return -this.dMax;
      else return -this.dMed;
    }
    if (p1_0 <= 0 && p1_1 <= 0) { // p1 is \
      if (p2_0 <= 0 && p2_1 <= 0) // p2 is \
      return -this.dMin;
      else if (p2_0 >= 0 && p2_1 >= 0) // p2 is /
      return -this.dMax;
      else return -this.dMed;
    }

    return -this.dMin;
  }