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

import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.market.sensitivity.PointSensitivities;
import com.opengamma.strata.pricer.impl.option.BlackFormulaRepository;
import com.opengamma.strata.product.bond.BondFuture;
import com.opengamma.strata.product.bond.ResolvedBondFuture;
import com.opengamma.strata.product.bond.ResolvedBondFutureOption;
import com.opengamma.strata.product.option.FutureOptionPremiumStyle;

/**
 * Pricer of options on bond future with a log-normal model on the underlying future price.
 * 
 * <h4>Price</h4>
 * Strata uses <i>decimal prices</i> for bond futures options in the trade model, pricers and market data.
 * This is coherent with the pricing of {@link BondFuture}.
 */
public final class BlackBondFutureOptionMarginedProductPricer {

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

  /**
   * The underlying future pricer.
   * The pricer take only the curves as inputs, no model parameters.
   */
  private final DiscountingBondFutureProductPricer futurePricer;

  /**
   * Creates an instance.
   * 
   * @param futurePricer  the pricer for {@link ResolvedBondFutureOption}
   */
  public BlackBondFutureOptionMarginedProductPricer(
      DiscountingBondFutureProductPricer futurePricer) {
    this.futurePricer = ArgChecker.notNull(futurePricer, "futurePricer");
  }

  //-------------------------------------------------------------------------
  /**
   * Returns the underlying future pricer function.
   * 
   * @return the future pricer
   */
  DiscountingBondFutureProductPricer getFuturePricer() {
    return futurePricer;
  }

  //-------------------------------------------------------------------------
  /**
   * Calculates the number related to bond futures product on which the daily margin is computed.
   * <p>
   * For two consecutive settlement prices C1 and C2, the daily margin is computed as 
   *    {@code marginIndex(future, C2) - marginIndex(future, C1)}.
   *    
   * @param option  the option product
   * @param price  the price of the product, in decimal form
   * @return the index
   */
  double marginIndex(ResolvedBondFutureOption option, double price) {
    double notional = option.getUnderlyingFuture().getNotional();
    return price * notional;
  }

  /**
   * Calculates the margin index sensitivity of the bond future product.
   * <p>
   * For two consecutive settlement prices C1 and C2, the daily margin is computed as 
   *    {@code marginIndex(future, C2) - marginIndex(future, C1)}.
   * The margin index sensitivity if the sensitivity of the margin index to the underlying curves.
   * 
   * @param option  the option product
   * @param priceSensitivity  the price sensitivity of the product
   * @return the index sensitivity
   */
  PointSensitivities marginIndexSensitivity(ResolvedBondFutureOption option, PointSensitivities priceSensitivity) {
    double notional = option.getUnderlyingFuture().getNotional();
    return priceSensitivity.multipliedBy(notional);
  }

  //-------------------------------------------------------------------------
  /**
   * Calculates the price of the bond future option product.
   * <p>
   * The price of the option is the price on the valuation date.
   * <p>
   * This calculates the underlying future price using the future pricer.
   * <p>
   * Strata uses <i>decimal prices</i> for bond futures. This is coherent with the pricing of {@link BondFuture}.
   * For example, a price of 1.32% is represented in Strata by 0.0132.
   * 
   * @param futureOption  the option product
   * @param discountingProvider  the discounting provider
   * @param volatilities  the volatilities
   * @return the price of the product, in decimal form
   */
  public double price(
      ResolvedBondFutureOption futureOption,
      LegalEntityDiscountingProvider discountingProvider,
      BlackBondFutureVolatilities volatilities) {

    double futurePrice = futurePrice(futureOption, discountingProvider);
    return price(futureOption, discountingProvider, volatilities, futurePrice);
  }

  /**
   * Calculates the price of the bond future option product
   * based on the price of the underlying future.
   * <p>
   * The price of the option is the price on the valuation date.
   * <p>
   * Strata uses <i>decimal prices</i> for bond futures. This is coherent with the pricing of {@link BondFuture}.
   * For example, a price of 1.32% is represented in Strata by 0.0132.
   * 
   * @param futureOption  the option product
   * @param discountingProvider  the discounting provider
   * @param volatilities  the volatilities
   * @param futurePrice  the price of the underlying future
   * @return the price of the product, in decimal form
   */
  public double price(
      ResolvedBondFutureOption futureOption,
      LegalEntityDiscountingProvider discountingProvider,
      BlackBondFutureVolatilities volatilities,
      double futurePrice) {

    ArgChecker.isTrue(futureOption.getPremiumStyle().equals(FutureOptionPremiumStyle.DAILY_MARGIN),
        "Premium style should be DAILY_MARGIN");
    double strike = futureOption.getStrikePrice();
    ResolvedBondFuture future = futureOption.getUnderlyingFuture();
    double volatility = volatilities.volatility(
        futureOption.getExpiry(), future.getLastTradeDate(), strike, futurePrice);
    double timeToExpiry = volatilities.relativeTime(futureOption.getExpiry());
    double price = BlackFormulaRepository.price(
        futurePrice, strike, timeToExpiry, volatility, futureOption.getPutCall().isCall());
    return price;
  }

  double price(
      ResolvedBondFutureOption futureOption,
      LegalEntityDiscountingProvider discountingProvider,
      BondFutureVolatilities volatilities) {

    ArgChecker.isTrue(volatilities instanceof BlackBondFutureVolatilities,
        "Provider must be of type BlackVolatilityBondFutureProvider");
    return price(futureOption, discountingProvider, (BlackBondFutureVolatilities) volatilities);
  }

  //-------------------------------------------------------------------------
  /**
   * Calculates the delta of the bond future option product.
   * <p>
   * The delta of the product is the sensitivity of the option price to the future price.
   * The volatility is unchanged for a fixed strike in the sensitivity computation, hence the "StickyStrike" name.
   * <p>
   * This calculates the underlying future price using the future pricer.
   * 
   * @param futureOption  the option product
   * @param discountingProvider  the discounting provider
   * @param volatilities  the volatilities
   * @return the price curve sensitivity of the product
   */
  public double deltaStickyStrike(
      ResolvedBondFutureOption futureOption,
      LegalEntityDiscountingProvider discountingProvider,
      BlackBondFutureVolatilities volatilities) {

    double futurePrice = futurePrice(futureOption, discountingProvider);
    return deltaStickyStrike(futureOption, discountingProvider, volatilities, futurePrice);
  }

  /**
   * Calculates the delta of the bond future option product based on the price of the underlying future.
   * <p>
   * The delta of the product is the sensitivity of the option price to the future price.
   * The volatility is unchanged for a fixed strike in the sensitivity computation, hence the "StickyStrike" name.
   * 
   * @param futureOption  the option product
   * @param discountingProvider  the discounting provider
   * @param volatilities  the volatilities
   * @param futurePrice  the price of the underlying future
   * @return the price curve sensitivity of the product
   */
  public double deltaStickyStrike(
      ResolvedBondFutureOption futureOption,
      LegalEntityDiscountingProvider discountingProvider,
      BlackBondFutureVolatilities volatilities,
      double futurePrice) {

    ArgChecker.isTrue(futureOption.getPremiumStyle().equals(FutureOptionPremiumStyle.DAILY_MARGIN),
        "Premium style should be DAILY_MARGIN");
    double strike = futureOption.getStrikePrice();
    ResolvedBondFuture future = futureOption.getUnderlyingFuture();
    double volatility = volatilities.volatility(futureOption.getExpiry(),
        future.getLastTradeDate(), strike, futurePrice);
    double timeToExpiry = volatilities.relativeTime(futureOption.getExpiry());
    double delta = BlackFormulaRepository.delta(
        futurePrice, strike, timeToExpiry, volatility, futureOption.getPutCall().isCall());
    return delta;
  }

  //-------------------------------------------------------------------------
  /**
   * Calculates the gamma of the bond future option product.
   * <p>
   * The gamma of the product is the sensitivity of the option delta to the future price.
   * The volatility is unchanged for a fixed strike in the sensitivity computation, hence the "StickyStrike" name.
   * <p>
   * This calculates the underlying future price using the future pricer.
   * 
   * @param futureOption  the option product
   * @param discountingProvider  the discounting provider
   * @param volatilities  the volatilities
   * @return the price curve sensitivity of the product
   */
  public double gammaStickyStrike(
      ResolvedBondFutureOption futureOption,
      LegalEntityDiscountingProvider discountingProvider,
      BlackBondFutureVolatilities volatilities) {

    double futurePrice = futurePrice(futureOption, discountingProvider);
    return gammaStickyStrike(futureOption, discountingProvider, volatilities, futurePrice);
  }

  /**
   * Calculates the gamma of the bond future option product based on the price of the underlying future.
   * <p>
   * The gamma of the product is the sensitivity of the option delta to the future price.
   * The volatility is unchanged for a fixed strike in the sensitivity computation, hence the "StickyStrike" name.
   * 
   * @param futureOption  the option product
   * @param discountingProvider  the discounting provider
   * @param volatilities  the volatilities
   * @param futurePrice  the price of the underlying future
   * @return the price curve sensitivity of the product
   */
  public double gammaStickyStrike(
      ResolvedBondFutureOption futureOption,
      LegalEntityDiscountingProvider discountingProvider,
      BlackBondFutureVolatilities volatilities,
      double futurePrice) {

    ArgChecker.isTrue(futureOption.getPremiumStyle().equals(FutureOptionPremiumStyle.DAILY_MARGIN),
        "Premium style should be DAILY_MARGIN");
    double strike = futureOption.getStrikePrice();
    ResolvedBondFuture future = futureOption.getUnderlyingFuture();
    double volatility = volatilities.volatility(futureOption.getExpiry(),
        future.getLastTradeDate(), strike, futurePrice);
    double timeToExpiry = volatilities.relativeTime(futureOption.getExpiry());
    double gamma = BlackFormulaRepository.gamma(futurePrice, strike, timeToExpiry, volatility);
    return gamma;
  }

  //-------------------------------------------------------------------------
  /**
   * Calculates the theta of the bond future option product.
   * <p>
   * The theta of the product is the minus of the option price sensitivity to the time to expiry.
   * The volatility is unchanged for a fixed strike in the sensitivity computation, hence the "StickyStrike" name.
   * <p>
   * This calculates the underlying future price using the future pricer.
   * 
   * @param futureOption  the option product
   * @param discountingProvider  the discounting provider
   * @param volatilities  the volatilities
   * @return the price curve sensitivity of the product
   */
  public double theta(
      ResolvedBondFutureOption futureOption,
      LegalEntityDiscountingProvider discountingProvider,
      BlackBondFutureVolatilities volatilities) {

    double futurePrice = futurePrice(futureOption, discountingProvider);
    return theta(futureOption, discountingProvider, volatilities, futurePrice);
  }

  /**
   * Calculates the theta of the bond future option product based on the price of the underlying future.
   * <p>
   * The theta of the product is minus of the option price sensitivity to the time to expiry.
   * The volatility is unchanged for a fixed strike in the sensitivity computation, hence the "StickyStrike" name.
   * 
   * @param futureOption  the option product
   * @param discountingProvider  the discounting provider
   * @param volatilities  the volatilities
   * @param futurePrice  the price of the underlying future
   * @return the price curve sensitivity of the product
   */
  public double theta(
      ResolvedBondFutureOption futureOption,
      LegalEntityDiscountingProvider discountingProvider,
      BlackBondFutureVolatilities volatilities,
      double futurePrice) {

    ArgChecker.isTrue(futureOption.getPremiumStyle().equals(FutureOptionPremiumStyle.DAILY_MARGIN),
        "Premium style should be DAILY_MARGIN");
    double strike = futureOption.getStrikePrice();
    ResolvedBondFuture future = futureOption.getUnderlyingFuture();
    double volatility = volatilities.volatility(futureOption.getExpiry(),
        future.getLastTradeDate(), strike, futurePrice);
    double timeToExpiry = volatilities.relativeTime(futureOption.getExpiry());
    double theta = BlackFormulaRepository.driftlessTheta(futurePrice, strike, timeToExpiry, volatility);
    return theta;
  }

  //-------------------------------------------------------------------------
  /**
   * Calculates the price sensitivity of the bond future option product based on curves.
   * <p>
   * The price sensitivity of the product is the sensitivity of the price to the underlying curves.
   * The volatility is unchanged for a fixed strike in the sensitivity computation, hence the "StickyStrike" name.
   * <p>
   * This calculates the underlying future price using the future pricer.
   * 
   * @param futureOption  the option product
   * @param discountingProvider  the discounting provider
   * @param volatilities  the volatilities
   * @return the price curve sensitivity of the product
   */
  public PointSensitivities priceSensitivityRatesStickyStrike(
      ResolvedBondFutureOption futureOption,
      LegalEntityDiscountingProvider discountingProvider,
      BlackBondFutureVolatilities volatilities) {

    ArgChecker.isTrue(futureOption.getPremiumStyle().equals(FutureOptionPremiumStyle.DAILY_MARGIN),
        "Premium style should be DAILY_MARGIN");
    double futurePrice = futurePrice(futureOption, discountingProvider);
    return priceSensitivityRatesStickyStrike(futureOption, discountingProvider, volatilities, futurePrice);
  }

  /**
   * Calculates the price sensitivity of the bond future option product based on the price of the underlying future.
   * <p>
   * The price sensitivity of the product is the sensitivity of the price to the underlying curves.
   * The volatility is unchanged for a fixed strike in the sensitivity computation, hence the "StickyStrike" name.
   * 
   * @param futureOption  the option product
   * @param discountingProvider  the discounting provider
   * @param volatilities  the volatilities
   * @param futurePrice  the price of the underlying future
   * @return the price curve sensitivity of the product
   */
  public PointSensitivities priceSensitivityRatesStickyStrike(
      ResolvedBondFutureOption futureOption,
      LegalEntityDiscountingProvider discountingProvider,
      BlackBondFutureVolatilities volatilities,
      double futurePrice) {

    double delta = deltaStickyStrike(futureOption, discountingProvider, volatilities, futurePrice);
    PointSensitivities futurePriceSensitivity =
        futurePricer.priceSensitivity(futureOption.getUnderlyingFuture(), discountingProvider);
    return futurePriceSensitivity.multipliedBy(delta);
  }

  PointSensitivities priceSensitivity(
      ResolvedBondFutureOption futureOption,
      LegalEntityDiscountingProvider discountingProvider,
      BondFutureVolatilities volatilities) {

    ArgChecker.isTrue(volatilities instanceof BlackBondFutureVolatilities,
        "Provider must be of type BlackVolatilityBondFutureProvider");
    return priceSensitivityRatesStickyStrike(
        futureOption, discountingProvider, (BlackBondFutureVolatilities) volatilities);
  }

  //-------------------------------------------------------------------------
  /**
   * Calculates the price sensitivity to the Black volatility used for the pricing of the bond future option.
   * <p>
   * This calculates the underlying future price using the future pricer.
   * 
   * @param futureOption  the option product
   * @param discountingProvider  the discounting provider
   * @param volatilities  the volatilities
   * @return the sensitivity
   */
  public BondFutureOptionSensitivity priceSensitivityModelParamsVolatility(
      ResolvedBondFutureOption futureOption,
      LegalEntityDiscountingProvider discountingProvider,
      BlackBondFutureVolatilities volatilities) {

    double futurePrice = futurePrice(futureOption, discountingProvider);
    return priceSensitivityModelParamsVolatility(futureOption, discountingProvider, volatilities, futurePrice);
  }

  /**
   * Calculates the price sensitivity to the Black volatility used for the pricing of the bond future option
   * based on the price of the underlying future.
   * 
   * @param futureOption  the option product
   * @param discountingProvider  the discounting provider
   * @param volatilities  the volatilities
   * @param futurePrice  the underlying future price
   * @return the sensitivity
   */
  public BondFutureOptionSensitivity priceSensitivityModelParamsVolatility(
      ResolvedBondFutureOption futureOption,
      LegalEntityDiscountingProvider discountingProvider,
      BlackBondFutureVolatilities volatilities,
      double futurePrice) {

    ArgChecker.isTrue(futureOption.getPremiumStyle().equals(FutureOptionPremiumStyle.DAILY_MARGIN),
        "Premium style should be DAILY_MARGIN");
    double strike = futureOption.getStrikePrice();
    ResolvedBondFuture future = futureOption.getUnderlyingFuture();
    double volatility = volatilities.volatility(futureOption.getExpiry(),
        future.getLastTradeDate(), strike, futurePrice);
    double timeToExpiry = volatilities.relativeTime(futureOption.getExpiry());
    double vega = BlackFormulaRepository.vega(futurePrice, strike, timeToExpiry, volatility);
    return BondFutureOptionSensitivity.of(
        volatilities.getName(), timeToExpiry, future.getLastTradeDate(), strike, futurePrice, future.getCurrency(), vega);
  }

  //-------------------------------------------------------------------------
  // calculate the price of the underlying future
  private double futurePrice(ResolvedBondFutureOption futureOption, LegalEntityDiscountingProvider discountingProvider) {
    ResolvedBondFuture future = futureOption.getUnderlyingFuture();
    return futurePricer.price(future, discountingProvider);
  }

}
