@Test
  public void testGetPaymentsAmortizedWithOverPayment() {

    AmortizationAttributes amAttrs = generateAmortizationAttributesObjectTemplate();
    amAttrs.setLoanAmount(ofUSD(10000));
    amAttrs.setInterestRateAsPercent(3.);
    amAttrs.setInterestOnly(false);
    amAttrs.setRegularPayment(ofUSD(400));
    amAttrs.setPaymentFrequency(TimePeriod.SemiMonthly.getPeriodsPerYear());
    int termInMonths = 24;
    amAttrs.setTermInMonths(termInMonths);
    amAttrs.setAmortizationPeriodInMonths(24);
    amAttrs.setCompoundingPeriodsPerYear(2);

    List<ScheduledPayment> schedule = AmortizationCalculator.generateSchedule(amAttrs);

    assertEquals(
        "Amortized payment count",
        amAttrs.getPaymentFrequency() * termInMonths / 12,
        schedule.size());

    MonetaryAmount interestTotal =
        schedule.stream().map(payment -> payment.getInterest()).reduce((a, b) -> a.add(b)).get();

    assertEquals("Amortized Interest total", ofUSD(164.81), interestTotal);
  }
  @Test
  public void testGetPaymentsAmortizedFully() {

    AmortizationAttributes amAttrs = generateAmortizationAttributesObjectTemplate();
    MonetaryAmount loanAmount = ofUSD(10000);
    amAttrs.setLoanAmount(loanAmount);
    amAttrs.setInterestRateAsPercent(7.);
    amAttrs.setInterestOnly(false);
    amAttrs.setPaymentFrequency(TimePeriod.Monthly.getPeriodsPerYear());
    int termInMonths = 12;
    amAttrs.setTermInMonths(termInMonths);
    amAttrs.setAmortizationPeriodInMonths(12);
    amAttrs.setCompoundingPeriodsPerYear(2);
    amAttrs.setRegularPayment(AmortizationCalculator.getPeriodicPayment(amAttrs));

    List<ScheduledPayment> schedule = AmortizationCalculator.generateSchedule(amAttrs);

    MonetaryAmount interestTotal =
        schedule.stream().map(payment -> payment.getInterest()).reduce((a, b) -> a.add(b)).get();

    assertEquals("Amortized Interest total", ofUSD(377.67), interestTotal);

    MonetaryAmount principalTotal =
        schedule.stream().map(payment -> payment.getPrincipal()).reduce((a, b) -> a.add(b)).get();

    assertEquals("Amortized Principal fully paid", loanAmount, principalTotal);
  }
  @Test
  public void testGenerateScheduleInterestOnlyArrayBounds() {
    AmortizationAttributes amAttrs = generateAmortizationAttributesObjectTemplate();
    amAttrs.setLoanAmount(ofUSD(10000));
    amAttrs.setInterestRateAsPercent(12.);
    amAttrs.setInterestOnly(true);
    amAttrs.setPaymentFrequency(TimePeriod.Weekly.getPeriodsPerYear());
    int termInMonths = 24;
    amAttrs.setTermInMonths(termInMonths);
    amAttrs.setAdjustmentDate(LocalDate.of(2016, Month.JANUARY, 1));

    List<ScheduledPayment> schedule = AmortizationCalculator.generateSchedule(amAttrs);

    try {
      schedule.get(-1);
      fail("Interest only schedule should not allow negative payment number");
    } catch (IndexOutOfBoundsException iobe) {
    }

    int expectedPayments =
        (int) Math.ceil(amAttrs.getPaymentFrequency() * amAttrs.getTermInMonths() / 12.);

    try {
      schedule.get(expectedPayments);
      fail("Interest only schedule should not allow payments beyond schedule bound");
    } catch (IndexOutOfBoundsException iobe) {
    }

    schedule.get(expectedPayments - 1);
  }
  @Test
  public void testGenerateScheduleInterestOnlyMonthly() {
    AmortizationAttributes amAttrs = generateAmortizationAttributesObjectTemplate();
    amAttrs.setLoanAmount(ofUSD(10000));
    amAttrs.setInterestRateAsPercent(10.);
    amAttrs.setInterestOnly(true);
    amAttrs.setPaymentFrequency(TimePeriod.Monthly.getPeriodsPerYear());
    int termInMonths = 12;
    amAttrs.setTermInMonths(termInMonths);
    amAttrs.setAdjustmentDate(LocalDate.of(2016, Month.JANUARY, 1));
    amAttrs.setRegularPayment(ofUSD(0));

    List<ScheduledPayment> schedule = AmortizationCalculator.generateSchedule(amAttrs);
    assertEquals("Interest only schedule term in months", termInMonths, schedule.size());

    MonetaryAmount expectedInterest = ofUSD(83.34);
    MonetaryAmount expectedPrincipal = ofUSD(0);
    LocalDate expectedDate = amAttrs.getAdjustmentDate();
    int index = 0;

    for (ScheduledPayment payment : schedule) {
      expectedDate = expectedDate.plusMonths(1L);
      assertEquals("Interest only schedule payment number", ++index, payment.getPaymentNumber());
      assertEquals("Interest only schedule payment date", expectedDate, payment.getPaymentDate());
      assertEquals(
          "Interest only schedule payment interest", expectedInterest, payment.getInterest());
      assertEquals(
          "Interest only schedule payment principal", expectedPrincipal, payment.getPrincipal());
      assertEquals(
          "Interest only schedule payment total payment", expectedInterest, payment.getPayment());
      assertEquals(
          "Interest only schedule payment loan principal", ofUSD(10000), payment.getBalance());
    }
  }
  @Test
  public void testPaymentDateWeekly() {
    LocalDate scheduleStartDate = LocalDate.of(2015, Month.DECEMBER, 2);
    AmortizationAttributes amAttrs = generateAmortizationAttributesObjectTemplate();
    amAttrs.setAdjustmentDate(scheduleStartDate);
    amAttrs.setPaymentFrequency(TimePeriod.Weekly.getPeriodsPerYear());
    int termInMonths = 24;
    amAttrs.setTermInMonths(termInMonths);

    List<ScheduledPayment> schedule = AmortizationCalculator.generateSchedule(amAttrs);

    for (int i = 1; i <= (52 * 2); i++) {
      String msg = String.format("Date for weekly payment %d", i);
      assertEquals(msg, scheduleStartDate.plusWeeks(i), schedule.get(i - 1).getPaymentDate());
    }
  }
  private void testPaymentDatesWithMonthlyIntervals(int periodsPerYear) {
    LocalDate scheduleStartDate = LocalDate.of(2015, Month.DECEMBER, 2);
    AmortizationAttributes amAttrs = generateAmortizationAttributesObjectTemplate();
    amAttrs.setAdjustmentDate(scheduleStartDate);
    amAttrs.setPaymentFrequency(periodsPerYear);
    int termInMonths = 24;
    amAttrs.setTermInMonths(termInMonths);

    List<ScheduledPayment> schedule = AmortizationCalculator.generateSchedule(amAttrs);

    for (int i = 1; i <= termInMonths / (12 / periodsPerYear); i++) {
      String msg = String.format("Date for payment %d when %d payments a year", i, periodsPerYear);
      assertEquals(
          msg,
          scheduleStartDate.plusMonths(i * (12 / periodsPerYear)),
          schedule.get(i - 1).getPaymentDate());
    }
  }
  @Test
  public void testGetPeriodInterest_Amortized() {

    AmortizationAttributes amAttrs = generateAmortizationAttributesObjectTemplate();
    amAttrs.setLoanAmount(USD50000.multiply(2));
    amAttrs.setInterestRateAsPercent(12.);
    amAttrs.setCompoundingPeriodsPerYear(2);
    amAttrs.setPaymentFrequency(12);

    List<ScheduledPayment> generatedSchedule = AmortizationCalculator.generateSchedule(amAttrs);
    generatedSchedule
        .stream()
        .forEachOrdered(
            p -> {
              MonetaryAmount periodInterest = AmortizationCalculator.getPeriodInterest(amAttrs);
              assertEquals(
                  "Monthly amortized payment interest matches peirod interest",
                  p.getInterest(),
                  periodInterest);
              amAttrs.setLoanAmount(p.getBalance());
            });
  }
  @Test
  public void testPaymentDateSemiMonthly() {
    LocalDate scheduleStartDate = LocalDate.of(2015, Month.DECEMBER, 2);
    AmortizationAttributes amAttrs = generateAmortizationAttributesObjectTemplate();
    amAttrs.setAdjustmentDate(scheduleStartDate);
    amAttrs.setPaymentFrequency(TimePeriod.SemiMonthly.getPeriodsPerYear());
    int termInMonths = 24;
    amAttrs.setTermInMonths(termInMonths);

    int dayOfMonthPayment1 = scheduleStartDate.getDayOfMonth();
    int dayOfMonthPayment2 = scheduleStartDate.plusDays(14).getDayOfMonth();

    List<ScheduledPayment> schedule = AmortizationCalculator.generateSchedule(amAttrs);

    // First payment of month
    for (int i = 1; i <= 48; i += 2) {
      String msg = String.format("First date for semi-monthly payment %d", i);
      assertEquals(
          msg,
          scheduleStartDate.plusMonths(i / 2).plusDays(14),
          schedule.get(i - 1).getPaymentDate());
      assertEquals(
          "Semi-monthly common day of month",
          dayOfMonthPayment2,
          schedule.get(i - 1).getPaymentDate().getDayOfMonth());
    }

    // Second payment of month
    for (int i = 2; i <= 48; i += 2) {
      String msg = String.format("Second date for semi-monthly payment %d", i);
      assertEquals(msg, scheduleStartDate.plusMonths(i / 2), schedule.get(i - 1).getPaymentDate());
      assertEquals(
          "Semi-monthly common day of month",
          dayOfMonthPayment1,
          schedule.get(i - 1).getPaymentDate().getDayOfMonth());
    }
  }