class EuropeanStandardBarrierOptionModel extends AnalyticOptionModel<EuropeanStandardBarrierOptionDefinition, StandardOptionDataBundle> {

  @Override
  public Function1D<StandardOptionDataBundle, Double> getPricingFunction(final EuropeanStandardBarrierOptionDefinition definition) {
    Validate.notNull(definition, "definition");
    final Barrier barrier = definition.getBarrier();
    final boolean isKnockIn = barrier.getKnockType() == KnockType.IN;
    final boolean isDown = barrier.getBarrierType() == BarrierType.DOWN;
    final double h = barrier.getBarrierLevel();
    final int phi = definition.isCall() ? 1 : -1;
    final double eta = isDown ? 1 : -1;
    return new Function1D<StandardOptionDataBundle, Double>() {

      @SuppressWarnings("synthetic-access")
      @Override
      public Double evaluate(final StandardOptionDataBundle data) {
        Validate.notNull(data, "data");
        final boolean isCall = definition.isCall();
        final double s = data.getSpot();
        final double b = data.getCostOfCarry();
        final double t = definition.getTimeToExpiry(data.getDate());
        final double rebate = definition.getRebate();
        final double k = definition.getStrike();
        final double r = data.getInterestRate(t);
        final double sigma = data.getVolatility(t, h); //REVIEW emcleod 19-7-10 will only work if volatility is constant
        final double df1 = Math.exp(t * (b - r));
        final double df2 = Math.exp(-r * t);
        if (CompareUtils.closeEquals(sigma, 0, 1e-16)) {
          return df1 * definition.getPayoffFunction().getPayoff(data, null);
        }
        final double sigmaSq = sigma * sigma;
        final double sigmaT = sigma * Math.sqrt(t);
        final double mu = (b - 0.5 * sigmaSq) / sigmaSq;
        final double lambda = Math.sqrt(mu * mu + 2 * r / sigmaSq);
        final double m1 = sigmaT * (1 + mu);
        final double x1 = Math.log(s / k) / sigmaT + m1;
        final double x2 = Math.log(s / h) / sigmaT + m1;
        final double y1 = Math.log(h * h / s / k) / sigmaT + m1;
        final double y2 = Math.log(h / s) / sigmaT + m1;
        final double z = Math.log(h / s) / sigmaT + lambda * sigmaT;
        final double xA = getA(s, k, df1, df2, x1, sigmaT, phi);
        final double xB = getA(s, k, df1, df2, x2, sigmaT, phi);
        final double xC = getC(s, k, df1, df2, y1, sigmaT, h, mu, phi, eta);
        final double xD = getC(s, k, df1, df2, y2, sigmaT, h, mu, phi, eta);
        final double xE = isKnockIn ? getE(s, rebate, df2, x2, y2, sigmaT, h, mu, eta) : getF(s, rebate, z, sigmaT, h, mu, lambda, eta);
        if (isKnockIn) {
          if (isDown) {
            if (isCall) {
              return k > h ? xC + xE : xA - xB + xD + xE;
            }
            return k > h ? xB - xC + xD + xE : xA + xE;
          }
          if (isCall) {
            return k > h ? xA + xE : xB - xC + xD + xE;
          }
          return k > h ? xA - xB + xD + xE : xC + xE;
        }
        if (isDown) {
          if (isCall) {
            return k > h ? xA - xC + xE : xB - xD + xE;
          }
          return k > h ? xA - xB + xC - xD + xE : xE;
        }
        if (isCall) {
          return k > h ? xE : xA - xB + xC - xD + xE;
        }
        return k > h ? xB - xD + xE : xA - xC + xE;
      }

    };
  }

}