1use crate::user::User;
4use crate::user_file::UserFile;
5
6pub struct UserList {
7 user_file: UserFile,
8}
9
10impl UserList {
11 pub fn new(user_file: UserFile) -> Self {
12 UserList { user_file }
13 }
14
15 pub fn add(&self, user: &User) {
16 let _ = self.user_file.write(user);
17 }
18
19 pub fn list(&self) -> Vec<User> {
20 self.user_file.read()
21 }
22
23 pub fn find(&self, search_terms: &[String]) -> Result<Vec<User>, String> {
25 let all = self.list();
26 if search_terms.is_empty() {
27 return Ok(vec![]);
28 }
29 let mut matches_per_term: Vec<Vec<User>> = Vec::new();
30 for term in search_terms {
31 let m = Self::matching_users(&all, term);
32 if m.is_empty() {
33 return Err(format!("No user found matching '{}'", term));
34 }
35 matches_per_term.push(m);
36 }
37 Self::unique_combination(&matches_per_term)
38 .ok_or_else(|| {
39 format!(
40 "Couldn't find a combination of users matching {}",
41 search_terms
42 .iter()
43 .map(|s| format!("'{}'", s))
44 .collect::<Vec<_>>()
45 .join(", ")
46 )
47 })
48 }
49
50 fn matching_users(all: &[User], search_term: &str) -> Vec<User> {
51 let term_lower = search_term.to_lowercase();
52 let mut result: Vec<User> = all
53 .iter()
54 .filter(|u| {
55 u.name()
57 .split_whitespace()
58 .any(|w| w.to_lowercase() == term_lower)
59 || u.name().to_lowercase().split_whitespace().any(|w| {
60 w.starts_with(&term_lower) || term_lower.starts_with(&w.to_lowercase())
61 })
62 || u.initials().contains(&term_lower)
63 || u.name().to_lowercase().contains(&term_lower)
64 || u.email().to_lowercase().contains(&term_lower)
65 })
66 .cloned()
67 .collect();
68 result.dedup();
69 result
70 }
71
72 fn unique_combination(term_matches: &[Vec<User>]) -> Option<Vec<User>> {
73 let mut combinations: Vec<Vec<User>> = vec![vec![]];
74 for matches in term_matches {
75 let mut new_combos = Vec::new();
76 for combo in &combinations {
77 for u in matches {
78 if !combo.iter().any(|c| c == u) {
79 let mut extended = combo.clone();
80 extended.push(u.clone());
81 new_combos.push(extended);
82 }
83 }
84 }
85 combinations = new_combos;
86 }
87 combinations.into_iter().find(|c| c.len() == term_matches.len())
88 }
89}