use std::{
collections::{HashMap, HashSet},
hash::Hash,
};
use bellframe::{
method::LABEL_LEAD_END, method_lib::QueryError, Bell, Mask, MethodLib, Row, Stage,
};
use itertools::Itertools;
use crate::query::{self, CallVec, MethodIdx, MethodVec};
use super::{EndIndices, OptionalRangeInclusive};
#[allow(unused_imports)] use super::SearchBuilder;
const NUM_METHOD_SUGGESTIONS: usize = 10;
pub struct Method {
source: MethodSource,
lead_labels: HashMap<String, Vec<isize>>,
custom_shorthand: Option<String>,
override_count_range: OptionalRangeInclusive,
override_start_indices: Option<Vec<isize>>,
override_end_indices: Option<Vec<isize>>,
override_courses: Option<Vec<String>>,
}
enum MethodSource {
Title(String),
CustomPn {
name: String,
pn_str: String,
stage: Stage, },
}
impl Method {
pub fn with_title(title: String) -> Self {
Self::new(MethodSource::Title(title))
}
pub fn with_custom_pn(name: String, pn_str: String, stage: Stage) -> Self {
Self::new(MethodSource::CustomPn {
name,
pn_str,
stage,
})
}
fn new(source: MethodSource) -> Self {
Self {
source,
lead_labels: hmap::hmap! { LABEL_LEAD_END.to_owned() => vec![0] },
custom_shorthand: None,
override_count_range: OptionalRangeInclusive::OPEN,
override_start_indices: None,
override_end_indices: None,
override_courses: None,
}
}
pub fn shorthand(mut self, shorthand: Option<String>) -> Self {
self.custom_shorthand = shorthand;
self
}
pub fn count_range(mut self, range: OptionalRangeInclusive) -> Self {
self.override_count_range = range;
self
}
pub fn start_indices(mut self, indices: Option<Vec<isize>>) -> Self {
self.override_start_indices = indices;
self
}
pub fn end_indices(mut self, indices: Option<Vec<isize>>) -> Self {
self.override_end_indices = indices;
self
}
pub fn courses(mut self, courses: Option<Vec<String>>) -> Self {
self.override_courses = courses;
self
}
pub fn lead_labels(mut self, labels: HashMap<String, Vec<isize>>) -> Self {
self.lead_labels = labels;
self
}
#[allow(clippy::should_implement_trait)]
pub fn add(self, set: &mut MethodSet) -> MethodId {
set.add(self)
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum SpliceStyle {
LeadLabels,
Calls,
}
impl Default for SpliceStyle {
fn default() -> Self {
Self::LeadLabels
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MethodId {
pub(crate) index: MethodIdx,
}
pub struct MethodSet {
pub(super) vec: MethodVec<Method>,
}
impl MethodSet {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, method: Method) -> MethodId {
let index = self.vec.push(method);
MethodId { index }
}
}
impl Default for MethodSet {
fn default() -> Self {
Self {
vec: MethodVec::new(),
}
}
}
impl<I> From<I> for MethodSet
where
I: IntoIterator<Item = Method>,
{
fn from(iter: I) -> Self {
Self {
vec: iter.into_iter().collect(),
}
}
}
impl Method {
pub(super) fn bellframe_method(&self, cc_lib: &MethodLib) -> crate::Result<bellframe::Method> {
let bellframe_method = match &self.source {
MethodSource::Title(title) => cc_lib
.get_by_title_with_suggestions(title, NUM_METHOD_SUGGESTIONS)
.map_err(|err| match err {
QueryError::PnParseErr { pn, error } => panic!(
"Invalid pn `{}` in CC library entry for {}: {}",
pn, title, error
),
QueryError::NotFound(suggestions) => crate::Error::MethodNotFound {
title: title.to_owned(),
suggestions,
},
}),
MethodSource::CustomPn {
name,
pn_str: place_notation_string,
stage,
} => bellframe::Method::from_place_not_string(
name.to_owned(),
*stage,
place_notation_string,
)
.map_err(|error| crate::Error::MethodPnParse {
name: name.to_owned(),
place_notation_string: place_notation_string.to_owned(),
error,
}),
};
let mut bellframe_method = bellframe_method?;
for (label, indices) in &self.lead_labels {
for index in indices {
let lead_len_i = bellframe_method.lead_len() as isize;
let index = ((index % lead_len_i) + lead_len_i) % lead_len_i;
bellframe_method.add_label(index as usize, label.clone());
}
}
Ok(bellframe_method)
}
#[allow(clippy::too_many_arguments)]
pub(super) fn build(
self,
bellframe_method: bellframe::Method,
fixed_bells: &[(Bell, usize)],
default_method_count: OptionalRangeInclusive,
default_start_indices: &[isize],
default_end_indices: &EndIndices,
allowed_course_masks: &[Mask],
non_duffer_courses: Option<&[CourseSet]>,
part_head: &Row,
stage: Stage,
) -> crate::Result<query::Method> {
let allowed_course_masks = match self.override_courses {
Some(unparsed_courses) => unparsed_courses
.into_iter()
.map(|mask_str| {
Mask::parse_with_stage(&mask_str, stage).map_err(|error| {
crate::Error::CustomCourseMaskParse {
method_title: bellframe_method.title().to_owned(),
mask_str,
error,
}
})
})
.collect::<Result<Vec<_>, _>>()?,
None => allowed_course_masks.to_owned(),
};
let allowed_course_set = CourseSet {
masks: allowed_course_masks,
any_stroke: false,
any_bells: false,
};
let not_wrapped_start_indices = self
.override_start_indices
.as_deref()
.unwrap_or(default_start_indices);
let mut start_indices = wrap_sub_lead_indices(not_wrapped_start_indices, &bellframe_method);
let mut end_indices = match (&self.override_end_indices, default_end_indices) {
(Some(indices), _) | (None, EndIndices::Specific(indices)) => {
wrap_sub_lead_indices(indices, &bellframe_method)
}
(None, EndIndices::Any) => (0..bellframe_method.lead_len()).collect_vec(),
};
if !part_head.is_rounds() {
let union = start_indices
.iter()
.filter(|idx| end_indices.contains(idx))
.copied()
.collect_vec();
start_indices = union.clone();
end_indices = union;
}
Ok(query::Method {
shorthand: self
.custom_shorthand
.unwrap_or_else(|| default_shorthand(bellframe_method.title())),
count_range: self.override_count_range.or(default_method_count),
plain_course: bellframe_method
.plain_course()
.map_annots(|annot| annot.labels.to_vec()),
start_indices,
end_indices,
allowed_course_masks: allowed_course_set
.as_course_masks(&bellframe_method, fixed_bells),
allowed_lead_masks: allowed_course_set.as_lead_masks(&bellframe_method, fixed_bells),
non_duffer_lead_masks: match non_duffer_courses {
Some(course_sets) => course_sets
.iter()
.flat_map(|c| c.as_lead_masks(&bellframe_method, fixed_bells))
.collect_vec(),
None => vec![Mask::empty(stage)], },
inner: bellframe_method,
})
}
}
#[derive(Debug, Clone)]
pub struct CourseSet {
pub masks: Vec<Mask>,
pub any_stroke: bool,
pub any_bells: bool,
}
impl CourseSet {
fn as_lead_masks(
&self,
method: &bellframe::Method,
fixed_bells: &[(Bell, usize)],
) -> Vec<Mask> {
let course_masks = self.as_course_masks(method, fixed_bells);
let mut lead_head_masks = HashSet::new();
for course_mask in course_masks {
for lead_head in method.lead_head().closure() {
lead_head_masks.insert(&course_mask * &lead_head);
}
}
let mut filtered_lead_head_masks = Vec::new();
for mask in &lead_head_masks {
let is_implied_by_another_mask = lead_head_masks
.iter()
.any(|mask2| mask.is_strict_subset_of(mask2));
if !is_implied_by_another_mask {
filtered_lead_head_masks.push(mask.clone());
}
}
filtered_lead_head_masks
}
fn as_course_masks(
&self,
method: &bellframe::Method,
fixed_bells: &[(Bell, usize)],
) -> Vec<Mask> {
let mut course_masks: Vec<Mask> = self.masks.clone();
if self.any_bells {
let mut expanded_masks = Vec::new();
for mask in &course_masks {
let min_bell = mask.bells().flatten().min().unwrap_or(Bell::TREBLE);
let max_bell = mask.bells().flatten().max().unwrap_or(Bell::TREBLE);
let min_offset = -(min_bell.index_u8() as i16);
let max_offset = (method.stage().num_bells_u8() - max_bell.number()) as i16;
for offset in min_offset..=max_offset {
expanded_masks.push(Mask::from_bells(
mask.bells()
.map(|maybe_bell| maybe_bell.map(|b| b + offset)),
));
}
}
course_masks = expanded_masks;
}
if self.any_stroke {
course_masks = course_masks
.into_iter()
.flat_map(|mask| [&mask * method.lead_end(), mask])
.collect_vec();
}
course_masks
.into_iter()
.filter_map(|mask| try_fixing_bells(mask, fixed_bells))
.collect_vec()
}
}
fn try_fixing_bells(mut mask: Mask, fixed_bells: &[(Bell, usize)]) -> Option<Mask> {
for &(bell, place) in fixed_bells {
mask.set_bell(bell, place).ok()?;
}
Some(mask)
}
fn wrap_sub_lead_indices(indices: &[isize], method: &bellframe::Method) -> Vec<usize> {
indices
.iter()
.map(|idx| {
let lead_len_i = method.lead_len() as isize;
(*idx % lead_len_i + lead_len_i) as usize % method.lead_len()
})
.collect_vec()
}
fn default_shorthand(title: &str) -> String {
title
.chars()
.next()
.expect("Can't have empty method title")
.to_string()
}
pub(super) fn fixed_bells(
methods: &MethodVec<(bellframe::Method, self::Method)>,
calls: &CallVec<query::Call>,
start_row: &Row,
stage: Stage,
) -> Vec<(Bell, usize)> {
let mut fixed_bells = stage.bells().collect_vec();
for (m, _) in methods {
let f = fixed_bells_of_method(m, calls);
fixed_bells.retain(|b| f.contains(b));
}
fixed_bells
.iter()
.map(|b| (start_row[b.index()], b.index()))
.collect_vec()
}
fn fixed_bells_of_method(
method: &bellframe::Method,
calls: &CallVec<query::Call>,
) -> HashSet<Bell> {
let mut fixed_bells: HashSet<Bell> = method.lead_head().fixed_bells().collect();
for call in calls {
filter_bells_fixed_by_call(method, call, &mut fixed_bells);
}
fixed_bells
}
fn filter_bells_fixed_by_call(
method: &bellframe::Method,
call: &query::Call,
set: &mut HashSet<Bell>,
) {
for sub_lead_idx_after_call in method.label_indices(&call.label_from) {
let idx_before_call = (sub_lead_idx_after_call + method.lead_len() - 1) % method.lead_len();
let idx_after_call = idx_before_call + 1;
let row_before_call = method.first_lead().get_row(idx_before_call).unwrap();
let row_after_no_call = method.first_lead().get_row(idx_after_call).unwrap();
let mut row_after_call = row_before_call.to_owned();
call.place_notation.permute(&mut row_after_call).unwrap();
for (bell_after_no_call, bell_after_call) in
row_after_no_call.bell_iter().zip(&row_after_call)
{
if bell_after_call != bell_after_no_call {
set.remove(&bell_after_call);
}
}
}
}