/**
 * Compute the spread to be added to the rate of the instrument for which the present value of the
 * instrument is zero. The "rate" can be a "rate" or a "yield" and will depend of each instrument.
 *
 * @deprecated {@link YieldCurveBundle} is deprecated
 */
@Deprecated
public final class ParSpreadRateCalculator
    extends InstrumentDerivativeVisitorAdapter<YieldCurveBundle, Double> {

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

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

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

  /** The methods and calculators. */
  private static final PresentValueMCACalculator PVMCC = PresentValueMCACalculator.getInstance();

  private static final PresentValueBasisPointCalculator PVBPC =
      PresentValueBasisPointCalculator.getInstance();
  private static final CashDiscountingMethod METHOD_DEPOSIT = CashDiscountingMethod.getInstance();
  private static final DepositZeroDiscountingMethod METHOD_DEPOSIT_ZERO =
      DepositZeroDiscountingMethod.getInstance();
  private static final ForwardRateAgreementDiscountingMethod METHOD_FRA =
      ForwardRateAgreementDiscountingMethod.getInstance();
  //  private static final InterestRateFutureTransactionDiscountingMethod
  // METHOD_IR_FUTURES_TRANSACTION = InterestRateFutureTransactionDiscountingMethod.getInstance();
  private static final InterestRateFutureSecurityDiscountingMethod METHOD_IR_FUTURES_SECURITY =
      InterestRateFutureSecurityDiscountingMethod.getInstance();

  @Override
  public Double visitCash(final Cash deposit, final YieldCurveBundle curves) {
    return METHOD_DEPOSIT.parSpread(deposit, curves);
  }

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

  /**
   * For swaps the ParSpread is the spread to be added on each coupon of the first leg to obtain a
   * present value of zero. It is computed as the opposite of the present value of the swap divided
   * by the present value of a basis point of the first leg (as computed by the
   * PresentValueBasisPointCalculator).
   *
   * @param swap The swap.
   * @param curves The yield curve bundle.
   * @return The par spread.
   */
  @Override
  public Double visitSwap(final Swap<?, ?> swap, final YieldCurveBundle curves) {
    Validate.notNull(curves);
    Validate.notNull(swap);
    return -curves
            .getFxRates()
            .convert(swap.accept(PVMCC, curves), swap.getFirstLeg().getCurrency())
            .getAmount()
        / swap.getFirstLeg().accept(PVBPC, curves);
  }

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

  /**
   * For ForwardRateAgreement the ParSpread is the spread to be added to the fixed rate to obtain a
   * present value of zero.
   *
   * @param fra The forward rate agreement.
   * @param curves The yield curve bundle.
   * @return The par spread.
   */
  @Override
  public Double visitForwardRateAgreement(
      final ForwardRateAgreement fra, final YieldCurveBundle curves) {
    Validate.notNull(curves);
    Validate.notNull(fra);
    return METHOD_FRA.parSpread(fra, curves);
  }

  /**
   * For InterestRateFutures the ParSpread is the spread to be added to the reference price to
   * obtain a present value of zero.
   *
   * @param future The futures.
   * @param curves The yield curve bundle.
   * @return The par spread.
   */
  @Override
  public Double visitInterestRateFutureTransaction(
      final InterestRateFutureTransaction future, final YieldCurveBundle curves) {
    return -(METHOD_IR_FUTURES_SECURITY.price(future.getUnderlying(), curves)
        - future.getReferencePrice());
  }

  //  /**
  //   * For InterestRateFutures the ParSpread is the spread to be added to the reference price to
  // obtain a present value of zero.
  //   * @param future The futures.
  //   * @param curves The yield curve bundle.
  //   * @return The par spread.
  //   */
  //  @Override
  //  public Double visitInterestRateFutureSecurity(final InterestRateFutureSecurity future, final
  // YieldCurveBundle curves) {
  //    return -(METHOD_IR_FUTURES_SECURITY.presentValue(future, curves).getAmount());
  //  }
}
/**
 * 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;
  }
}