using System; using System.Text.RegularExpressions; using MfGames.Gallium; using MfGames.Nitride.Generators; using NodaTime; using Zio; namespace MfGames.Nitride.Temporal.Schedules; /// /// A schedule that uses the `UPath` of the entity to determine an offset from /// a starting point and calculates the information from there. /// [WithProperties] public partial class NumericalPathSchedule : ISchedule { public NumericalPathSchedule() { this.PathRegex = new Regex(@"^.*(\d+)"); this.GetPath = (entity) => entity.Get().ToString(); this.CaptureGroup = 1; this.CaptureOffset = -1; } /// /// Gets or sets the group number of the capture group with 1 being being the /// first group in PathRegex. /// public int CaptureGroup { get; set; } /// /// Gets or sets the offset to make the resulting capture group a zero-based /// number. /// public int CaptureOffset { get; set; } /// /// Gets or sets the method for retrieving the path for the entity. /// public Func GetPath { get; set; } /// /// Gets or sets the regular expression to identify a matching path. /// public Regex? PathRegex { get; set; } /// /// Gets or sets the period between each matching entity. More precisely, /// the schedule will be TimeSpan * (CaptureGroup + CaptureOffset). /// public SchedulePeriod SchedulePeriod { get; set; } /// /// Gets or sets when the first item is scheduled. /// public DateTime? ScheduleStart { get; set; } /// public virtual Entity Apply( Entity entity, Timekeeper timekeeper) { // Get the path and match it. string? path = this.GetPath(entity); Match? match = this.PathRegex?.Match(path) ?? throw new NullReferenceException( "PathRegex was not configured for the " + this.GetType().Name + "."); if (!match.Success) { return entity; } // Figure out the index/number of this entry. int number = int.Parse(match.Groups[this.CaptureGroup].Value) + this.CaptureOffset; // Figure out the time from the start. TimeSpan span = this.SchedulePeriod switch { SchedulePeriod.Instant => TimeSpan.Zero, SchedulePeriod.Day => TimeSpan.FromDays(1), SchedulePeriod.Week => TimeSpan.FromDays(7), _ => throw new InvalidOperationException( "Cannot parse schedule period from " + this.SchedulePeriod + "."), }; DateTime start = this.ScheduleStart ?? throw new NullReferenceException( "Cannot use a schedule without a start date."); DateTime when = start + span * number; Instant instant = timekeeper.CreateInstant(when); // If the time hasn't past, then we don't apply it. Instant now = timekeeper.Clock.GetCurrentInstant(); return instant > now ? entity : this.Apply(entity, number, instant); } /// public virtual bool CanApply(Entity entity) { string? path = this.GetPath(entity); bool match = this.PathRegex?.IsMatch(path) ?? false; return match; } protected virtual Entity Apply( Entity entity, int number, Instant instant) { return entity.Set(instant); } }