/**
 * For an instrument, this calculates the sensitivity of the present value (PV) to points on the
 * yield curve(s) (i.e. dPV/dR at every point the instrument has sensitivity). The return format is
 * a map with curve names (String) as keys and List of DoublesPair as the values; each list holds
 * set of time (corresponding to point of the yield curve) and sensitivity pairs (i.e. dPV/dR at
 * that time). <b>Note:</b> The length of the list is instrument dependent and may have repeated
 * times (with the understanding the sensitivities should be summed).
 */
public class PresentValueCurveSensitivityCalculator
    extends AbstractInstrumentDerivativeVisitor<YieldCurveBundle, Map<String, List<DoublesPair>>> {
  // TODO: Change the output format from Map to InterestRateCurveSensitivity, which wraps the map
  // and adds common functionality.

  /** The method unique instance. */
  private static final PresentValueCurveSensitivityCalculator INSTANCE =
      new PresentValueCurveSensitivityCalculator();

  /**
   * Return the unique instance of the class.
   *
   * @return The instance.
   */
  public static PresentValueCurveSensitivityCalculator getInstance() {
    return INSTANCE;
  }

  /** Constructor. */
  PresentValueCurveSensitivityCalculator() {}

  /** The method used for OIS coupons. */
  private static final CouponOISDiscountingMethod METHOD_OIS =
      CouponOISDiscountingMethod.getInstance();

  private static final CouponIborDiscountingMethod METHOD_IBOR =
      CouponIborDiscountingMethod.getInstance();
  private static final CouponIborGearingDiscountingMethod METHOD_IBOR_GEARING =
      CouponIborGearingDiscountingMethod.getInstance();
  private static final CashDiscountingMethod METHOD_DEPOSIT = CashDiscountingMethod.getInstance();
  private static final DepositZeroDiscountingMethod METHOD_DEPOSIT_ZERO =
      DepositZeroDiscountingMethod.getInstance();
  private static final BillSecurityDiscountingMethod METHOD_BILL_SECURITY =
      BillSecurityDiscountingMethod.getInstance();
  private static final BillTransactionDiscountingMethod METHOD_BILL_TRANSACTION =
      BillTransactionDiscountingMethod.getInstance();

  @Override
  public Map<String, List<DoublesPair>> visit(
      final InstrumentDerivative instrument, final YieldCurveBundle curves) {
    return instrument.accept(this, curves);
  }

  @Override
  public Map<String, List<DoublesPair>> visitCash(final Cash cash, final YieldCurveBundle curves) {
    return METHOD_DEPOSIT.presentValueCurveSensitivity(cash, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitDepositZero(
      final DepositZero deposit, final YieldCurveBundle curves) {
    return METHOD_DEPOSIT_ZERO.presentValueCurveSensitivity(deposit, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitForwardRateAgreement(
      final ForwardRateAgreement fra, final YieldCurveBundle curves) {
    final ForwardRateAgreementDiscountingMethod method =
        ForwardRateAgreementDiscountingMethod.getInstance();
    return method.presentValueCurveSensitivity(fra, curves).getSensitivities();
  }

  /** {@inheritDoc} Future transaction pricing without convexity adjustment. */
  @Override
  public Map<String, List<DoublesPair>> visitInterestRateFuture(
      final InterestRateFuture future, final YieldCurveBundle curves) {
    final InterestRateFutureDiscountingMethod method =
        InterestRateFutureDiscountingMethod.getInstance();
    return method.presentValueCurveSensitivity(future, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitBondFixedSecurity(
      final BondFixedSecurity bond, final YieldCurveBundle curves) {
    final BondSecurityDiscountingMethod method = BondSecurityDiscountingMethod.getInstance();
    return method.presentValueCurveSensitivity(bond, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitBondFixedTransaction(
      final BondFixedTransaction bond, final YieldCurveBundle curves) {
    final BondTransactionDiscountingMethod method = BondTransactionDiscountingMethod.getInstance();
    return method.presentValueSensitivity(bond, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitBondIborTransaction(
      final BondIborTransaction bond, final YieldCurveBundle curves) {
    final BondTransactionDiscountingMethod method = BondTransactionDiscountingMethod.getInstance();
    return method.presentValueSensitivity(bond, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitBillSecurity(
      final BillSecurity bill, final YieldCurveBundle curves) {
    return METHOD_BILL_SECURITY.presentValueCurveSensitivity(bill, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitBillTransaction(
      final BillTransaction bill, final YieldCurveBundle curves) {
    return METHOD_BILL_TRANSACTION.presentValueCurveSensitivity(bill, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitBondFuture(
      final BondFuture bondFuture, final YieldCurveBundle curves) {
    Validate.notNull(curves);
    Validate.notNull(bondFuture);
    final BondFutureDiscountingMethod method = BondFutureDiscountingMethod.getInstance();
    return method.presentValueCurveSensitivity(bondFuture, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitSwap(
      final Swap<?, ?> swap, final YieldCurveBundle curves) {
    final Map<String, List<DoublesPair>> senseR = visit(swap.getSecondLeg(), curves);
    final Map<String, List<DoublesPair>> senseP = visit(swap.getFirstLeg(), curves);
    return addSensitivity(senseR, senseP);
  }

  @Override
  public Map<String, List<DoublesPair>> visitFixedCouponSwap(
      final SwapFixedCoupon<?> swap, final YieldCurveBundle curves) {
    return visitSwap(swap, curves);
  }

  @Override
  public Map<String, List<DoublesPair>> visitTenorSwap(
      final TenorSwap<? extends Payment> swap, final YieldCurveBundle curves) {
    return visitSwap(swap, curves);
  }

  @Override
  public Map<String, List<DoublesPair>> visitFloatingRateNote(
      final FloatingRateNote frn, final YieldCurveBundle curves) {
    return visitSwap(frn, curves);
  }

  @Override
  public Map<String, List<DoublesPair>> visitCrossCurrencySwap(
      final CrossCurrencySwap ccs, final YieldCurveBundle curves) {
    final Map<String, List<DoublesPair>> senseD = visit(ccs.getDomesticLeg(), curves);
    final Map<String, List<DoublesPair>> senseF = visit(ccs.getForeignLeg(), curves);
    // Note the sensitivities subtract rather than add here because the CCS is set up as domestic
    // FRN minus a foreign FRN
    return addSensitivity(senseD, multiplySensitivity(senseF, -ccs.getSpotFX()));
  }

  @Override
  public Map<String, List<DoublesPair>> visitForexForward(
      final ForexForward fx, final YieldCurveBundle curves) {
    final Map<String, List<DoublesPair>> senseP1 = visit(fx.getPaymentCurrency1(), curves);
    final Map<String, List<DoublesPair>> senseP2 = visit(fx.getPaymentCurrency2(), curves);
    // Note the sensitivities add rather than subtract here because the FX Forward is set up as a
    // notional in one currency PLUS a notional in another  with the  opposite sign
    return InterestRateCurveSensitivityUtils.addSensitivity(
        senseP1, multiplySensitivity(senseP2, fx.getSpotForexRate()));
  }

  @Override
  public Map<String, List<DoublesPair>> visitGenericAnnuity(
      final Annuity<? extends Payment> annuity, final YieldCurveBundle data) {
    final Map<String, List<DoublesPair>> map = new HashMap<String, List<DoublesPair>>();
    for (final Payment p : annuity.getPayments()) {
      final Map<String, List<DoublesPair>> tempMap = visit(p, data);
      for (final String name : tempMap.keySet()) {
        if (!map.containsKey(name)) {
          map.put(name, tempMap.get(name));
        } else {
          final List<DoublesPair> tempList = map.get(name);
          tempList.addAll(tempMap.get(name));
          map.put(name, tempList);
        }
      }
    }
    return map;
  }

  @Override
  public Map<String, List<DoublesPair>> visitFixedPayment(
      final PaymentFixed payment, final YieldCurveBundle data) {
    final String curveName = payment.getFundingCurveName();
    final YieldAndDiscountCurve curve = data.getCurve(curveName);
    final double t = payment.getPaymentTime();

    final DoublesPair s = new DoublesPair(t, -t * payment.getAmount() * curve.getDiscountFactor(t));
    final List<DoublesPair> list = new ArrayList<DoublesPair>();
    list.add(s);
    final Map<String, List<DoublesPair>> result = new HashMap<String, List<DoublesPair>>();
    result.put(curveName, list);
    return result;
  }

  @Override
  public Map<String, List<DoublesPair>> visitCouponIborSpread(
      final CouponIborSpread payment, final YieldCurveBundle data) {
    final String fundingCurveName = payment.getFundingCurveName();
    final String liborCurveName = payment.getForwardCurveName();
    final YieldAndDiscountCurve fundCurve = data.getCurve(fundingCurveName);
    final YieldAndDiscountCurve liborCurve = data.getCurve(liborCurveName);

    final double tPay = payment.getPaymentTime();
    final double tStart = payment.getFixingPeriodStartTime();
    final double tEnd = payment.getFixingPeriodEndTime();
    final double dfPay = fundCurve.getDiscountFactor(tPay);
    final double dfStart = liborCurve.getDiscountFactor(tStart);
    final double dfEnd = liborCurve.getDiscountFactor(tEnd);
    final double forward = (dfStart / dfEnd - 1) / payment.getFixingYearFraction();
    final double notional = payment.getNotional();

    final Map<String, List<DoublesPair>> result = new HashMap<String, List<DoublesPair>>();

    List<DoublesPair> temp = new ArrayList<DoublesPair>();
    DoublesPair s;
    s =
        new DoublesPair(
            tPay,
            -tPay
                * dfPay
                * notional
                * (forward + payment.getSpread())
                * payment.getPaymentYearFraction());
    temp.add(s);

    if (!liborCurveName.equals(fundingCurveName)) {
      result.put(fundingCurveName, temp);
      temp = new ArrayList<DoublesPair>();
    }

    final double ratio =
        notional
            * dfPay
            * dfStart
            / dfEnd
            * payment.getPaymentYearFraction()
            / payment.getFixingYearFraction();
    s = new DoublesPair(tStart, -tStart * ratio);
    temp.add(s);
    s = new DoublesPair(tEnd, tEnd * ratio);
    temp.add(s);

    result.put(liborCurveName, temp);

    return result;
  }

  @Override
  public Map<String, List<DoublesPair>> visitCouponOIS(
      final CouponOIS payment, final YieldCurveBundle data) {
    final Map<String, List<DoublesPair>> result =
        METHOD_OIS.presentValueCurveSensitivity(payment, data).getSensitivities();
    return result;
  }

  @Override
  public Map<String, List<DoublesPair>> visitFixedCouponAnnuity(
      final AnnuityCouponFixed annuity, final YieldCurveBundle data) {
    return visitGenericAnnuity(annuity, data);
  }

  @Override
  public Map<String, List<DoublesPair>> visitCouponFixed(
      final CouponFixed payment, final YieldCurveBundle data) {
    return visitFixedPayment(payment.toPaymentFixed(), data);
  }

  @Override
  public Map<String, List<DoublesPair>> visitForwardLiborAnnuity(
      final AnnuityCouponIbor annuity, final YieldCurveBundle data) {
    return visitGenericAnnuity(annuity, data);
  }

  @Override
  public Map<String, List<DoublesPair>> visitFixedFloatSwap(
      final FixedFloatSwap swap, final YieldCurveBundle data) {
    return visitFixedCouponSwap(swap, data);
  }

  @Override
  public Map<String, List<DoublesPair>> visitCouponCMS(
      final CouponCMS payment, final YieldCurveBundle data) {
    final CouponCMSDiscountingMethod method = CouponCMSDiscountingMethod.getInstance();
    return method.presentValueSensitivity(payment, data).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitCouponIbor(
      final CouponIbor coupon, final YieldCurveBundle curves) {
    return METHOD_IBOR.presentValueCurveSensitivity(coupon, curves).getSensitivities();
  }

  @Override
  public Map<String, List<DoublesPair>> visitCouponIborGearing(
      final CouponIborGearing coupon, final YieldCurveBundle curves) {
    return METHOD_IBOR_GEARING.presentValueCurveSensitivity(coupon, curves).getSensitivities();
  }

  /**
   * Compute the sensitivity of the discount factor at a given time.
   *
   * @param curveName The curve name associated to the discount factor.
   * @param curve The curve from which the discount factor should be computed.
   * @param time The time
   * @return The sensitivity.
   */
  public static Map<String, List<DoublesPair>> discountFactorSensitivity(
      final String curveName, final YieldAndDiscountCurve curve, final double time) {
    final DoublesPair s = new DoublesPair(time, -time * curve.getDiscountFactor(time));
    final List<DoublesPair> list = new ArrayList<DoublesPair>();
    list.add(s);
    final Map<String, List<DoublesPair>> result = new HashMap<String, List<DoublesPair>>();
    result.put(curveName, list);
    return result;
  }
}
/**
 * Get the single fixed rate that makes the PV of the instrument zero. For fixed-float swaps this is
 * the swap rate, for FRAs it is the forward etc. For instruments that cannot PV to zero, e.g.
 * bonds, a single payment of -1.0 is assumed at zero (i.e. the bond must PV to 1.0)
 *
 * @deprecated Use the par rate calculators that reference {@link ParameterProviderInterface}
 */
@Deprecated
public final class ParRateCalculator
    extends InstrumentDerivativeVisitorAdapter<YieldCurveBundle, Double> {

  /** The unique instance of the calculator. */
  private static final ParRateCalculator INSTANCE = new ParRateCalculator();

  /**
   * Gets the calculator instance.
   *
   * @return The calculator.
   */
  public static ParRateCalculator getInstance() {
    return INSTANCE;
  }

  /** Constructor. */
  private ParRateCalculator() {}

  /** The methods and calculators. */
  private static final PresentValueCalculator PVC = PresentValueCalculator.getInstance();

  private static final CouponONDiscountingMethod METHOD_OIS =
      CouponONDiscountingMethod.getInstance();
  private static final CouponIborDiscountingMethod METHOD_IBOR =
      CouponIborDiscountingMethod.getInstance();
  private static final DepositZeroDiscountingMethod METHOD_DEPOSIT_ZERO =
      DepositZeroDiscountingMethod.getInstance();
  private static final ForwardRateAgreementDiscountingBundleMethod METHOD_FRA =
      ForwardRateAgreementDiscountingBundleMethod.getInstance();
  private static final SwapFixedCouponDiscountingMethod METHOD_SWAP =
      SwapFixedCouponDiscountingMethod.getInstance();

  private static final InterestRateFutureTransactionDiscountingMethod METHOD_IRFUT_TRANSACTION =
      InterestRateFutureTransactionDiscountingMethod.getInstance();
  private static final InterestRateFutureSecurityDiscountingMethod METHOD_IRFUT_SECURITY =
      InterestRateFutureSecurityDiscountingMethod.getInstance();

  //     -----     Deposit     -----

  // TODO: review
  @Override
  public Double visitCash(final Cash cash, final YieldCurveBundle curves) {
    final YieldAndDiscountCurve curve = curves.getCurve(cash.getYieldCurveName());
    final double ta = cash.getStartTime();
    final double tb = cash.getEndTime();
    final double yearFrac = cash.getAccrualFactor();
    // TODO need a getForwardRate method on YieldAndDiscountCurve
    if (yearFrac == 0.0) {
      if (!CompareUtils.closeEquals(ta, tb, 1e-16)) {
        throw new IllegalArgumentException(
            "Year fraction is zero, but payment time greater than trade time");
      }
      final double eps = 1e-8;
      final double rate = curve.getInterestRate(ta);
      final double dRate = curve.getInterestRate(ta + eps);
      return rate + ta * (dRate - rate) / eps;
    }
    return (curve.getDiscountFactor(ta) / curve.getDiscountFactor(tb) - 1) / yearFrac;
  }

  @Override
  public Double visitDepositZero(final DepositZero deposit, final YieldCurveBundle curves) {
    return METHOD_DEPOSIT_ZERO.parRate(deposit, curves);
  }

  //     -----     Payment/Coupon     ------

  @Override
  public Double visitForwardRateAgreement(
      final ForwardRateAgreement fra, final YieldCurveBundle curves) {
    return METHOD_FRA.parRate(fra, curves);
  }

  @Override
  public Double visitCouponIbor(final CouponIbor payment, final YieldCurveBundle data) {
    return METHOD_IBOR.parRate(payment, data);
  }

  @Override
  public Double visitCouponIborSpread(final CouponIborSpread payment, final YieldCurveBundle data) {
    final YieldAndDiscountCurve curve = data.getCurve(payment.getForwardCurveName());
    return (curve.getDiscountFactor(payment.getFixingPeriodStartTime())
                / curve.getDiscountFactor(payment.getFixingPeriodEndTime())
            - 1.0)
        / payment.getFixingAccrualFactor();
  }

  @Override
  public Double visitCouponIborGearing(
      final CouponIborGearing payment, final YieldCurveBundle data) {
    final YieldAndDiscountCurve curve = data.getCurve(payment.getForwardCurveName());
    return (curve.getDiscountFactor(payment.getFixingPeriodStartTime())
                / curve.getDiscountFactor(payment.getFixingPeriodEndTime())
            - 1.0)
        / payment.getFixingAccrualFactor();
  }

  @Override
  public Double visitCouponOIS(final CouponON payment, final YieldCurveBundle data) {
    return METHOD_OIS.parRate(payment, data);
  }

  @Override
  public Double visitCapFloorIbor(final CapFloorIbor payment, final YieldCurveBundle data) {
    return visitCouponIborSpread(payment.toCoupon(), data);
  }

  //     -----     Swap     -----

  /**
   * Computes the par rate of a swap with one fixed leg.
   *
   * @param swap The Fixed coupon swap.
   * @param curves The curves.
   * @return The par swap rate. If the fixed leg has been set up with some fixed payments these are
   *     ignored for the purposes of finding the swap rate
   */
  @Override
  public Double visitFixedCouponSwap(final SwapFixedCoupon<?> swap, final YieldCurveBundle curves) {
    final double pvSecond = swap.getSecondLeg().accept(PVC, curves);
    final double pvbp = swap.getFixedLeg().withUnitCoupon().accept(PVC, curves);
    return -pvSecond / pvbp;
  }

  /**
   * Computes the swap convention-modified par rate for a fixed coupon swap.
   *
   * <p>Reference: Swaption pricing - v 1.3, OpenGamma Quantitative Research, June 2012.
   *
   * @param swap The swap.
   * @param dayCount The day count convention to modify the swap rate.
   * @param curves The curves.
   * @return The modified rate.
   */
  public Double visitFixedCouponSwap(
      final SwapFixedCoupon<?> swap, final DayCount dayCount, final YieldCurveBundle curves) {
    final double pvbp = METHOD_SWAP.presentValueBasisPoint(swap, dayCount, curves);
    return visitFixedCouponSwap(swap, pvbp, curves);
  }

  /**
   * Computes the swap convention-modified par rate for a fixed coupon swap.
   *
   * <p>Reference: Swaption pricing - v 1.3, OpenGamma Quantitative Research, June 2012.
   *
   * @param swap The swap.
   * @param dayCount The day count convention to modify the swap rate.
   * @param curves The curves.
   * @param calendar The calendar
   * @return The modified rate.
   */
  public Double visitFixedCouponSwap(
      final SwapFixedCoupon<?> swap,
      final DayCount dayCount,
      final YieldCurveBundle curves,
      final Calendar calendar) {
    final double pvbp = METHOD_SWAP.presentValueBasisPoint(swap, dayCount, calendar, curves);
    return visitFixedCouponSwap(swap, pvbp, curves);
  }

  /**
   * Computes the swap convention-modified par rate for a fixed coupon swap with a PVBP externally
   * provided.
   *
   * <p>Reference: Swaption pricing - v 1.3, OpenGamma Quantitative Research, June 2012.
   *
   * @param swap The swap.
   * @param pvbp The present value of a basis point.
   * @param curves The curves.
   * @return The modified rate.
   */
  public Double visitFixedCouponSwap(
      final SwapFixedCoupon<?> swap, final double pvbp, final YieldCurveBundle curves) {
    final double pvSecond =
        -swap.getSecondLeg().accept(PVC, curves)
            * Math.signum(swap.getSecondLeg().getNthPayment(0).getNotional());
    return -pvSecond / pvbp;
  }

  //     -----     Futures     -----

  /** {@inheritDoc} Compute the future rate (1-price) without convexity adjustment. */
  @Override
  public Double visitInterestRateFutureTransaction(
      final InterestRateFutureTransaction future, final YieldCurveBundle curves) {
    return METHOD_IRFUT_TRANSACTION.parRate(future, curves);
  }

  /** {@inheritDoc} Compute the future rate (1-price) without convexity adjustment. */
  @Override
  public Double visitInterestRateFutureSecurity(
      final InterestRateFutureSecurity future, final YieldCurveBundle curves) {
    return METHOD_IRFUT_SECURITY.parRate(future, curves);
  }

  // TODO: review
  @Override
  public Double visitForexForward(final ForexForward fx, final YieldCurveBundle curves) {
    // TODO this is not a par rate, it is a forward FX rate
    final YieldAndDiscountCurve curve1 =
        curves.getCurve(fx.getPaymentCurrency1().getFundingCurveName());
    final YieldAndDiscountCurve curve2 =
        curves.getCurve(fx.getPaymentCurrency2().getFundingCurveName());
    final double t = fx.getPaymentTime();
    return fx.getSpotForexRate() * curve2.getDiscountFactor(t) / curve1.getDiscountFactor(t);
  }

  //     -----     Forex     ------: see ForwardRateForexCalculator

  //     -----     Bond     -----

  // TODO: review
  /**
   * This gives you the bond coupon, for a given yield curve, that renders the bond par (present
   * value of all cash flows equal to 1.0)
   *
   * @param bond the bond
   * @param curves the input curves
   * @return the par rate
   */
  @Override
  public Double visitBondFixedSecurity(
      final BondFixedSecurity bond, final YieldCurveBundle curves) {
    final Annuity<CouponFixed> coupons = bond.getCoupon();
    final int n = coupons.getNumberOfPayments();
    final CouponFixed[] unitCoupons = new CouponFixed[n];
    for (int i = 0; i < n; i++) {
      unitCoupons[i] = coupons.getNthPayment(i).withUnitCoupon();
    }
    final Annuity<CouponFixed> unitCouponAnnuity = new Annuity<>(unitCoupons);
    final double pvann = unitCouponAnnuity.accept(PVC, curves);
    final double matPV = bond.getNominal().accept(PVC, curves);
    return (1 - matPV) / pvann;
  }
}
 @Override
 public Map<String, List<DoublesPair>> visitCouponIbor(
     final CouponIbor coupon, final YieldCurveBundle curves) {
   return METHOD_IBOR.presentValueCurveSensitivity(coupon, curves).getSensitivities();
 }
 @Override
 public Double visitCouponIbor(final CouponIbor payment, final YieldCurveBundle data) {
   return METHOD_IBOR.parRate(payment, data);
 }