using ErsatzTV.Core.Domain; using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Interfaces.Scheduling; namespace ErsatzTV.Core.Scheduling; public class RandomizedRotatingMediaCollectionEnumerator : IMediaCollectionEnumerator { private readonly Lazy> _lazyMinimumDuration; private readonly IList _mediaItems; private readonly Random _random; private readonly Dictionary> _groupMedia; private int _index; private int _groupNumber; public RandomizedRotatingMediaCollectionEnumerator(IList mediaItems, CollectionEnumeratorState state) { CurrentIncludeInProgramGuide = Option.None; _mediaItems = mediaItems; _lazyMinimumDuration = new Lazy>( () => _mediaItems.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); _random = new Random(state.Seed); _groupMedia = new Dictionary>(); for (int i = 0; i < mediaItems.Count; i++) { int id = mediaItems[i] switch { Episode e => e.Season.ShowId, MusicVideo mv => mv.ArtistId, _ => mediaItems[i].Id }; if (_groupMedia.TryGetValue(id, out IList newList)) { newList.Add(i); } else { _groupMedia.Add(id, new List { i }); } } _groupNumber = 0; State = new CollectionEnumeratorState { Seed = state.Seed }; // we want to move at least once so we start with a random item and not the first // because _index defaults to 0 while (State.Index <= state.Index) { MoveNext(); } } public void ResetState(CollectionEnumeratorState state) => // seed never changes here, no need to reset State.Index = state.Index; public CollectionEnumeratorState State { get; } public Option Current => _mediaItems.Any() ? _mediaItems[_index] : None; public Option CurrentIncludeInProgramGuide { get; } public void MoveNext() { IList groups = _groupMedia.Keys.ToList(); int nextRandom = _random.Next(); int groupNumber = nextRandom % groups.Count; if (_groupNumber == groupNumber) { if (groupNumber == groups.Count - 1) { _groupNumber = 0; } else { _groupNumber = groupNumber + 1; } } else { _groupNumber = groupNumber; } int itemNumber = nextRandom % _groupMedia[groups[_groupNumber]].Count; _index = _groupMedia[groups[_groupNumber]][itemNumber]; State.Index++; } public Option MinimumDuration => _lazyMinimumDuration.Value; public int Count => _mediaItems.Count; }