/*
 * Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.strata.pricer.capfloor;

import static com.opengamma.strata.market.model.SabrParameterType.ALPHA;
import static com.opengamma.strata.market.model.SabrParameterType.BETA;
import static com.opengamma.strata.market.model.SabrParameterType.NU;
import static com.opengamma.strata.market.model.SabrParameterType.RHO;

import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.basics.value.ValueDerivatives;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder;
import com.opengamma.strata.pricer.rate.RatesProvider;
import com.opengamma.strata.product.capfloor.IborCapletFloorletPeriod;
import com.opengamma.strata.product.common.PutCall;

/**
 * Pricer for caplet/floorlet in SABR model.
 * <p>
 * The value of the caplet/floorlet after expiry is a fixed payoff amount. The value is zero if valuation date is 
 * after payment date of the caplet/floorlet.
 */
public class SabrIborCapletFloorletPeriodPricer
    extends VolatilityIborCapletFloorletPeriodPricer {

  /**
   * Default implementation.
   */
  public static final SabrIborCapletFloorletPeriodPricer DEFAULT = new SabrIborCapletFloorletPeriodPricer();

  @Override
  protected void validate(IborCapletFloorletVolatilities volatilities) {
    ArgChecker.isTrue(volatilities instanceof SabrIborCapletFloorletVolatilities, "volatilities must be SABR volatilities");
  }

  //-------------------------------------------------------------------------
  /**
   * Calculates the present value sensitivity of the Ibor caplet/floorlet to the rate curves.
   * <p>
   * The present value sensitivity is computed in a "sticky model parameter" style, i.e. the sensitivity to the 
   * curve nodes with the SABR model parameters unchanged. This sensitivity does not include a potential 
   * re-calibration of the model parameters to the raw market data.
   * 
   * @param period  the Ibor caplet/floorlet period
   * @param ratesProvider  the rates provider
   * @param volatilities  the volatilities
   * @return the point sensitivity to the rate curves
   */
  public PointSensitivityBuilder presentValueSensitivityRatesStickyModel(
      IborCapletFloorletPeriod period,
      RatesProvider ratesProvider,
      SabrIborCapletFloorletVolatilities volatilities) {

    Currency currency = period.getCurrency();
    if (ratesProvider.getValuationDate().isAfter(period.getPaymentDate())) {
      return PointSensitivityBuilder.none();
    }
    double expiry = volatilities.relativeTime(period.getFixingDateTime());
    PutCall putCall = period.getPutCall();
    double strike = period.getStrike();
    double indexRate = ratesProvider.iborIndexRates(period.getIndex()).rate(period.getIborRate().getObservation());
    PointSensitivityBuilder dfSensi = ratesProvider.discountFactors(currency).zeroRatePointSensitivity(period.getPaymentDate());
    double factor = period.getNotional() * period.getYearFraction();
    if (expiry < 0d) { // option expired already, but not yet paid
      double sign = putCall.isCall() ? 1d : -1d;
      double payoff = Math.max(sign * (indexRate - strike), 0d);
      return dfSensi.multipliedBy(payoff * factor);
    }
    ValueDerivatives volatilityAdj = volatilities.volatilityAdjoint(expiry, strike, indexRate);
    PointSensitivityBuilder indexRateSensiSensi =
        ratesProvider.iborIndexRates(period.getIndex()).ratePointSensitivity(period.getIborRate().getObservation());
    double df = ratesProvider.discountFactor(currency, period.getPaymentDate());
    double fwdPv = factor * volatilities.price(expiry, putCall, strike, indexRate, volatilityAdj.getValue());
    double fwdDelta = factor * volatilities.priceDelta(expiry, putCall, strike, indexRate, volatilityAdj.getValue());
    double fwdVega = factor * volatilities.priceVega(expiry, putCall, strike, indexRate, volatilityAdj.getValue());

    return dfSensi.multipliedBy(fwdPv).combinedWith(
        indexRateSensiSensi.multipliedBy(fwdDelta * df + fwdVega * volatilityAdj.getDerivative(0) * df));
  }

  //-------------------------------------------------------------------------
  /**
   * Calculates the present value sensitivity to the SABR model parameters of the Ibor caplet/floorlet.
   * <p>
   * The sensitivity of the present value to the SABR model parameters, alpha, beta, rho and nu.
   * 
   * @param period  the Ibor caplet/floorlet period
   * @param ratesProvider  the rates provider
   * @param volatilities  the volatilities
   * @return the point sensitivity to the SABR model parameters
   */
  public PointSensitivityBuilder presentValueSensitivityModelParamsSabr(
      IborCapletFloorletPeriod period,
      RatesProvider ratesProvider,
      SabrIborCapletFloorletVolatilities volatilities) {

    double expiry = volatilities.relativeTime(period.getFixingDateTime());
    if (expiry < 0d) { // option expired already
      return PointSensitivityBuilder.none();
    }
    Currency currency = period.getCurrency();
    PutCall putCall = period.getPutCall();
    double strike = period.getStrike();
    double indexRate = ratesProvider.iborIndexRates(period.getIndex()).rate(period.getIborRate().getObservation());
    double factor = period.getNotional() * period.getYearFraction();
    ValueDerivatives volatilityAdj = volatilities.volatilityAdjoint(expiry, strike, indexRate);
    DoubleArray derivative = volatilityAdj.getDerivatives();
    double df = ratesProvider.discountFactor(currency, period.getPaymentDate());
    double vega = df * factor * volatilities.priceVega(expiry, putCall, strike, indexRate, volatilityAdj.getValue());
    IborCapletFloorletVolatilitiesName name = volatilities.getName();

    return PointSensitivityBuilder.of(
        IborCapletFloorletSabrSensitivity.of(name, expiry, ALPHA, currency, vega * derivative.get(2)),
        IborCapletFloorletSabrSensitivity.of(name, expiry, BETA, currency, vega * derivative.get(3)),
        IborCapletFloorletSabrSensitivity.of(name, expiry, RHO, currency, vega * derivative.get(4)),
        IborCapletFloorletSabrSensitivity.of(name, expiry, NU, currency, vega * derivative.get(5)));
  }

}
