class CapFloorIborInArrearsSmileModelCapGenericReplicationMethod {

  public MultipleCurrencyAmount presentValue(final CapFloorIbor cap, final MulticurveProviderInterface curves) {
    ArgumentChecker.notNull(cap, "The cap/floor shoud not be null");
    ArgumentChecker.notNull(curves, "curves");
    final Currency ccy = cap.getCurrency();
    // Construct a "standard" CapFloorIbor whose paymentTime is set to be fixingPeriodEndTime
    CapFloorIbor capStandard = new CapFloorIbor(cap.getCurrency(), cap.getFixingPeriodEndTime(),
        cap.getPaymentYearFraction(), cap.getNotional(), cap.getFixingTime(), cap.getIndex(),
        cap.getFixingPeriodStartTime(), cap.getFixingPeriodEndTime(), cap.getFixingAccrualFactor(), cap.getStrike(),
        cap.isCap());
    final double forward = curves.getSimplyCompoundForwardRate(cap.getIndex(), cap.getFixingPeriodStartTime(),
        cap.getFixingPeriodEndTime(), cap.getFixingAccrualFactor());
    final double beta = (1.0 + cap.getFixingAccrualFactor() * forward) *
        curves.getDiscountFactor(ccy, cap.getFixingPeriodEndTime())
        / curves.getDiscountFactor(ccy, cap.getFixingPeriodStartTime());

    final double df = curves.getDiscountFactor(capStandard.getCurrency(), capStandard.getPaymentTime());
    final double strikePart = (1.0 + cap.getFixingAccrualFactor() * capStandard.getStrike()) *
        presentValueStandard(forward, capStandard.getStrike(), capStandard.getFixingTime(),
            capStandard.isCap(), df, capStandard.getNotional(), capStandard.getPaymentYearFraction());

    final InArrearsIntegrant integrant = new InArrearsIntegrant(capStandard, curves);
    double integralPart;
    try {
      if (cap.isCap()) {
        double atmVol = _smileFunction.getVolatility(forward);
        double upper = forward * Math.exp(6.0 * atmVol * Math.sqrt(cap.getFixingTime()));
        double strike = cap.getStrike();
        integralPart = INTEGRATOR.integrate(integrant, strike, upper);
        double reminder = integrant.evaluate(upper) * upper;
        double error = reminder / integralPart;

        int count = 0;
        while (Math.abs(error) > REL_ERROR && count < MAX_COUNT) {
          integralPart += INTEGRATOR.integrate(integrant, upper, 2.0 * upper);
          upper *= 2.0;
          // The increase of integralPart in the next loop is bounded by reminder
          reminder = integrant.evaluate(upper) * upper;
          error = reminder / integralPart;
          ++count;
          if (count == MAX_COUNT) {
            LOGGER.info("Maximum iteration count, " + MAX_COUNT +
                ", has been reached. Relative error is greater than " + REL_ERROR);
          }
        }
      } else {
        double strike = cap.getStrike();
        integralPart = INTEGRATOR.integrate(integrant, REL_TOL * strike, strike);
      }
    } catch (final Exception e) {
      throw new MathException(e);
    }
    integralPart *= 2.0 * cap.getFixingAccrualFactor();
    final double pv = (strikePart + integralPart) / beta;
    return MultipleCurrencyAmount.of(cap.getCurrency(), pv);
  }

  public  CapFloorIborInArrearsSmileModelCapGenericReplicationMethod(final InterpolatedSmileFunction smileFunction);

}