/**
   * Get the value of the contingent (or protection) leg
   *
   * <p>This exists to duplicate the function of the same name from PresentValueCreditDefaultSwap.
   * <b>Note</b> this version agrees with the ISDA c library.
   *
   * @param valuationDate The valuation date - this is taken to be the same as today
   * @param cds Description of the CDS
   * @param yieldCurve The discount curve
   * @param hazardRateCurve The survival curve
   * @return Present Value of protection (or contingent) leg
   * @deprecated use calculateProtectionLeg
   */
  @Deprecated
  public double calculateContingentLeg(
      final ZonedDateTime valuationDate,
      final CreditDefaultSwapDefinition cds,
      final ISDADateCurve yieldCurve,
      final HazardRateCurve hazardRateCurve) {
    ArgumentChecker.notNull(valuationDate, "null valuationDate");
    ArgumentChecker.notNull(cds, "null cds");
    ArgumentChecker.notNull(yieldCurve, "null yieldCurve");
    ArgumentChecker.notNull(hazardRateCurve, "null hazardRateCurve");

    final LocalDate valueDate = valuationDate.toLocalDate();
    final LocalDate today = valueDate;
    final LocalDate stepinDate = cds.getEffectiveDate().toLocalDate();
    final LocalDate startDate = cds.getStartDate().toLocalDate();
    final LocalDate endDate = cds.getMaturityDate().toLocalDate();
    final double rr = cds.getRecoveryRate();
    final boolean protectionStart = cds.getProtectionStart();

    return cds.getNotional()
        * calculateProtectionLeg(
            today,
            stepinDate,
            valueDate,
            startDate,
            endDate,
            ISDACompliantDateYieldCurve.fromISDADateCurve(yieldCurve),
            ISDACompliantDateCreditCurve.fromHazardRateCurve(hazardRateCurve),
            rr,
            protectionStart);
  }
  /**
   * Get the RPV01 of the premium leg - i.e. the value of the leg per point of spread (expressed as
   * a fraction, so 1bs is 0.0001)
   *
   * <p>This exists to duplicate the function of the same name from PresentValueCreditDefaultSwap.
   * <b>Note</b> this version agrees with the ISDA c library.
   *
   * @param valuationDate The valuation date - this is taken to be the same as today
   * @param cds Description of the CDS
   * @param yieldCurve The discount curve
   * @param hazardRateCurve The survival curve
   * @param priceType Clean or dirty
   * @return The RPV01 of the premium leg
   * @deprecated use calculateRPV01
   */
  @Deprecated
  public double calculatePremiumLeg(
      final ZonedDateTime valuationDate,
      final CreditDefaultSwapDefinition cds,
      final ISDADateCurve yieldCurve,
      final HazardRateCurve hazardRateCurve,
      final PriceType priceType) {

    ArgumentChecker.notNull(valuationDate, "null valuationDate");
    ArgumentChecker.notNull(cds, "null cds");
    ArgumentChecker.notNull(yieldCurve, "null yieldCurve");
    ArgumentChecker.notNull(hazardRateCurve, "null hazardRateCurve");
    ArgumentChecker.notNull(priceType, "null priceType");

    final LocalDate valueDate = valuationDate.toLocalDate();
    final LocalDate today = valueDate;
    final LocalDate stepinDate = cds.getEffectiveDate().toLocalDate();
    final LocalDate startDate = cds.getStartDate().toLocalDate();
    final LocalDate endDate = cds.getMaturityDate().toLocalDate();

    return cds.getNotional()
        * pvPremiumLegPerUnitSpread(
            today,
            stepinDate,
            valueDate,
            startDate,
            endDate,
            cds.getIncludeAccruedPremium(),
            cds.getCouponFrequency().getPeriod(),
            cds.getStubType(),
            ISDACompliantDateYieldCurve.fromISDADateCurve(yieldCurve),
            ISDACompliantDateCreditCurve.fromHazardRateCurve(hazardRateCurve),
            cds.getProtectionStart(),
            priceType);
  }
  /**
   * Computes the risky present value of a premium payment
   *
   * <p>This mimics the ISDA c code function <b>FeePaymentPVWithTimeLine<b>
   *
   * @param today
   * @param valueDate
   * @param stepinDate
   * @param accStartDate
   * @param accEndDate
   * @param payAccOnDefault
   * @return PV
   */
  private double[] calculateSinglePeriodRPV01(
      final LocalDate today,
      final LocalDate accStartDate,
      final LocalDate accEndDate,
      final LocalDate paymentDate,
      final int obsOffset,
      final ISDACompliantDateYieldCurve yieldCurve,
      final ISDACompliantDateCreditCurve hazardRateCurve) {

    final double accTime = _accuralDayCount.getDayCountFraction(accStartDate, accEndDate);
    double t = _curveDayCount.getDayCountFraction(today, paymentDate);
    double tObsOffset = _curveDayCount.getDayCountFraction(today, accEndDate.plusDays(obsOffset));

    // TODO Do we need this?
    // Compensate Java shortcoming
    if (Double.compare(t, -0.0) == 0) {
      t = 0;
    }
    if (Double.compare(tObsOffset, -0.0) == 0) {
      tObsOffset = 0;
    }

    final double survival = hazardRateCurve.getSurvivalProbability(tObsOffset);
    final double discount = yieldCurve.getDiscountFactor(t);
    return new double[] {accTime * discount * survival, accTime};
  }
  /**
   * This is the present value of the premium leg per unit of fractional spread - hence it is equal
   * to 10,000 times the RPV01 (Risky PV01). The actual PV of the leg is this multiplied by the
   * notional and the fractional spread (i.e. spread in basis points divided by 10,000)
   *
   * <p>This mimics the ISDA c function <b>JpmcdsCdsFeeLegPV</b>
   *
   * @param today The 'current' date
   * @param stepinDate Date when party assumes ownership. This is normally today + 1 (T+1). Aka
   *     assignment date or effective date.
   * @param valueDate The valuation date. The date that values are PVed to. Is is normally today + 3
   *     business days. Aka cash-settle date.
   * @param startDate The protection start date. If protectStart = true, then protections starts at
   *     the beginning of the day, otherwise it is at the end.
   * @param endDate The protection end date (the protection ends at end of day)
   * @param payAccOnDefault Is the accrued premium paid in the event of a default
   * @param tenor The nominal step between premium payments (e.g. 3 months, 6 months).
   * @param stubType stubType Options are FRONTSHORT, FRONTLONG, BACKSHORT, BACKLONG or NONE -
   *     <b>Note</b> in this code NONE is not allowed
   * @param yieldCurve Curve from which payments are discounted
   * @param hazardRateCurve Curve giving survival probability
   * @param protectStart Does protection start at the beginning of the day
   * @param priceType Clean or Dirty price. The clean price removes the accrued premium if the trade
   *     is between payment times.
   * @return 10,000 times the RPV01 (on a notional of 1)
   */
  public double pvPremiumLegPerUnitSpread(
      final LocalDate today,
      final LocalDate stepinDate,
      final LocalDate valueDate,
      final LocalDate startDate,
      final LocalDate endDate,
      final boolean payAccOnDefault,
      final Period tenor,
      final StubType stubType,
      final ISDACompliantDateYieldCurve yieldCurve,
      final ISDACompliantDateCreditCurve hazardRateCurve,
      final boolean protectStart,
      final PriceType priceType) {
    ArgumentChecker.notNull(today, "null today");
    ArgumentChecker.notNull(stepinDate, "null stepinDate");
    ArgumentChecker.notNull(valueDate, "null valueDate");
    ArgumentChecker.notNull(startDate, "null startDate");
    ArgumentChecker.notNull(endDate, "null endDate");
    ArgumentChecker.notNull(tenor, "null tenor");
    ArgumentChecker.notNull(stubType, "null stubType");
    ArgumentChecker.notNull(yieldCurve, "null yieldCurve");
    ArgumentChecker.notNull(hazardRateCurve, "null hazardRateCurve");
    ArgumentChecker.notNull(priceType, "null priceType");
    ArgumentChecker.isFalse(valueDate.isBefore(today), "Require valueDate >= today");
    ArgumentChecker.isFalse(stepinDate.isBefore(today), "Require stepin >= today");

    final ISDAPremiumLegSchedule paymentSchedule =
        new ISDAPremiumLegSchedule(
            startDate,
            endDate,
            tenor,
            stubType,
            _businessdayAdjustmentConvention,
            _calandar,
            protectStart);
    final int nPayments = paymentSchedule.getNumPayments();

    // these are potentially different from startDate and endDate
    final LocalDate globalAccStart = paymentSchedule.getAccStartDate(0);
    final LocalDate golobalAccEnd = paymentSchedule.getAccEndDate(nPayments - 1);

    // TODO this logic could be part of ISDAPremiumLegSchdule
    final LocalDate matDate = protectStart ? golobalAccEnd.minusDays(1) : golobalAccEnd;

    if (today.isAfter(matDate) || stepinDate.isAfter(matDate)) {
      return 0.0; // trade has expired
    }

    final LocalDate[] yieldCurveDates = yieldCurve.getCurveDates();
    final LocalDate[] creditCurveDates = hazardRateCurve.getCurveDates();
    // This is common to the protection leg
    final LocalDate[] integrationSchedule =
        payAccOnDefault
            ? getIntegrationNodesAsDates(
                globalAccStart, golobalAccEnd, yieldCurveDates, creditCurveDates)
            : null;
    final int obsOffset = protectStart ? -1 : 0; // protection start at the beginning or end day

    double rpv01 = 0.0;
    for (int i = 0; i < nPayments; i++) {

      final LocalDate accStart = paymentSchedule.getAccStartDate(i);
      final LocalDate accEnd = paymentSchedule.getAccEndDate(i);
      final LocalDate pay = paymentSchedule.getPaymentDate(i);

      if (!accEnd.isAfter(stepinDate)) {
        continue; // this cashflow has already been realised
      }

      final double[] temp =
          calculateSinglePeriodRPV01(
              today, accStart, accEnd, pay, obsOffset, yieldCurve, hazardRateCurve);
      rpv01 += temp[0];

      if (payAccOnDefault) {
        final LocalDate offsetStepinDate = stepinDate.plusDays(obsOffset);
        final LocalDate offsetAccStartDate = accStart.plusDays(obsOffset);
        final LocalDate offsetAccEndDate = accEnd.plusDays(obsOffset);
        rpv01 +=
            calculateSinglePeriodAccrualOnDefault(
                today,
                offsetStepinDate,
                offsetAccStartDate,
                offsetAccEndDate,
                temp[1],
                yieldCurve,
                hazardRateCurve,
                integrationSchedule);
      }
    }

    // Compute the discount factor discounting the upfront payment made on the cash settlement date
    // back to the valuation date
    final double t = _curveDayCount.getDayCountFraction(today, valueDate);
    final double df = yieldCurve.getDiscountFactor(t);
    rpv01 /= df;

    // Do we want to calculate the clean price (includes the previously accrued portion of the
    // premium)
    if (priceType == PriceType.CLEAN) {
      rpv01 -= calculateAccruedInterest(paymentSchedule, stepinDate);
    }

    return rpv01;
  }
  /**
   * Get the value of the protection leg for unit notional
   *
   * <p>This mimics the ISDA c function <b>JpmcdsCdsContingentLegPV</b>
   *
   * @param today The 'current' date
   * @param stepinDate Date when party assumes ownership. This is normally today + 1 (T+1). Aka
   *     assignment date or effective date.
   * @param valueDate The valuation date. The date that values are PVed to. Is is normally today + 3
   *     business days. Aka cash-settle date.
   * @param startDate The protection start date. If protectStart = true, then protections starts at
   *     the beginning of the day, otherwise it is at the end.
   * @param endDate The protection end date (the protection ends at end of day)
   * @param yieldCurve Curve from which payments are discounted
   * @param hazardRateCurve Curve giving survival probability
   * @param recoveryRate The recovery rate of the protected debt
   * @param protectStart Does protection start at the beginning of the day
   * @return unit notional PV of protection (or contingent) leg
   */
  public double calculateProtectionLeg(
      final LocalDate today,
      final LocalDate stepinDate,
      final LocalDate valueDate,
      final LocalDate startDate,
      final LocalDate endDate,
      final ISDACompliantDateYieldCurve yieldCurve,
      final ISDACompliantDateCreditCurve hazardRateCurve,
      final double recoveryRate,
      final boolean protectStart) {
    ArgumentChecker.notNull(today, "null today");
    ArgumentChecker.notNull(valueDate, "null valueDate");
    ArgumentChecker.notNull(startDate, "null startDate");
    ArgumentChecker.notNull(endDate, "null endDate");
    ArgumentChecker.notNull(yieldCurve, "null yieldCurve");
    ArgumentChecker.notNull(hazardRateCurve, "null hazardRateCurve");
    ArgumentChecker.isInRangeInclusive(0, 1.0, recoveryRate);
    ArgumentChecker.isFalse(valueDate.isBefore(today), "Require valueDate >= today");
    ArgumentChecker.isFalse(stepinDate.isBefore(today), "Require stepin >= today");

    if (recoveryRate == 1.0) {
      return 0.0;
    }

    final LocalDate temp = stepinDate.isAfter(startDate) ? stepinDate : startDate;
    final LocalDate effectiveStartDate = protectStart ? temp.minusDays(1) : temp;

    if (!endDate.isAfter(effectiveStartDate)) {
      return 0.0; // the protection has expired
    }

    final LocalDate[] yieldCurveDates = yieldCurve.getCurveDates();
    final LocalDate[] creditCurveDates = hazardRateCurve.getCurveDates();
    final double[] integrationSchedule =
        ISDACompliantScheduleGenerator.getIntegrationNodesAsTimes(
            today, effectiveStartDate, endDate, yieldCurveDates, creditCurveDates);

    // double s1 = hazardRateCurve.getSurvivalProbability(integrationSchedule[0]);
    // double df1 = yieldCurve.getDiscountFactor(integrationSchedule[0]);

    double ht1 = hazardRateCurve.getRT(integrationSchedule[0]);
    double rt1 = yieldCurve.getRT(integrationSchedule[0]);
    double s1 = Math.exp(-ht1);
    double p1 = Math.exp(-rt1);
    double pv = 0.0;
    final int n = integrationSchedule.length;
    for (int i = 1; i < n; ++i) {

      final double ht0 = ht1;
      final double rt0 = rt1;
      final double p0 = p1;
      final double s0 = s1;

      ht1 = hazardRateCurve.getRT(integrationSchedule[i]);
      rt1 = yieldCurve.getRT(integrationSchedule[i]);
      s1 = Math.exp(-ht1);
      p1 = Math.exp(-rt1);
      final double dht = ht1 - ht0;
      final double drt = rt1 - rt0;
      final double dhrt = dht + drt;

      // this is equivalent to the ISDA code without explicitly calculating the time step - it also
      // handles the limit
      double dPV;
      if (Math.abs(dhrt) < 1e-5) {
        dPV = dht * (1 - dhrt * (0.5 - dhrt / 6)) * p0 * s0;
      } else {
        dPV = dht / dhrt * (p0 * s0 - p1 * s1);
      }

      // *************
      // ISDA code
      // **************
      // final double dt = integrationSchedule[i] - integrationSchedule[i - 1];
      // final double s0 = s1;
      // final double df0 = df1;
      // s1 = hazardRateCurve.getSurvivalProbability(integrationSchedule[i]);
      // df1 = yieldCurve.getDiscountFactor(integrationSchedule[i]);
      // final double hazardRate = Math.log(s0 / s1) / dt;
      // final double interestRate = Math.log(df0 / df1) / dt;
      // pv += (hazardRate / (hazardRate + interestRate)) * (1.0 - Math.exp(-(hazardRate +
      // interestRate) * dt)) * s0 * df0;

      pv += dPV;
    }
    pv *= 1.0 - recoveryRate;
    // System.out.println(pv);

    // Compute the discount factor discounting the upfront payment made on the cash settlement date
    // back to the valuation date
    final double t = _curveDayCount.getDayCountFraction(today, valueDate);
    final double df = yieldCurve.getDiscountFactor(t);
    pv /= df;

    return pv;
  }
  /**
   * this mimics the ISDA c JpmcdsAccrualOnDefaultPVWithTimeLine
   *
   * @param today
   * @param valueDate
   * @param offsetStepinDate
   * @param offsetAccStartDate
   * @param offsetAccEndDate
   * @param obsOffset
   * @param yieldCurve
   * @param hazardRateCurve
   * @param integrationSchedule
   * @return The single period accrual on default
   */
  private double calculateSinglePeriodAccrualOnDefault(
      final LocalDate today,
      final LocalDate offsetStepinDate,
      final LocalDate offsetAccStartDate,
      final LocalDate offsetAccEndDate,
      final double accTime,
      final ISDACompliantDateYieldCurve yieldCurve,
      final ISDACompliantDateCreditCurve hazardRateCurve,
      final LocalDate[] integrationSchedule) {

    final LocalDate[] truncatedDateList =
        truncateList(offsetAccStartDate, offsetAccEndDate, integrationSchedule);
    final int nItems = truncatedDateList.length;

    // max(offsetStepinDate,offsetAccStartDate)
    LocalDate subStartDate =
        offsetStepinDate.isAfter(offsetAccStartDate) ? offsetStepinDate : offsetAccStartDate;

    final double tAcc =
        ACT_365.getDayCountFraction(
            offsetAccStartDate, offsetAccEndDate); // This is hardcoded to ACT/365 in ISDA code
    final double accRate = accTime / tAcc;
    double t = ACT_365.getDayCountFraction(today, subStartDate);

    // Compensate Java shortcoming
    if (Double.compare(t, -0.0) == 0) {
      t = 0;
    }
    double s0 = hazardRateCurve.getSurvivalProbability(t);
    double df0 = yieldCurve.getDiscountFactor(t);

    double myPV = 0.0;
    for (int j = 1; j < nItems; ++j) {

      if (!truncatedDateList[j].isAfter(offsetStepinDate)) {
        continue;
      }

      double thisAccPV = 0.0;
      t = ACT_365.getDayCountFraction(today, truncatedDateList[j]);
      final double s1 = hazardRateCurve.getSurvivalProbability(t);
      final double df1 = yieldCurve.getDiscountFactor(t);

      final double t0 =
          ACT_365.getDayCountFraction(offsetAccStartDate, subStartDate)
              + 1 / 730.; // add on half a day
      final double t1 =
          ACT_365.getDayCountFraction(offsetAccStartDate, truncatedDateList[j]) + 1 / 730.;
      t = t1 - t0; // t repurposed

      // TODO check for s0 == s1 -> zero prob of default (and thus zero PV contribution) from this
      // section
      // if (s0 == s1) {
      // continue;
      // }

      // TODO this is a know bug that is fixed in ISDA v.1.8.2

      final double lambda = Math.log(s0 / s1) / t;
      final double fwdRate = Math.log(df0 / df1) / t;
      final double lambdafwdRate = lambda + fwdRate + 1.0e-50;

      thisAccPV =
          lambda
              * accRate
              * s0
              * df0
              * ((t0 + 1.0 / (lambdafwdRate)) / (lambdafwdRate)
                  - (t1 + 1.0 / (lambdafwdRate)) / (lambdafwdRate) * s1 / s0 * df1 / df0);
      myPV += thisAccPV;
      s0 = s1;
      df0 = df1;
      subStartDate = truncatedDateList[j];
    }
    return myPV;
  }