use super::Matrix;
use crate::types::Color;
use crate::types::ItemId;
use crate::ColoredItem;
use std::collections::HashMap;
use std::fmt::Debug;
use std::fmt::Display;
#[derive(Debug, thiserror::Error)]
pub enum BuildError {
#[error("Item {0} is used in an option, but not declared")]
ItemNotDeclared(String),
#[error("Item {0} is declared as both primary and secondary")]
ItemDeclaredTwice(String),
#[error("No primary items declared")]
NoPrimaryItems,
#[error("Primary item {0} is not used in any option, so no solutions are possible.")]
PrimaryItemNotUsed(String),
#[error("No options declared")]
NoOptions,
}
#[derive(Debug, Clone)]
pub struct Builder<T> {
primary_items: Vec<String>,
secondary_items: Vec<String>,
options: Vec<(T, Vec<String>)>,
}
impl<T> Default for Builder<T> {
fn default() -> Self {
Self {
primary_items: Vec::new(),
secondary_items: Vec::new(),
options: Vec::new(),
}
}
}
impl<T> Builder<T> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_primary_items<S: Display>(&mut self, items: impl IntoIterator<Item = S>) {
self.primary_items
.extend(items.into_iter().map(|t| t.to_string()));
}
pub fn add_secondary_items<S: Display>(&mut self, items: impl IntoIterator<Item = S>) {
self.secondary_items
.extend(items.into_iter().map(|t| t.to_string()));
}
pub fn add_primary_item(&mut self, item: impl Display) {
self.primary_items.push(item.to_string());
}
pub fn add_secondary_item(&mut self, item: impl Display) {
self.secondary_items.push(item.to_string());
}
pub fn add_option<S: Display>(&mut self, meaning: T, items: impl IntoIterator<Item = S>) {
let items: Vec<_> = items.into_iter().map(|i| i.to_string()).collect();
for item in &items {
if let Some((item_name, _color)) = item.split_once(':') {
assert!(
!self.primary_items.contains(&item_name.to_string()),
"Primary items cannot be colored: {item} in {items:?}"
);
}
}
self.options.push((meaning, items));
}
pub fn build(self) -> Result<Matrix<T>, BuildError> {
let primary_items: &[String] = &self.primary_items;
let secondary_items: &[String] = &self.secondary_items;
let options = self.options;
let header_names: HashMap<&str, ItemId> = primary_items
.iter()
.chain(secondary_items.iter())
.enumerate()
.map(|(i, name)| (name.as_ref(), ItemId::new(i)))
.collect();
let mut colors = HashMap::new();
for (_, option) in &options {
for item in option {
if let Some((_name, color)) = item.split_once(':') {
let next_id = colors.len();
colors.entry(color.to_string()).or_insert(next_id);
}
}
}
let mut matrix = Matrix::new(self.primary_items.len(), self.secondary_items.len());
for (meaning, opt_items) in options {
let mut parsed_items = Vec::new();
for s in opt_items {
let parsed_item = if let Some((name, color)) = s.split_once(':') {
let item_id = *header_names
.get(name)
.ok_or_else(|| BuildError::ItemNotDeclared(name.to_string()))?;
let color_id = colors[color];
ColoredItem::with_color(item_id, Color::new(color_id))
} else {
let item_id = header_names[s.as_str()];
ColoredItem::new(item_id)
};
parsed_items.push(parsed_item);
}
matrix.add_option(meaning, &parsed_items);
}
Ok(matrix)
}
}
impl<T: Debug> Builder<T> {
#[must_use]
pub fn dump_knuth_format(&self) -> String {
let mut buf = self.primary_items.join(" ").to_string();
if !self.secondary_items.is_empty() {
buf.push_str(" | ");
buf.push_str(&self.secondary_items.join(" "));
}
buf.push('\n');
for (_, items) in &self.options {
buf.push_str(&items.join(" "));
}
buf
}
}