using System; using System.Text.RegularExpressions; using MfGames.Gallium; using MfGames.Nitride.Generators; using NodaTime; using TimeSpanParserUtil; 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 = @"^.*(\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 string? PathRegex { get; set; } /// /// Gets or sets the period between each matching entity. The period is /// amount of time between each item based on the number. So the first will /// be on the ScheduleDate, the second SchedulePeriod later, the third /// SchedulePeriod after the second, etc. More precisely, the date for /// any item TimeSpan * ((int)CaptureGroup + (int)CaptureOffset). /// public string? SchedulePeriod { get; set; } /// /// Gets or sets the schedule period as a TimeSpan object. This is converted /// from SchedulePeriod using https://github.com/pengowray/TimeSpanParser. /// If the value is null, then this will be instant (TimeSpan.Zero). /// public virtual TimeSpan SchedulePeriodTimeSpan { get => this.SchedulePeriod == null ? TimeSpan.Zero : TimeSpanParser.Parse(this.SchedulePeriod); set => this.SchedulePeriod = value.ToString(); } /// /// 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); Regex? regex = this.GetRegex(); Match? match = regex?.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. DateTime start = this.ScheduleStart ?? throw new NullReferenceException( "Cannot use a schedule without a start date."); DateTime when = start + this.SchedulePeriodTimeSpan * 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); Regex? regex = this.GetRegex(); bool match = regex?.IsMatch(path) ?? false; return match; } public Regex? GetRegex() { return this.PathRegex == null ? null : new Regex(this.PathRegex); } protected virtual Entity Apply( Entity entity, int number, Instant instant) { return entity.Set(instant); } }