use std::collections::VecDeque; use super::song::{Plan, Song, Verse}; pub struct PlaylistEntry { pub song: Song, pub plan_name: Option, } pub struct PlaylistVerseRef { pub song_index: usize, pub map_verse_index: usize, } pub struct Display { pub playlist: VecDeque, current: PlaylistVerseRef, frozen_at: Option, pub blanked: bool, } impl Display { pub fn verse_ref(&self) -> &PlaylistVerseRef { self.frozen_at.as_ref().unwrap_or(&self.current) } pub fn verse(&self) -> Option<&Verse> { if self.blanked { None } else { let verse_ref = self.verse_ref(); let entry = self.entry(verse_ref)?; entry.song.verses.get(self.verse_name(verse_ref)?) } } pub fn entry(&self, verse_ref: &PlaylistVerseRef) -> Option<&PlaylistEntry> { self.playlist.get(verse_ref.song_index) } pub fn plan(&self, verse_ref: &PlaylistVerseRef) -> Option<&Plan> { let PlaylistEntry { song, plan_name } = self.entry(verse_ref)?; // TODO: this could "fail silently" and use the default plan Some(song.plan(plan_name.as_deref())) } pub fn verse_name(&self, verse_ref: &PlaylistVerseRef) -> Option<&String> { self.plan(verse_ref)?.get(verse_ref.map_verse_index) } pub fn clamp(&mut self) { // ensure all plan names exist or fallback to defaults for p in self.playlist.iter_mut() { if let Some(plan_name) = p.plan_name.as_mut() { if !p.song.other_plans.contains_key(plan_name) { p.plan_name = None; } } } // ensure song index is within bounds or be 0 self.current.song_index = self.current.song_index.clamp(0, self.playlist.len()); if let Some(frozen_at) = self.frozen_at.as_mut() { frozen_at.song_index = frozen_at.song_index.clamp(0, self.playlist.len()); } // ensure map verse index is within bounds or be 0 if let Some(plan) = self.plan(&self.current) { self.current.map_verse_index = self.current.map_verse_index.clamp(0, plan.len()); } let new_frozen_map_verse_index = match &self.frozen_at { Some(frozen_at) => { if let Some(plan) = self.plan(&frozen_at) { frozen_at.map_verse_index.clamp(0, plan.len()) } else { 0 } } None => 0, }; if let Some(frozen_at) = self.frozen_at.as_mut() { frozen_at.map_verse_index = new_frozen_map_verse_index } } } mod test { use super::*; fn default(playlist: VecDeque) -> Display { let current = PlaylistVerseRef { song_index: 0, map_verse_index: 0, }; Display { playlist, current, frozen_at: None, blanked: false, } } fn two_song_playlist() -> VecDeque { VecDeque::from([ PlaylistEntry { song: "Song1\n\ns1verse1\n\ns1verse2".parse().unwrap(), plan_name: None, }, PlaylistEntry { song: "Song2\n\ns2verse1\n\ns2verse2".parse().unwrap(), plan_name: None, }, ]) } #[test] fn displays_no_verse_when_empty() { let playlist: VecDeque = VecDeque::from(vec![]); let display = default(playlist); assert_eq!(display.verse(), None) } #[test] fn displays_the_first_verse_when_a_song_is_there() { let display = default(two_song_playlist()); assert_eq!(display.verse().unwrap().content, "s1verse1") } }