class BensoussanCrouhyGalaiOptionOnOptionModel extends AnalyticOptionModel<EuropeanOptionOnEuropeanVanillaOptionDefinition, StandardOptionDataBundle> {

  @Override
  public Function1D<StandardOptionDataBundle, Double> getPricingFunction(final EuropeanOptionOnEuropeanVanillaOptionDefinition definition) {
    Validate.notNull(definition, "definition");
    return new Function1D<StandardOptionDataBundle, Double>() {

      @SuppressWarnings("synthetic-access")
      @Override
      public Double evaluate(final StandardOptionDataBundle data) {
        Validate.notNull(data, "data");
        final double s = data.getSpot();
        final OptionDefinition underlying = definition.getUnderlyingOption();
        final double k1 = definition.getStrike();
        final double k2 = underlying.getStrike();
        final ZonedDateTime date = data.getDate();
        final double t1 = definition.getTimeToExpiry(date);
        final double sigma = data.getVolatility(t1, k1);
        final double r = data.getInterestRate(t1);
        final double b = data.getCostOfCarry();
        final OptionDefinition callDefinition = underlying.isCall() ? underlying : new EuropeanVanillaOptionDefinition(k2, underlying.getExpiry(), true);
        final GreekResultCollection result = BSM.getGreeks(callDefinition, data, REQUIRED_GREEKS);
        final double callBSM = result.get(Greek.FAIR_PRICE);
        final double callDelta = result.get(Greek.DELTA);
        final double underlyingSigma = sigma * Math.abs(callDelta) * s / callBSM;
        final double d1 = getD1(callBSM, k1, t1, underlyingSigma, b);
        final double d2 = getD2(d1, underlyingSigma, t1);
        final int sign = definition.isCall() ? 1 : -1;
        if (underlying.isCall()) {
          return sign * (callBSM * NORMAL.getCDF(sign * d1) - k1 * Math.exp(-r * t1) * NORMAL.getCDF(sign * d2));
        }
        throw new NotImplementedException("This model can only price call-on-call or put-on-call options");
      }

    };
  }

}