ChildDateOfBirthCalculator.java

package uk.gov.dhsc.htbhf.claimant.message.processor;

import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import uk.gov.dhsc.htbhf.claimant.entitlement.PaymentCycleEntitlementCalculator;
import uk.gov.dhsc.htbhf.claimant.entity.PaymentCycle;

import java.time.LocalDate;
import java.util.List;

import static org.springframework.util.CollectionUtils.isEmpty;
import static uk.gov.dhsc.htbhf.claimant.message.processor.NextPaymentCycleSummary.NO_CHILDREN;

/**
 * Contains utility methods for helping to figure out information pertaining to the dates of birth
 * for the Claimant.
 */
@Component
public class ChildDateOfBirthCalculator {

    private final PaymentCycleEntitlementCalculator paymentCycleEntitlementCalculator;

    public ChildDateOfBirthCalculator(PaymentCycleEntitlementCalculator paymentCycleEntitlementCalculator) {
        this.paymentCycleEntitlementCalculator = paymentCycleEntitlementCalculator;
    }

    /**
     * Calculates the number of children under one on a given date.
     * @param dateOfBirthOfChildren list of dates of birth to check against
     * @param atDate date at which to check if each child was one year old.
     * @return number of children under one at the give date.
     */
    public static Integer getNumberOfChildrenUnderOne(List<LocalDate> dateOfBirthOfChildren, LocalDate atDate) {
        return getNumberOfChildrenUnderAgeInYears(dateOfBirthOfChildren, atDate, 1);
    }

    /**
     * Calculates the number of children under four on a given date.
     * @param dateOfBirthOfChildren list of dates of birth to check against
     * @param atDate date at which to check if each child was four years old.
     * @return number of children under one at the give date.
     */
    public static Integer getNumberOfChildrenUnderFour(List<LocalDate> dateOfBirthOfChildren, LocalDate atDate) {
        return getNumberOfChildrenUnderAgeInYears(dateOfBirthOfChildren, atDate, 4);
    }

    /**
     * Calculates how many of the children's dates of birth listed on the given PaymentCycle have birthdays
     * which would affect the next payment after this cycle. The dates that would affect the next payment
     * are between the last entitlement date of the current cycle and the final entitlement date of the next
     * cycle. Those with birthdays on the start boundary date (the date of the final entitlement date of
     * the current cycle) are not included, but those on the end boundary date (the final entitlement date
     * of the next cycle) are included.
     *
     * @param paymentCycle The current PaymentCycle including the relevant dates of birth of the children.
     * @return The number of children who will soon turn 1 or 4 that affect the next Payment.
     */
    public NextPaymentCycleSummary getNextPaymentCycleSummary(PaymentCycle paymentCycle) {
        if (CollectionUtils.isEmpty(paymentCycle.getChildrenDob())) {
            return NO_CHILDREN;
        }
        LocalDate currentCycleStartDate = paymentCycle.getCycleStartDate();
        LocalDate nextCycleStartDate = paymentCycle.getCycleEndDate().plusDays(1);
        LocalDate lastEntitlementDateInCurrentCycle = getLatestEntitlementDateFromCycleStartDate(currentCycleStartDate);
        LocalDate lastEntitlementDateInNextCycle = getLatestEntitlementDateFromCycleStartDate(nextCycleStartDate);
        int childrenAgedOneAffectingNextPayment = countChildrenOfAge(paymentCycle, lastEntitlementDateInCurrentCycle, lastEntitlementDateInNextCycle, 1);
        int childrenAgedFourAffectingNextPayment = countChildrenOfAge(paymentCycle, lastEntitlementDateInCurrentCycle, lastEntitlementDateInNextCycle, 4);
        boolean childrenUnderFourAtEndOfCycle = hasChildrenUnderFourAtGivenDate(paymentCycle.getChildrenDob(), lastEntitlementDateInNextCycle);
        return NextPaymentCycleSummary.builder()
                .numberOfChildrenTurningOne(childrenAgedOneAffectingNextPayment)
                .numberOfChildrenTurningFour(childrenAgedFourAffectingNextPayment)
                .childrenUnderFourPresentAtEndOfCycle(childrenUnderFourAtEndOfCycle)
                .build();
    }

    /**
     * Calculates whether there were any children under 4 at all at the start of the given PaymentCycle.
     *
     * @param paymentCycle The payment cycle to check
     * @return true if any children were under 4 at the start of the given PaymentCycle.
     */
    public boolean hadChildrenUnder4AtStartOfPaymentCycle(PaymentCycle paymentCycle) {
        return hasChildrenUnderFourAtGivenDate(paymentCycle.getChildrenDob(), paymentCycle.getCycleStartDate());
    }

    /**
     * Calculates whether or not there are any children under 4 at the given date.
     * @param childrenDob the list of children's dates of birth to check
     * @param atDate the date to compare the children's date of birth against
     * @return true if any children were under four at the given date
     */
    public boolean hasChildrenUnderFourAtGivenDate(List<LocalDate> childrenDob, LocalDate atDate) {
        if (CollectionUtils.isEmpty(childrenDob)) {
            return false;
        }
        return childrenDob.stream()
                .anyMatch(childDob -> childDob.isAfter(atDate.minusYears(4)));
    }

    private static Integer getNumberOfChildrenUnderAgeInYears(List<LocalDate> dateOfBirthOfChildren, LocalDate atDate, Integer ageInYears) {
        if (isEmpty(dateOfBirthOfChildren)) {
            return 0;
        }
        LocalDate pastDate = atDate.minusYears(ageInYears);
        return Math.toIntExact(dateOfBirthOfChildren.stream()
                .filter(date -> date.isAfter(pastDate) && !date.isAfter(atDate))
                .count());
    }

    private int countChildrenOfAge(PaymentCycle paymentCycle, LocalDate lastEntitlementDateInCurrentCycle,
                                   LocalDate lastEntitlementDateInNextCycle, int age) {
        return Math.toIntExact(paymentCycle.getChildrenDob().stream()
                .filter(childDob -> isWithinPeriodExcludingStartDateIncludingEndDate(
                        lastEntitlementDateInCurrentCycle,
                        lastEntitlementDateInNextCycle,
                        age,
                        childDob))
                .count());
    }

    private LocalDate getLatestEntitlementDateFromCycleStartDate(LocalDate cycleStartDate) {
        return paymentCycleEntitlementCalculator.getVoucherEntitlementDatesFromStartDate(cycleStartDate)
                .stream().max(LocalDate::compareTo).get();
    }

    private boolean isWithinPeriodExcludingStartDateIncludingEndDate(LocalDate lastEntitlementDateInCurrentCycle,
                                                                     LocalDate lastEntitlementDateInNextCycle,
                                                                     int age,
                                                                     LocalDate childDob) {
        return childDob.isAfter(lastEntitlementDateInCurrentCycle.minusYears(age))
                && childDob.isBefore(lastEntitlementDateInNextCycle.minusYears(age).plusDays(1));
    }

}