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

import java.time.LocalDate;

import com.opengamma.strata.basics.currency.MultiCurrencyAmount;
import com.opengamma.strata.basics.currency.Payment;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.market.sensitivity.PointSensitivities;
import com.opengamma.strata.pricer.ZeroRateSensitivity;
import com.opengamma.strata.pricer.rate.RatesProvider;
import com.opengamma.strata.product.fx.ResolvedFxSingle;
import com.opengamma.strata.product.fx.ResolvedFxSwap;

/**
 * Pricer for foreign exchange swap transaction products.
 * <p>
 * This provides the ability to price an {@link ResolvedFxSwap}.
 */
public class DiscountingFxSwapProductPricer {

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

  /**
   * Underlying single FX pricer.
   */
  private final DiscountingFxSingleProductPricer fxPricer;

  /**
   * Creates an instance.
   * 
   * @param fxPricer  the pricer for {@link ResolvedFxSingle}
   */
  public DiscountingFxSwapProductPricer(
      DiscountingFxSingleProductPricer fxPricer) {
    this.fxPricer = ArgChecker.notNull(fxPricer, "fxPricer");
  }

  //-------------------------------------------------------------------------
  /**
   * Calculates the present value of the FX swap product.
   * <p>
   * This discounts each payment on each leg in its own currency.
   * 
   * @param swap  the product
   * @param provider  the rates provider
   * @return the present value in the two natural currencies
   */
  public MultiCurrencyAmount presentValue(ResolvedFxSwap swap, RatesProvider provider) {
    MultiCurrencyAmount farPv = fxPricer.presentValue(swap.getFarLeg(), provider);
    MultiCurrencyAmount nearPv = fxPricer.presentValue(swap.getNearLeg(), provider);
    return nearPv.plus(farPv);
  }

  /**
   * Calculates the present value sensitivity of the FX swap product.
   * <p>
   * The present value sensitivity of the product is the sensitivity of the present value to
   * the underlying curves.
   * 
   * @param swap  the product
   * @param provider  the rates provider
   * @return the present value sensitivity
   */
  public PointSensitivities presentValueSensitivity(ResolvedFxSwap swap, RatesProvider provider) {
    PointSensitivities nearSens = fxPricer.presentValueSensitivity(swap.getNearLeg(), provider);
    PointSensitivities farSens = fxPricer.presentValueSensitivity(swap.getFarLeg(), provider);
    return nearSens.combinedWith(farSens);
  }

  //-------------------------------------------------------------------------
  /**
   * Calculates the par spread.
   * <p>
   * The par spread is the spread that should be added to the FX forward points to have a zero value.
   * 
   * @param swap  the product
   * @param provider  the rates provider
   * @return the spread
   */
  public double parSpread(ResolvedFxSwap swap, RatesProvider provider) {
    Payment counterPaymentNear = swap.getNearLeg().getCounterCurrencyPayment();
    MultiCurrencyAmount pv = presentValue(swap, provider);
    double pvCounterCcy = pv.convertedTo(counterPaymentNear.getCurrency(), provider).getAmount();
    double dfEnd = provider.discountFactor(counterPaymentNear.getCurrency(), swap.getFarLeg().getPaymentDate());
    double notionalBaseCcy = swap.getNearLeg().getBaseCurrencyPayment().getAmount();
    return -pvCounterCcy / (notionalBaseCcy * dfEnd);
  }

  /**
   * Calculates the par spread sensitivity to the curves.
   * <p>
   * The sensitivity is reported in the counter currency of the product, but is actually dimensionless.
   * 
   * @param swap  the product
   * @param provider  the rates provider
   * @return the spread curve sensitivity
   */
  public PointSensitivities parSpreadSensitivity(ResolvedFxSwap swap, RatesProvider provider) {
    Payment counterPaymentNear = swap.getNearLeg().getCounterCurrencyPayment();
    MultiCurrencyAmount pv = presentValue(swap, provider);
    double pvCounterCcy = pv.convertedTo(counterPaymentNear.getCurrency(), provider).getAmount();
    double dfEnd = provider.discountFactor(counterPaymentNear.getCurrency(), swap.getFarLeg().getPaymentDate());
    double notionalBaseCcy = swap.getNearLeg().getBaseCurrencyPayment().getAmount();
    double ps = -pvCounterCcy / (notionalBaseCcy * dfEnd);
    // backward sweep
    double psBar = 1d;
    double pvCounterCcyBar = -1d / (notionalBaseCcy * dfEnd) * psBar;
    double dfEndBar = -ps / dfEnd * psBar;
    ZeroRateSensitivity ddfEnddr = provider.discountFactors(counterPaymentNear.getCurrency())
        .zeroRatePointSensitivity(swap.getFarLeg().getPaymentDate());
    PointSensitivities result = ddfEnddr.multipliedBy(dfEndBar).build();
    PointSensitivities dpvdr = presentValueSensitivity(swap, provider);
    PointSensitivities dpvdrConverted = dpvdr.convertedTo(counterPaymentNear.getCurrency(), provider);
    return result.combinedWith(dpvdrConverted.multipliedBy(pvCounterCcyBar));
  }

  //-------------------------------------------------------------------------
  /**
   * Calculates the currency exposure of the FX swap product.
   * <p>
   * This discounts each payment on each leg in its own currency.
   * 
   * @param product  the product
   * @param provider  the rates provider
   * @return the currency exposure
   */
  public MultiCurrencyAmount currencyExposure(ResolvedFxSwap product, RatesProvider provider) {
    return presentValue(product, provider);
  }

  /**
   * Calculates the current cash of the FX swap product.
   * 
   * @param swap  the product
   * @param valuationDate  the valuation date
   * @return the current cash
   */
  public MultiCurrencyAmount currentCash(ResolvedFxSwap swap, LocalDate valuationDate) {
    MultiCurrencyAmount farPv = fxPricer.currentCash(swap.getFarLeg(), valuationDate);
    MultiCurrencyAmount nearPv = fxPricer.currentCash(swap.getNearLeg(), valuationDate);
    return nearPv.plus(farPv);
  }

}
