flow_gates/scope.rs
1use crate::hierarchy::GateHierarchy;
2use crate::types::{Gate, GateMode};
3use std::sync::Arc;
4
5/// Builder for querying and filtering gates
6///
7/// `GateQuery` provides a fluent API for filtering gates by various criteria
8/// such as parameters, scope, and other properties.
9///
10/// # Example
11///
12/// ```rust,no_run
13/// use flow_gates::{GateQuery, Gate};
14///
15/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
16/// // In practice, you would get gates from storage
17/// // let gate1 = /* ... */;
18/// // let gate2 = /* ... */;
19/// // let gate3 = /* ... */;
20/// // let gates = vec![&gate1, &gate2, &gate3];
21/// // let filtered: Vec<&Gate> = GateQuery::new(gates.iter().copied())
22/// // .by_parameters("FSC-A", "SSC-A")
23/// // .by_scope(Some("file-guid"))
24/// // .collect();
25/// # Ok(())
26/// # }
27/// ```
28pub struct GateQuery<'a> {
29 gates: Vec<&'a Gate>,
30}
31
32impl<'a> GateQuery<'a> {
33 /// Create a new query builder from an iterator of gates
34 ///
35 /// # Arguments
36 /// * `gates` - Iterator over gate references
37 pub fn new(gates: impl Iterator<Item = &'a Gate>) -> Self {
38 Self {
39 gates: gates.collect(),
40 }
41 }
42
43 /// Filter gates by parameters (channels)
44 ///
45 /// Only gates that operate on the specified x and y parameters are retained.
46 ///
47 /// # Arguments
48 /// * `x` - X-axis parameter name
49 /// * `y` - Y-axis parameter name
50 pub fn by_parameters(mut self, x: &str, y: &str) -> Self {
51 self.gates.retain(|gate| {
52 gate.x_parameter_channel_name() == x && gate.y_parameter_channel_name() == y
53 });
54 self
55 }
56
57 /// Filter gates by scope (file GUID)
58 ///
59 /// Only gates that apply to the specified file (or are global) are retained.
60 ///
61 /// # Arguments
62 /// * `file_guid` - Optional file GUID. If `None`, only global gates are returned.
63 pub fn by_scope(mut self, file_guid: Option<&str>) -> Self {
64 match file_guid {
65 Some(guid) => self.gates.retain(|gate| gate.mode.applies_to(guid)),
66 None => self.gates.retain(|gate| matches!(gate.mode, GateMode::Global)),
67 }
68 self
69 }
70
71 /// Collect the filtered gates into a vector
72 ///
73 /// # Returns
74 /// A vector of gate references matching the query criteria
75 pub fn collect(self) -> Vec<&'a Gate> {
76 self.gates
77 }
78}
79
80impl<'a> IntoIterator for GateQuery<'a> {
81 type Item = &'a Gate;
82 type IntoIter = std::vec::IntoIter<&'a Gate>;
83
84 fn into_iter(self) -> Self::IntoIter {
85 self.gates.into_iter()
86 }
87}
88
89/// Helper functions for managing gate types
90impl GateMode {
91 /// Create a global type
92 pub fn global() -> Self {
93 GateMode::Global
94 }
95
96 /// Create a file-specific type
97 pub fn file_specific(guid: impl Into<Arc<str>>) -> Self {
98 GateMode::FileSpecific { guid: guid.into() }
99 }
100
101 /// Create a file group type
102 pub fn file_group(guids: Vec<impl Into<Arc<str>>>) -> Self {
103 GateMode::FileGroup {
104 guids: guids.into_iter().map(|g| g.into()).collect(),
105 }
106 }
107
108 /// Add a file to a group type (no-op for Global or FileSpecific)
109 pub fn add_file(&mut self, guid: impl Into<Arc<str>>) {
110 if let GateMode::FileGroup { guids } = self {
111 let guid = guid.into();
112 if !guids.contains(&guid) {
113 guids.push(guid);
114 }
115 }
116 }
117
118 /// Remove a file from a group type (no-op for Global or FileSpecific)
119 pub fn remove_file(&mut self, guid: &str) {
120 if let GateMode::FileGroup { guids } = self {
121 guids.retain(|g| g.as_ref() != guid);
122 }
123 }
124
125 /// Get the list of file GUIDs this type applies to (None for Global)
126 pub fn file_guids(&self) -> Option<Vec<&str>> {
127 match self {
128 GateMode::Global => None,
129 GateMode::FileSpecific { guid } => Some(vec![guid.as_ref()]),
130 GateMode::FileGroup { guids } => Some(guids.iter().map(|g| g.as_ref()).collect()),
131 }
132 }
133}
134
135/// Filter gates by type (scope)
136///
137/// This is a convenience function that filters gates by their scope.
138/// Consider using `GateQuery` for more complex filtering needs.
139///
140/// # Arguments
141/// * `gates` - Iterator over gate references
142/// * `file_guid` - Optional file GUID. If `None`, only global gates are returned.
143///
144/// # Returns
145/// A vector of gates matching the scope criteria
146pub fn filter_gates_by_type<'a>(
147 gates: impl Iterator<Item = &'a Gate>,
148 file_guid: Option<&str>,
149) -> Vec<&'a Gate> {
150 match file_guid {
151 Some(guid) => gates.filter(|gate| gate.mode.applies_to(guid)).collect(),
152 None => gates
153 .filter(|gate| matches!(gate.mode, GateMode::Global))
154 .collect(),
155 }
156}
157
158/// Filter gates by parameters (channels)
159///
160/// Returns only gates that operate on the specified x and y parameters.
161///
162/// # Arguments
163/// * `gates` - Iterator over gate references
164/// * `x_param` - X-axis parameter name
165/// * `y_param` - Y-axis parameter name
166///
167/// # Returns
168/// A vector of gates operating on the specified parameters
169///
170/// # Example
171/// ```rust,no_run
172/// use flow_gates::{filter_gates_by_parameters, Gate};
173///
174/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
175/// // In practice, you would get gates from storage
176/// // let gate1 = /* ... */;
177/// // let gate2 = /* ... */;
178/// // let gate3 = /* ... */;
179/// // let gates = vec![&gate1, &gate2, &gate3];
180/// // let fsc_ssc_gates = filter_gates_by_parameters(gates.iter().copied(), "FSC-A", "SSC-A");
181/// # Ok(())
182/// # }
183/// ```
184pub fn filter_gates_by_parameters<'a>(
185 gates: impl Iterator<Item = &'a Gate>,
186 x_param: &str,
187 y_param: &str,
188) -> Vec<&'a Gate> {
189 gates
190 .filter(|gate| {
191 gate.x_parameter_channel_name() == x_param && gate.y_parameter_channel_name() == y_param
192 })
193 .collect()
194}
195
196/// Filter gates by scope
197///
198/// Returns gates that apply to the specified file (or are global if `file_guid` is `None`).
199///
200/// # Arguments
201/// * `gates` - Iterator over gate references
202/// * `file_guid` - Optional file GUID. If `None`, only global gates are returned.
203///
204/// # Returns
205/// A vector of gates matching the scope criteria
206///
207/// # Example
208/// ```rust,no_run
209/// use flow_gates::{filter_gates_by_scope, Gate};
210///
211/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
212/// // In practice, you would get gates from storage
213/// // let gate1 = /* ... */;
214/// // let gate2 = /* ... */;
215/// // let gate3 = /* ... */;
216/// // let gates = vec![&gate1, &gate2, &gate3];
217/// // let file_gates = filter_gates_by_scope(gates.iter().copied(), Some("file-123"));
218/// // let global_gates = filter_gates_by_scope(gates.iter().copied(), None);
219/// # Ok(())
220/// # }
221/// ```
222pub fn filter_gates_by_scope<'a>(
223 gates: impl Iterator<Item = &'a Gate>,
224 file_guid: Option<&str>,
225) -> Vec<&'a Gate> {
226 filter_gates_by_type(gates, file_guid)
227}
228
229/// Filter a hierarchy by parameters, rebuilding relationships
230///
231/// Creates a new hierarchy containing only gates that operate on the specified
232/// parameters, preserving parent-child relationships where both gates match.
233///
234/// # Arguments
235/// * `hierarchy` - The original hierarchy
236/// * `gates_map` - Map of gate IDs to gate references
237/// * `x_param` - X-axis parameter name
238/// * `y_param` - Y-axis parameter name
239///
240/// # Returns
241/// A new hierarchy containing only gates matching the parameters
242///
243/// # Example
244/// ```rust,no_run
245/// use flow_gates::{filter_hierarchy_by_parameters, GateHierarchy, Gate};
246/// use std::collections::HashMap;
247/// use std::sync::Arc;
248///
249/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
250/// let hierarchy = GateHierarchy::new();
251/// let mut gates_map: HashMap<Arc<str>, &Gate> = HashMap::new();
252/// // ... populate gates_map ...
253///
254/// let filtered = filter_hierarchy_by_parameters(&hierarchy, &gates_map, "FSC-A", "SSC-A");
255/// # Ok(())
256/// # }
257/// ```
258pub fn filter_hierarchy_by_parameters(
259 hierarchy: &GateHierarchy,
260 gates_map: &std::collections::HashMap<Arc<str>, &Gate>,
261 x_param: &str,
262 y_param: &str,
263) -> GateHierarchy {
264 let mut new_hierarchy = GateHierarchy::new();
265
266 // Get all gates matching parameters
267 let matching_gates: std::collections::HashSet<Arc<str>> = gates_map
268 .iter()
269 .filter(|(_, gate)| {
270 gate.x_parameter_channel_name() == x_param
271 && gate.y_parameter_channel_name() == y_param
272 })
273 .map(|(id, _)| id.clone())
274 .collect();
275
276 // Rebuild relationships for matching gates using public API
277 // Get all gates in hierarchy and check their relationships
278 if let Some(sorted_gates) = hierarchy.topological_sort() {
279 for gate_id in sorted_gates {
280 if matching_gates.contains(&gate_id) {
281 // Check if this gate has a parent that also matches
282 if let Some(parent_id) = hierarchy.get_parent(gate_id.as_ref()) {
283 if matching_gates.contains(parent_id) {
284 let _ = new_hierarchy.add_child(parent_id.clone(), gate_id.clone());
285 }
286 }
287 }
288 }
289 }
290
291 new_hierarchy
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297
298 #[test]
299 fn test_global_type_applies_to_all() {
300 let gate_type = GateMode::global();
301 assert!(gate_type.applies_to("any-guid"));
302 assert!(gate_type.applies_to("another-guid"));
303 }
304
305 #[test]
306 fn test_file_specific_type() {
307 let gate_type = GateMode::file_specific("test-guid");
308 assert!(gate_type.applies_to("test-guid"));
309 assert!(!gate_type.applies_to("other-guid"));
310 }
311
312 #[test]
313 fn test_file_group_type() {
314 let mut gate_type = GateMode::file_group(vec!["guid1", "guid2"]);
315 assert!(gate_type.applies_to("guid1"));
316 assert!(gate_type.applies_to("guid2"));
317 assert!(!gate_type.applies_to("guid3"));
318
319 gate_type.add_file("guid3");
320 assert!(gate_type.applies_to("guid3"));
321
322 gate_type.remove_file("guid1");
323 assert!(!gate_type.applies_to("guid1"));
324 }
325}