class CapFloorCMSSpreadSABRBinormalMethod {

  public PresentValueSABRSensitivityDataBundle presentValueSABRSensitivity(final CapFloorCMSSpread cmsSpread, final SABRSwaptionProviderInterface sabrData) {
    ArgumentChecker.notNull(cmsSpread, "CMA cap/floor");
    ArgumentChecker.notNull(sabrData, "SABR swaption provider");
    final Currency ccy = cmsSpread.getCurrency();
    final MulticurveProviderInterface multicurves = sabrData.getMulticurveProvider();
    // Forward sweep
    final double forward1 = cmsSpread.getUnderlyingSwap1().accept(PRDC, multicurves);
    final double forward2 = cmsSpread.getUnderlyingSwap2().accept(PRDC, multicurves);
    CouponCMS cmsCoupon1 = CouponCMS.from(cmsSpread, cmsSpread.getUnderlyingSwap1(), cmsSpread.getSettlementTime());
    cmsCoupon1 = cmsCoupon1.withNotional(Math.abs(cmsCoupon1.getNotional()));
    CouponCMS cmsCoupon2 = CouponCMS.from(cmsSpread, cmsSpread.getUnderlyingSwap2(), cmsSpread.getSettlementTime());
    cmsCoupon2 = cmsCoupon2.withNotional(Math.abs(cmsCoupon2.getNotional()));
    final CapFloorCMS cmsCap1 = CapFloorCMS.from(cmsCoupon1, forward1, true);
    final CapFloorCMS cmsCap2 = CapFloorCMS.from(cmsCoupon2, forward2, true);
    final double cmsCoupon1Price = _methodCmsCoupon.presentValue(cmsCoupon1, sabrData).getAmount(ccy);
    final double cmsCoupon2Price = _methodCmsCoupon.presentValue(cmsCoupon2, sabrData).getAmount(ccy);
    final double cmsCap1Price = _methodCmsCap.presentValue(cmsCap1, sabrData).getAmount(ccy);
    final double cmsCap2Price = _methodCmsCap.presentValue(cmsCap2, sabrData).getAmount(ccy);
    final double discountFactorPayment = multicurves.getDiscountFactor(ccy, cmsSpread.getPaymentTime());
    final double factor = discountFactorPayment * cmsCap1.getNotional() * cmsCap1.getPaymentYearFraction();
    final double expectation1 = cmsCoupon1Price / factor;
    final double expectation2 = cmsCoupon2Price / factor;
    NormalFunctionData dataCap1 = new NormalFunctionData(expectation1, factor, 0.0);
    final EuropeanVanillaOption optionCap1 = new EuropeanVanillaOption(forward1, cmsSpread.getFixingTime(), true);
    double cmsCap1ImpliedVolatility = 0;
    try {
      cmsCap1ImpliedVolatility = NORMAL_IMPLIED_VOLATILITY.getImpliedVolatility(dataCap1, optionCap1, cmsCap1Price);
    } catch (final Exception e) {
      //TODO
    }
    NormalFunctionData dataCap2 = new NormalFunctionData(expectation2, factor, cmsCap1ImpliedVolatility);
    final EuropeanVanillaOption optionCap2 = new EuropeanVanillaOption(forward2, cmsSpread.getFixingTime(), true);
    double cmsCap2ImpliedVolatility = 0;
    try {
      cmsCap2ImpliedVolatility = NORMAL_IMPLIED_VOLATILITY.getImpliedVolatility(dataCap2, optionCap2, cmsCap2Price);
    } catch (final Exception e) {
      //TODO
    }
    final double rho = _correlation.evaluate(cmsSpread.getStrike());
    final double cmsSpreadImpliedVolatility = Math.sqrt(cmsCap1ImpliedVolatility * cmsCap1ImpliedVolatility - 2 * rho * cmsCap1ImpliedVolatility * cmsCap2ImpliedVolatility + cmsCap2ImpliedVolatility
        * cmsCap2ImpliedVolatility);
    final NormalFunctionData dataSpread = new NormalFunctionData(expectation1 - expectation2, discountFactorPayment * cmsSpread.getNotional() * cmsSpread.getPaymentYearFraction(),
        cmsSpreadImpliedVolatility);
    final EuropeanVanillaOption optionSpread = new EuropeanVanillaOption(cmsSpread.getStrike(), cmsSpread.getFixingTime(), cmsSpread.isCap());
    final double[] cmsSpreadPriceDerivative = new double[3];
    NORMAL_PRICE.getPriceAdjoint(optionSpread, dataSpread, cmsSpreadPriceDerivative);
    // Backward sweep
    final double cmsSpreadPriceBar = 1.0;
    final double cmsSpreadImpliedVolatilityBar = cmsSpreadPriceDerivative[1] * cmsSpreadPriceBar;
    final double cmsCap2ImpliedVolatilityBar = (cmsCap2ImpliedVolatility - rho * cmsCap1ImpliedVolatility) / cmsSpreadImpliedVolatility * cmsSpreadImpliedVolatilityBar;
    final double cmsCap1ImpliedVolatilityBar = (cmsCap1ImpliedVolatility - rho * cmsCap2ImpliedVolatility) / cmsSpreadImpliedVolatility * cmsSpreadImpliedVolatilityBar;
    dataCap2 = new NormalFunctionData(expectation2, factor, cmsCap2ImpliedVolatility);
    final double[] cmsCap2PriceNormalDerivative = new double[3];
    NORMAL_PRICE.getPriceAdjoint(optionCap2, dataCap2, cmsCap2PriceNormalDerivative);
    final double expectation2Bar = -cmsSpreadPriceDerivative[0] * cmsSpreadPriceBar + -cmsCap2PriceNormalDerivative[0] / cmsCap2PriceNormalDerivative[1] * cmsCap2ImpliedVolatilityBar;
    dataCap1 = new NormalFunctionData(expectation1, factor, cmsCap1ImpliedVolatility);
    final double[] cmsCap1PriceNormalDerivative = new double[3];
    NORMAL_PRICE.getPriceAdjoint(optionCap1, dataCap1, cmsCap1PriceNormalDerivative);
    final double expectation1Bar = cmsSpreadPriceDerivative[0] * cmsSpreadPriceBar + -cmsCap1PriceNormalDerivative[0] / cmsCap1PriceNormalDerivative[1] * cmsCap1ImpliedVolatilityBar;
    final double cmsCap2PriceBar = 1.0 / cmsCap2PriceNormalDerivative[1] * cmsCap2ImpliedVolatilityBar;
    final double cmsCap1PriceBar = 1.0 / cmsCap1PriceNormalDerivative[1] * cmsCap1ImpliedVolatilityBar;
    final double cmsCoupon2PriceBar = expectation2Bar / factor;
    final double cmsCoupon1PriceBar = expectation1Bar / factor;
    PresentValueSABRSensitivityDataBundle cmsCoupon1SABRSensitivity = _methodCmsCoupon.presentValueSABRSensitivity(cmsCoupon1, sabrData);
    PresentValueSABRSensitivityDataBundle cmsCoupon2SABRSensitivity = _methodCmsCoupon.presentValueSABRSensitivity(cmsCoupon2, sabrData);
    PresentValueSABRSensitivityDataBundle cmsCap1SABRSensitivity = _methodCmsCap.presentValueSABRSensitivity(cmsCap1, sabrData);
    PresentValueSABRSensitivityDataBundle cmsCap2SABRSensitivity = _methodCmsCap.presentValueSABRSensitivity(cmsCap2, sabrData);
    cmsCoupon1SABRSensitivity = cmsCoupon1SABRSensitivity.multiplyBy(cmsCoupon1PriceBar);
    cmsCoupon2SABRSensitivity = cmsCoupon2SABRSensitivity.multiplyBy(cmsCoupon2PriceBar);
    cmsCap1SABRSensitivity = cmsCap1SABRSensitivity.multiplyBy(cmsCap1PriceBar);
    cmsCap2SABRSensitivity = cmsCap2SABRSensitivity.multiplyBy(cmsCap2PriceBar);
    PresentValueSABRSensitivityDataBundle result = cmsCoupon1SABRSensitivity;
    result = result.plus(cmsCoupon2SABRSensitivity);
    result = result.plus(cmsCap1SABRSensitivity);
    result = result.plus(cmsCap2SABRSensitivity);
    return result;
  }

  public  CapFloorCMSSpreadSABRBinormalMethod(final DoubleFunction1D correlation, final CapFloorCMSSABRReplicationAbstractMethod methodCmsCap,
      final CouponCMSSABRReplicationGenericMethod methodCmsCoupon);

}