use std::{collections::HashMap, hash::Hash, sync::Arc};
use bellframe::{Block, Row, RowBuf};
use crate::{
builder::{CallDisplayStyle, MethodId, MusicTypeId},
group::PartHead,
query::{CallIdx, MethodIdx, MethodVec, Query},
utils::{
counts::Counts,
lengths::{PerPartLength, TotalLength},
Score,
},
};
#[allow(unused_imports)] use crate::{
builder::{Method, MusicType, SearchBuilder},
Search,
};
#[derive(Debug, Clone)]
pub struct Composition {
pub(crate) path: Vec<PathElem>,
pub(crate) length: TotalLength,
pub(crate) part_head: PartHead,
pub(crate) total_score: Score,
pub(crate) method_counts: Counts,
pub(crate) music_counts: HashMap<MusicTypeId, usize>,
pub(crate) contiguous_duffer_lengths: Vec<PerPartLength>,
pub(crate) total_duffer: TotalLength,
pub(crate) query: Arc<Query>,
}
impl Composition {
pub fn length(&self) -> usize {
self.length.as_usize()
}
pub fn call_string(&self) -> String {
let needs_brackets = self.query.is_spliced()
|| self.query.call_display_style == CallDisplayStyle::Positional;
let is_snap_start = self.path[0].start_sub_lead_idx > 0;
let is_snap_finish = self.path.last().unwrap().end_sub_lead_idx(&self.query) > 0;
let part_head = self.part_head();
let mut path_iter = self.path.iter().peekable();
let mut s = String::new();
if self.query.call_display_style == CallDisplayStyle::Positional {
s.push('#');
}
s.push_str(if is_snap_start { "<" } else { "" });
while let Some(path_elem) = path_iter.next() {
if self.query.is_spliced()
|| self.query.call_display_style == CallDisplayStyle::Positional
{
let method = &self.query.methods[path_elem.method];
let num_leads_covered = num_leads_covered(
method.lead_len(),
path_elem.start_sub_lead_idx,
path_elem.length,
);
for _ in 0..num_leads_covered {
s.push_str(&method.shorthand);
}
}
if let Some(call_idx) = path_elem.call {
let call = &self.query.calls[call_idx];
s.push_str(if needs_brackets { "[" } else { "" });
match self.query.call_display_style {
CallDisplayStyle::CallingPositions(calling_bell) => {
let row_after_call = path_iter
.peek()
.map_or(part_head, |path_elem| &path_elem.start_row);
let place_of_calling_bell = row_after_call.place_of(calling_bell).unwrap();
let calling_position = &call.calling_positions[place_of_calling_bell];
s.push_str(call.short_symbol());
s.push_str(calling_position);
}
CallDisplayStyle::Positional => s.push_str(&call.symbol),
}
s.push_str(if needs_brackets { "]" } else { "" });
}
}
s.push_str(if is_snap_finish { ">" } else { "" });
s
}
pub fn part_head(&self) -> &Row {
self.query.part_head_group.get_row(self.part_head)
}
pub fn rows(&self) -> Block<(MethodId, usize)> {
let plain_courses = self
.query
.methods
.iter_enumerated()
.map(|(index, m)| {
m.plain_course()
.map_annots(|a| (MethodId { index }, a.sub_lead_idx))
})
.collect::<MethodVec<_>>();
let mut first_part = Block::with_leftover_row(self.query.start_row.clone());
for elem in &self.path {
assert_eq!(first_part.leftover_row(), elem.start_row.as_row());
let plain_course = &plain_courses[elem.method];
let start_idx = elem.start_sub_lead_idx;
let end_idx = start_idx + elem.length.as_usize();
if end_idx > plain_course.len() {
first_part
.extend_range(plain_course, start_idx..)
.expect("All path elems should have the same stage");
first_part
.extend_range(plain_course, ..end_idx - plain_course.len())
.expect("All path elems should have the same stage");
} else {
first_part
.extend_range(plain_course, start_idx..end_idx)
.expect("All path elems should have the same stage");
}
if let Some(call_idx) = elem.call {
let last_non_leftover_row = first_part.rows().next_back().unwrap();
let new_leftover_row = last_non_leftover_row
* self.query.calls[call_idx].place_notation.transposition();
first_part
.leftover_row_mut()
.copy_from(&new_leftover_row)
.unwrap();
}
}
let part_len = first_part.len();
let mut comp = first_part;
for _ in 0..self.query.num_parts() - 1 {
comp.extend_from_within(..part_len);
}
assert_eq!(comp.len(), self.length());
assert_eq!(comp.leftover_row(), self.query.end_row.as_row());
comp
}
pub fn total_score(&self) -> f32 {
self.total_score.0
}
pub fn average_score(&self) -> f32 {
self.total_score() / self.length() as f32
}
pub fn music_score(&self) -> f32 {
self.music_counts
.iter()
.map(|(id, count)| f32::from(self.query.music_types[id.index].weight) * *count as f32)
.sum::<f32>()
}
pub fn method_counts(&self) -> &[usize] {
self.method_counts.as_slice()
}
pub fn music_counts(&self) -> &HashMap<MusicTypeId, usize> {
&self.music_counts
}
pub fn contiguous_duffer_lengths(&self) -> impl DoubleEndedIterator<Item = usize> + '_ {
self.contiguous_duffer_lengths.iter().map(|l| l.as_usize())
}
pub fn total_duffer(&self) -> usize {
self.total_duffer.as_usize()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PathElem {
pub start_row: RowBuf,
pub method: MethodIdx,
pub start_sub_lead_idx: usize,
pub length: PerPartLength,
pub call: Option<CallIdx>,
}
impl PathElem {
pub(crate) fn ends_with_plain(&self) -> bool {
self.call.is_none()
}
pub(crate) fn end_sub_lead_idx(&self, query: &Query) -> usize {
query.methods[self.method].add_sub_lead_idx(self.start_sub_lead_idx, self.length)
}
}
#[derive(Debug, Clone, Copy)]
struct DisplayComposition<'a>(pub &'a Composition, pub &'a Query);
impl std::fmt::Display for DisplayComposition<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let DisplayComposition(comp, query) = self;
write!(f, "len: {}, ", comp.length)?;
if query.is_spliced() {
write!(f, "ms: {:>3?}, ", comp.method_counts.as_slice())?;
}
if query.num_parts() > 2 {
write!(f, "PH: {}, ", comp.part_head())?;
}
write!(
f,
"music: {:>6.2?}, avg score: {:.6}, str: {}",
comp.music_score(),
comp.average_score(),
comp.call_string()
)
}
}
fn num_leads_covered(lead_len: usize, start_sub_lead_idx: usize, length: PerPartLength) -> usize {
assert_ne!(length, PerPartLength::ZERO); let dist_to_end_of_first_lead = lead_len - start_sub_lead_idx;
let rows_after_end_of_first_lead = length.as_usize().saturating_sub(dist_to_end_of_first_lead);
crate::utils::div_rounding_up(rows_after_end_of_first_lead, lead_len) + 1
}
#[cfg(test)]
mod tests {
use crate::utils::lengths::PerPartLength;
#[test]
fn num_leads_covered() {
assert_eq!(super::num_leads_covered(32, 0, PerPartLength::new(32)), 1);
assert_eq!(super::num_leads_covered(32, 2, PerPartLength::new(32)), 2);
assert_eq!(super::num_leads_covered(32, 2, PerPartLength::new(30)), 1);
assert_eq!(super::num_leads_covered(32, 0, PerPartLength::new(2)), 1);
assert_eq!(super::num_leads_covered(32, 16, PerPartLength::new(24)), 2);
}
}