Files
ersatztv/ErsatzTV.Application/ProgramSchedules/ScheduleItemDurationEstimator.cs
T

74 lines
2.8 KiB
C#

using System.Globalization;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.ProgramSchedules;
/// <summary>
/// Pure computation of per-item runtime estimates from pre-aggregated collection durations.
/// Kept separate from the query handler so the (non-trivial) per-mode math can be unit-tested
/// without a database. See <see cref="ProgramScheduleItemViewModel.DurationEstimate" /> for the
/// documented semantics this implements.
/// </summary>
internal static class ScheduleItemDurationEstimator
{
/// <summary>
/// Aggregate runtime of a single referenced collection: the total runtime of every item with a
/// known non-zero duration, and how many such items there are.
/// </summary>
public sealed record CollectionDuration(TimeSpan Total, int ItemCount)
{
public TimeSpan? Average => ItemCount > 0 ? Total / ItemCount : null;
}
/// <summary>
/// Estimate the runtime of one pass of <paramref name="item" />, or <c>null</c> when no bounded
/// estimate is possible. <paramref name="durationsByCollectionId" /> holds aggregates only for the
/// plain collections that were resolved; a missing entry yields <c>null</c>.
/// </summary>
public static TimeSpan? Estimate(
ProgramScheduleItemViewModel item,
IReadOnlyDictionary<int, CollectionDuration> durationsByCollectionId)
{
if (item is ProgramScheduleItemDurationViewModel durationItem)
{
return durationItem.PlayoutDuration;
}
// only plain collections are aggregated in this pass
if (item.CollectionType is not CollectionType.Collection || item.Collection is null)
{
return null;
}
if (!durationsByCollectionId.TryGetValue(item.Collection.Id, out CollectionDuration duration))
{
return null;
}
return item switch
{
// one item per pass -> the average item runtime
ProgramScheduleItemOneViewModel => duration.Average,
// a fixed count of items, or the whole collection once
ProgramScheduleItemMultipleViewModel multiple => EstimateMultiple(multiple, duration),
// Flood is an unbounded fill; Duration is handled above from its explicit playoutDuration.
_ => null
};
}
private static TimeSpan? EstimateMultiple(
ProgramScheduleItemMultipleViewModel multiple,
CollectionDuration duration) =>
multiple.MultipleMode switch
{
MultipleMode.Count when
int.TryParse(multiple.Count, NumberStyles.Integer, CultureInfo.InvariantCulture, out int count)
&& count > 0
&& duration.Average is { } average => average * count,
MultipleMode.CollectionSize => duration.Total,
_ => null
};
}