// Copyright © 2015 HSL <https://www.hsl.fi> // This program is dual-licensed under the EUPL v1.2 and AGPLv3 licenses. package fi.hsl.parkandride.core.service; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import fi.hsl.parkandride.core.back.FacilityHistoryRepository; import fi.hsl.parkandride.core.back.FacilityRepository; import fi.hsl.parkandride.core.domain.*; import org.joda.time.Duration; import org.joda.time.Interval; import org.joda.time.LocalDate; import org.joda.time.LocalTime; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.BinaryOperator; import static fi.hsl.parkandride.util.Iterators.iterateFor; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.stream.Collectors.reducing; public class FacilityHistoryService { private static final LocalTime WINDOW_START = new LocalTime("06:00"); private static final LocalTime WINDOW_END = new LocalTime("10:00"); private static final FacilityStatusHistory IDENTITY_STATUS = new FacilityStatusHistory(null, null, null, FacilityStatus.IN_OPERATION, null); private final FacilityHistoryRepository facilityHistoryRepository; private final FacilityRepository facilityRepository; public FacilityHistoryService(FacilityHistoryRepository facilityHistoryRepository, FacilityRepository facilityRepository) { this.facilityHistoryRepository = facilityHistoryRepository; this.facilityRepository = facilityRepository; } /** * Deduces the status history for the given date range. * If the status changes during the day, the status that was mostly active * during the period of 6 to 10 a.m. is selected. */ @TransactionalRead public Map<LocalDate, FacilityStatus> getStatusHistoryByDay(final long facilityId, final LocalDate start, final LocalDate end) { final List<FacilityStatusHistory> statusHistory = facilityHistoryRepository.getStatusHistory(facilityId, start, end); return Maps.toMap(dateRangeClosed(start, end), date -> findEntryForDate(statusHistory, date, IDENTITY_STATUS).status); } /** * Deduces the unavailable capacities history for the given date range. * If the unavailable capacities change during the day, the capacity that was * mostly active during the the period of 6 to 10 am. is selected. */ @TransactionalRead public Map<LocalDate, FacilityCapacity> getCapacityHistory(final long facilityId, final LocalDate start, final LocalDate end) { final List<FacilityCapacityHistory> capacityHistory = facilityHistoryRepository.getCapacityHistory(facilityId, start, end); // Fall back to current unavailable capacities final Facility facility = facilityRepository.getFacility(facilityId); final FacilityCapacityHistory identity = new FacilityCapacityHistory( null, null, null, Optional.ofNullable(facility.builtCapacity).orElse(emptyMap()), Optional.ofNullable(facility.unavailableCapacities).orElse(emptyList()) ); return Maps.toMap(dateRangeClosed(start, end), date -> new FacilityCapacity(findEntryForDate(capacityHistory, date, identity))); } private static <T extends HasInterval> T findEntryForDate(List<T> entries, LocalDate date, T identity) { final Interval significantWindow = windowForDate(date); return entries.stream() .filter(e -> e.getInterval().overlaps(date.toInterval())) .collect(reducing(identity, greatestOverlap(significantWindow))); } private static <T extends HasInterval> BinaryOperator<T> greatestOverlap(Interval significantWindow) { return (h1, h2) -> { final Duration h1overlap = h1.overlapWith(significantWindow); final Duration h2overlap = h2.overlapWith(significantWindow); return h1overlap.compareTo(h2overlap) >= 0 ? h1 : h2; }; } private static Interval windowForDate(LocalDate date) { return new Interval( date.toDateTime(WINDOW_START), date.toDateTime(WINDOW_END) ); } private static List<LocalDate> dateRangeClosed(LocalDate start, LocalDate end) { return ImmutableList.copyOf(iterateFor(start, d -> !d.isAfter(end), d -> d.plusDays(1))); } }