Attainly/backend/app/Schedule/EvenDistributionScheduler.php

138 lines
3.8 KiB
PHP

<?php
namespace App\Schedule;
use DateTimeImmutable;
class EvenDistributionScheduler
{
/**
* @return list<DateTimeImmutable>
*/
public function scheduledDates(
int $assignmentCount,
DateTimeImmutable $startDate,
DateTimeImmutable $targetDate,
WorkloadPlacement $workloadPlacement,
): array {
$differenceInDays = $startDate->diff($targetDate)->days;
$dayCount = $differenceInDays + 1;
return $this->distributedDates(
assignmentCount: $assignmentCount,
dayCount: $dayCount,
startDate: $startDate,
workloadPlacement: $workloadPlacement,
);
}
/**
* @return list<DateTimeImmutable>
*/
private function distributedDates(
int $assignmentCount,
int $dayCount,
DateTimeImmutable $startDate,
WorkloadPlacement $workloadPlacement,
): array {
$assignmentsPerDay = intdiv($assignmentCount, $dayCount);
$remainderDayCount = $assignmentCount % $dayCount;
$remainderDayIndexes = $this->remainderDayIndexes(
assignmentsPerDay: $assignmentsPerDay,
dayCount: $dayCount,
remainderDayCount: $remainderDayCount,
workloadPlacement: $workloadPlacement,
);
$remainderDays = array_fill_keys($remainderDayIndexes, true);
$dates = [];
for ($dayIndex = 0; $dayIndex < $dayCount; $dayIndex++) {
$assignmentCountForDay = $assignmentsPerDay;
if (isset($remainderDays[$dayIndex])) {
$assignmentCountForDay++;
}
$date = $startDate->modify("+{$dayIndex} days");
for ($index = 0; $index < $assignmentCountForDay; $index++) {
$dates[] = $date;
}
}
return $dates;
}
/**
* @return list<int>
*/
private function remainderDayIndexes(
int $assignmentsPerDay,
int $dayCount,
int $remainderDayCount,
WorkloadPlacement $workloadPlacement,
): array {
if ($remainderDayCount === 0) {
return [];
}
if ($workloadPlacement === WorkloadPlacement::Spread) {
return $this->spreadRemainderDayIndexes(
assignmentsPerDay: $assignmentsPerDay,
dayCount: $dayCount,
remainderDayCount: $remainderDayCount,
);
}
$blockStart = $this->remainderBlockStart(
dayCount: $dayCount,
remainderDayCount: $remainderDayCount,
workloadPlacement: $workloadPlacement,
);
return range($blockStart, $blockStart + $remainderDayCount - 1);
}
/**
* @return list<int>
*/
private function spreadRemainderDayIndexes(
int $assignmentsPerDay,
int $dayCount,
int $remainderDayCount,
): array {
if ($remainderDayCount === 1) {
if ($assignmentsPerDay === 0) {
return [0];
}
return [intdiv($dayCount - 1, 2)];
}
$dayIndexes = [];
for ($remainderIndex = 0;
$remainderIndex < $remainderDayCount;
$remainderIndex++
) {
$scaledIndex = $remainderIndex * ($dayCount - 1)
/ ($remainderDayCount - 1);
$dayIndexes[] = (int) floor($scaledIndex + 0.5);
}
return $dayIndexes;
}
private function remainderBlockStart(
int $dayCount,
int $remainderDayCount,
WorkloadPlacement $workloadPlacement,
): int {
if ($workloadPlacement === WorkloadPlacement::Start) {
return 0;
}
if ($workloadPlacement === WorkloadPlacement::End) {
return $dayCount - $remainderDayCount;
}
return intdiv($dayCount - $remainderDayCount, 2);
}
}