use std::{
collections::{HashMap, HashSet},
hash::Hash,
};
use bellframe::{music::AtRowPositions, Bell, Block, Row, RowBuf, Stage, Stroke};
use itertools::Itertools;
use crate::{
parameters::{CallDisplayStyle, CallId, MethodId, MethodVec, MusicTypeVec, Parameters},
utils::lengths::{PerPartLength, TotalLength},
};
#[allow(unused_imports)] use crate::{
parameters::{Method, MusicType},
Search,
};
#[derive(Debug, Clone)]
pub struct Composition {
pub(crate) stage: Stage,
pub(crate) start_stroke: Stroke,
pub(crate) path: Vec<PathElem>,
pub(crate) length: TotalLength,
pub(crate) part_head: RowBuf,
pub(crate) total_score: f32,
}
impl Composition {
pub fn length(&self) -> usize {
self.length.as_usize()
}
pub fn method_counts(&self, params: &Parameters) -> MethodVec<usize> {
let counts_per_method_id = self
.rows(params)
.annots()
.counts_by(|(method_id, _)| *method_id);
params
.methods
.iter()
.map(|m| *counts_per_method_id.get(&m.id).unwrap_or(&0))
.collect()
}
pub fn music_counts_and_score(
&self,
params: &Parameters,
) -> (MusicTypeVec<AtRowPositions<usize>>, f32) {
let music_counts = self.music_counts(params);
let mut music_score = 0.0;
for (count, music_type) in music_counts.iter().zip_eq(¶ms.music_types) {
music_score += music_type.as_overall_score(*count);
}
(music_counts, music_score)
}
pub fn music_score(&self, params: &Parameters) -> f32 {
self.music_counts_and_score(params).1
}
pub fn music_counts(&self, params: &Parameters) -> MusicTypeVec<AtRowPositions<usize>> {
let rows = self.rows(params);
params
.music_types
.iter()
.map(|mt| mt.count(&rows, !self.start_stroke))
.collect()
}
pub fn part_head(&self) -> &Row {
&self.part_head
}
pub fn total_score(&self) -> f32 {
self.total_score
}
pub fn average_score(&self) -> f32 {
self.total_score() / self.length() as f32
}
pub fn atw_factor(&self, params: &Parameters) -> f32 {
let working_bells = params.working_bells();
let mut is_working = vec![false; self.stage.num_bells()];
for b in &working_bells {
is_working[b.index()] = true;
}
let total_unique_row_positions = working_bells.len() * working_bells.len() * params.methods.iter().map(|m| m.lead_len()).sum::<usize>();
let mut rung_place_bell_positions = HashSet::<(Bell, u8, MethodId, usize)>::new();
for (&(method_id, sub_lead_idx), row) in self.rows(params).annot_rows() {
for (place, bell) in row.bell_iter().enumerate() {
if is_working[bell.index()] {
rung_place_bell_positions.insert((bell, place as u8, method_id, sub_lead_idx));
}
}
}
rung_place_bell_positions.len() as f32 / total_unique_row_positions as f32
}
pub fn call_string(&self, params: &Parameters) -> String {
let needs_brackets =
params.is_spliced() || params.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(params) > 0;
let mut path_iter = self.path.iter().peekable();
let mut s = String::new();
if params.call_display_style == CallDisplayStyle::Positional {
s.push('#');
}
s.push_str(if is_snap_start { "<" } else { "" });
while let Some(path_elem) = path_iter.next() {
if params.is_spliced() || params.call_display_style == CallDisplayStyle::Positional {
let method = params.get_method(path_elem.method_id);
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_id) = path_elem.call_to_end {
let call = params.get_call(call_id);
s.push_str(if needs_brackets { "[" } else { "" });
match params.call_display_style {
CallDisplayStyle::CallingPositions(calling_bell) => {
let row_after_call = path_iter
.peek()
.map_or(&self.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 rows(&self, params: &Parameters) -> Block<(MethodId, usize)> {
assert_eq!(self.stage, params.stage);
let mut plain_course_cache = HashMap::<MethodId, Block<(MethodId, usize)>>::new();
let mut first_part =
Block::<(MethodId, usize)>::with_leftover_row(params.start_row.clone());
for elem in &self.path {
assert_eq!(first_part.leftover_row(), elem.start_row.as_row());
let plain_course = plain_course_cache.entry(elem.method_id).or_insert_with(|| {
params
.get_method(elem.method_id)
.plain_course()
.map_annots(|annot| (elem.method_id, annot.sub_lead_idx))
});
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..);
first_part.extend_range(plain_course, ..end_idx - plain_course.len());
} else {
first_part.extend_range(plain_course, start_idx..end_idx);
}
if let Some(call_id) = elem.call_to_end {
let last_non_leftover_row = first_part.rows().next_back().unwrap();
let new_leftover_row =
last_non_leftover_row * params.get_call(call_id).place_notation.transposition();
first_part.leftover_row_mut().copy_from(&new_leftover_row);
}
}
let part_len = first_part.len();
let mut comp = first_part;
for _ in 0..params.num_parts() - 1 {
comp.extend_from_within(..part_len);
}
assert_eq!(comp.len(), self.length());
assert_eq!(comp.leftover_row(), ¶ms.end_row);
comp
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PathElem {
pub start_row: RowBuf,
pub method_id: MethodId,
pub start_sub_lead_idx: usize,
pub length: PerPartLength,
pub call_to_end: Option<CallId>,
}
impl PathElem {
pub fn ends_with_plain(&self) -> bool {
self.call_to_end.is_none()
}
pub(crate) fn end_sub_lead_idx(&self, params: &Parameters) -> usize {
params
.get_method(self.method_id)
.add_sub_lead_idx(self.start_sub_lead_idx, self.length)
}
}
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);
}
}