class ExtremeSpreadOptionModel extends AnalyticOptionModel<ExtremeSpreadOptionDefinition, StandardOptionWithSpotTimeSeriesDataBundle> {

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

      @SuppressWarnings("synthetic-access")
      @Override
      public Double evaluate(final StandardOptionWithSpotTimeSeriesDataBundle data) {
        Validate.notNull(data, "data");
        final double s = data.getSpot();
        final double b = data.getCostOfCarry();
        final ZonedDateTime date = data.getDate();
        final double t1 = -definition.getTimeFromPeriodEnd(date);
        final double t2 = definition.getTimeToExpiry(date);
        final double r = data.getInterestRate(t2);
        final DoubleTimeSeries<?> ts = data.getSpotTimeSeries();
        final int eta = definition.isCall() ? 1 : -1;
        final int phi = definition.isReverse() ? -1 : 1;
        final double x = eta * phi == 1 ? ts.maxValue() : ts.minValue();
        final double sigma = data.getVolatility(t2, x); //REVIEW emcleod 21-7-10 works for flat vol surfaces
        final double m = Math.log(x / s);
        final double mu1 = b - sigma * sigma / 2;
        final double mu2 = b + sigma * sigma / 2;
        final double df1 = Math.exp(t2 * (b - r));
        final double df2 = Math.exp(b * (t1 - t2));
        final double df3 = Math.exp(-r * t2);
        final double sigmaT2 = sigma * Math.sqrt(t2);
        final double sigmaT1 = sigma * Math.sqrt(t1);
        final double sigmaSq = sigma * sigma;
        final double y = sigmaSq / 2 / b;
        if (definition.isReverse()) {
          final double dt = t2 - t1;
          final double sigmaDT = sigma * Math.sqrt(dt);
          final double z1 = s * df1 * (1 + y) * NORMAL.getCDF(eta * (m - mu2 * t2) / sigmaT2);
          final double z2 = df3 * x * NORMAL.getCDF(eta * (mu1 * t2 - m) / sigmaT2);
          final double z3 = -df3 * x * y * Math.exp(2 * mu1 * m / sigmaSq) * NORMAL.getCDF(eta * (m + mu1 * t2) / sigmaT2);
          final double z4 = -s * df1 * (1 + y) * NORMAL.getCDF(-eta * mu2 * dt / sigmaDT);
          final double z5 = -df2 * df1 * s * (1 - y) * NORMAL.getCDF(eta * mu1 * dt / sigmaDT);
          return -eta * (z1 + z2 + z3 + z4 + z5);
        }
        final double z1 = s * df1 * (1 + y) * NORMAL.getCDF(eta * (mu2 * t2 - m) / sigmaT2);
        final double z2 = -df2 * df1 * s * (1 + y) * NORMAL.getCDF(eta * (mu2 * t1 - m) / sigmaT1);
        final double z3 = df3 * x * NORMAL.getCDF(eta * (m - mu1 * t2) / sigmaT2);
        final double z4 = -df3 * x * y * Math.exp(2 * mu1 * m / sigmaSq) * NORMAL.getCDF(-eta * (m + mu1 * t2) / sigmaT2);
        final double z5 = -df3 * x * NORMAL.getCDF(eta * (m - mu1 * t1) / sigmaT1);
        final double z6 = df3 * x * y * Math.exp(2 * mu1 * m / sigmaSq) * NORMAL.getCDF(-eta * (m + mu1 * t1) / sigmaT1);
        return eta * (z1 + z2 + z3 + z4 + z5 + z6);
      }

    };
  }

}