class CouponIborCompoundingFlatSpreadDiscountingMethod {

  public MultipleCurrencyMulticurveSensitivity presentValueCurveSensitivity(final CouponIborCompoundingFlatSpread coupon, final MulticurveProviderInterface multicurve) {
    ArgumentChecker.notNull(coupon, "Coupon");
    ArgumentChecker.notNull(multicurve, "Multi-curve provider");
    final int nbSubPeriod = coupon.getFixingTimes().length;
    final double[] cpa = new double[nbSubPeriod + 1];
    final double[] cpaAccumulated = new double[nbSubPeriod + 1];
    final double[] forward = new double[nbSubPeriod];
    cpa[0] = coupon.getCompoundingPeriodAmountAccumulated();
    cpaAccumulated[0] = coupon.getCompoundingPeriodAmountAccumulated();
    for (int loopsub = 0; loopsub < nbSubPeriod; loopsub++) {
      forward[loopsub] = multicurve.getSimplyCompoundForwardRate(coupon.getIndex(), coupon.getFixingPeriodStartTimes()[loopsub], coupon.getFixingPeriodEndTimes()[loopsub],
          coupon.getFixingPeriodAccrualFactors()[loopsub]);
      cpa[loopsub + 1] += coupon.getNotional() * (forward[loopsub] + coupon.getSpread()) * coupon.getSubperiodsAccrualFactors()[loopsub]; // Basic Compounding Period Amount
      cpa[loopsub + 1] += cpaAccumulated[loopsub] * forward[loopsub] * coupon.getSubperiodsAccrualFactors()[loopsub]; // Additional Compounding Period Amount
      cpaAccumulated[loopsub + 1] = cpaAccumulated[loopsub] + cpa[loopsub + 1];
    }
    final double df = multicurve.getDiscountFactor(coupon.getCurrency(), coupon.getPaymentTime());
    // Backward sweep
    final double pvBar = 1.0;
    final double dfBar = cpaAccumulated[nbSubPeriod] * pvBar;
    final double[] cpaAccumulatedBar = new double[nbSubPeriod + 1];
    cpaAccumulatedBar[nbSubPeriod] = df * pvBar;
    final double[] cpaBar = new double[nbSubPeriod + 1];
    final double[] forwardBar = new double[nbSubPeriod];
    for (int loopsub = nbSubPeriod - 1; loopsub >= 0; loopsub--) {
      cpaAccumulatedBar[loopsub] = cpaAccumulatedBar[loopsub + 1];
      cpaBar[loopsub + 1] += cpaAccumulatedBar[loopsub + 1];
      cpaAccumulatedBar[loopsub] += forward[loopsub] * coupon.getSubperiodsAccrualFactors()[loopsub] * cpaBar[loopsub + 1];
      forwardBar[loopsub] += cpaAccumulated[loopsub] * coupon.getSubperiodsAccrualFactors()[loopsub] * cpaBar[loopsub + 1];
      forwardBar[loopsub] += coupon.getNotional() * coupon.getSubperiodsAccrualFactors()[loopsub] * cpaBar[loopsub + 1];
    }
    final Map<String, List<DoublesPair>> mapDsc = new HashMap<>();
    final List<DoublesPair> listDiscounting = new ArrayList<>();
    listDiscounting.add(DoublesPair.of(coupon.getPaymentTime(), -coupon.getPaymentTime() * df * dfBar));
    mapDsc.put(multicurve.getName(coupon.getCurrency()), listDiscounting);
    final Map<String, List<ForwardSensitivity>> mapFwd = new HashMap<>();
    final List<ForwardSensitivity> listForward = new ArrayList<>();
    for (int loopsub = 0; loopsub < nbSubPeriod; loopsub++) {
      listForward.add(new SimplyCompoundedForwardSensitivity(coupon.getFixingPeriodStartTimes()[loopsub], coupon.getFixingPeriodEndTimes()[loopsub],
          coupon.getFixingPeriodAccrualFactors()[loopsub],
          forwardBar[loopsub]));
    }
    mapFwd.put(multicurve.getName(coupon.getIndex()), listForward);
    return MultipleCurrencyMulticurveSensitivity.of(coupon.getCurrency(), MulticurveSensitivity.of(mapDsc, mapFwd));
  }

  private  CouponIborCompoundingFlatSpreadDiscountingMethod();

}