1use std::collections::{HashMap, HashSet, BinaryHeap};
11use std::cmp::Ordering;
12
13#[derive(Debug, Clone)]
15pub struct Activation {
16 pub rule_name: String,
18 pub salience: i32,
20 pub activation_group: Option<String>,
22 pub agenda_group: String,
24 pub ruleflow_group: Option<String>,
26 pub no_loop: bool,
28 pub lock_on_active: bool,
30 pub auto_focus: bool,
32 pub created_at: std::time::Instant,
34 id: usize,
36}
37
38impl Activation {
39 pub fn new(rule_name: String, salience: i32) -> Self {
41 Self {
42 rule_name,
43 salience,
44 activation_group: None,
45 agenda_group: "MAIN".to_string(),
46 ruleflow_group: None,
47 no_loop: true,
48 lock_on_active: false,
49 auto_focus: false,
50 created_at: std::time::Instant::now(),
51 id: 0,
52 }
53 }
54
55 pub fn with_activation_group(mut self, group: String) -> Self {
57 self.activation_group = Some(group);
58 self
59 }
60
61 pub fn with_agenda_group(mut self, group: String) -> Self {
63 self.agenda_group = group;
64 self
65 }
66
67 pub fn with_ruleflow_group(mut self, group: String) -> Self {
69 self.ruleflow_group = Some(group);
70 self
71 }
72
73 pub fn with_no_loop(mut self, no_loop: bool) -> Self {
75 self.no_loop = no_loop;
76 self
77 }
78
79 pub fn with_lock_on_active(mut self, lock: bool) -> Self {
81 self.lock_on_active = lock;
82 self
83 }
84
85 pub fn with_auto_focus(mut self, auto_focus: bool) -> Self {
87 self.auto_focus = auto_focus;
88 self
89 }
90}
91
92impl PartialEq for Activation {
94 fn eq(&self, other: &Self) -> bool {
95 self.id == other.id
96 }
97}
98
99impl Eq for Activation {}
100
101impl PartialOrd for Activation {
102 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
103 Some(self.cmp(other))
104 }
105}
106
107impl Ord for Activation {
108 fn cmp(&self, other: &Self) -> Ordering {
109 match self.salience.cmp(&other.salience) {
111 Ordering::Equal => {
112 other.created_at.cmp(&self.created_at)
114 }
115 other_order => other_order,
116 }
117 }
118}
119
120pub struct AdvancedAgenda {
122 activations: HashMap<String, BinaryHeap<Activation>>,
124 focus: String,
126 focus_stack: Vec<String>,
128 fired_rules: HashSet<String>,
130 fired_activation_groups: HashSet<String>,
132 locked_groups: HashSet<String>,
134 active_ruleflow_groups: HashSet<String>,
136 next_id: usize,
138}
139
140impl AdvancedAgenda {
141 pub fn new() -> Self {
143 let mut agenda = Self {
144 activations: HashMap::new(),
145 focus: "MAIN".to_string(),
146 focus_stack: Vec::new(),
147 fired_rules: HashSet::new(),
148 fired_activation_groups: HashSet::new(),
149 locked_groups: HashSet::new(),
150 active_ruleflow_groups: HashSet::new(),
151 next_id: 0,
152 };
153 agenda.activations.insert("MAIN".to_string(), BinaryHeap::new());
154 agenda
155 }
156
157 pub fn add_activation(&mut self, mut activation: Activation) {
159 if activation.auto_focus && activation.agenda_group != self.focus {
161 self.set_focus(activation.agenda_group.clone());
162 }
163
164 if let Some(ref group) = activation.activation_group {
166 if self.fired_activation_groups.contains(group) {
167 return; }
169 }
170
171 if let Some(ref group) = activation.ruleflow_group {
173 if !self.active_ruleflow_groups.contains(group) {
174 return; }
176 }
177
178 activation.id = self.next_id;
180 self.next_id += 1;
181
182 self.activations
184 .entry(activation.agenda_group.clone())
185 .or_insert_with(BinaryHeap::new)
186 .push(activation);
187 }
188
189 pub fn get_next_activation(&mut self) -> Option<Activation> {
191 loop {
192 if let Some(heap) = self.activations.get_mut(&self.focus) {
194 while let Some(activation) = heap.pop() {
195 if activation.no_loop && self.fired_rules.contains(&activation.rule_name) {
197 continue;
198 }
199
200 if activation.lock_on_active && self.locked_groups.contains(&activation.agenda_group) {
202 continue;
203 }
204
205 if let Some(ref group) = activation.activation_group {
207 if self.fired_activation_groups.contains(group) {
208 continue;
209 }
210 }
211
212 return Some(activation);
213 }
214 }
215
216 if let Some(prev_focus) = self.focus_stack.pop() {
218 self.focus = prev_focus;
219 } else {
220 return None; }
222 }
223 }
224
225 pub fn mark_rule_fired(&mut self, activation: &Activation) {
227 self.fired_rules.insert(activation.rule_name.clone());
228
229 if let Some(ref group) = activation.activation_group {
231 self.fired_activation_groups.insert(group.clone());
232 }
233
234 if activation.lock_on_active {
236 self.locked_groups.insert(activation.agenda_group.clone());
237 }
238 }
239
240 pub fn set_focus(&mut self, group: String) {
242 if group != self.focus {
243 self.focus_stack.push(self.focus.clone());
244 self.focus = group;
245 }
246 }
247
248 pub fn get_focus(&self) -> &str {
250 &self.focus
251 }
252
253 pub fn clear(&mut self) {
255 self.activations.clear();
256 self.activations.insert("MAIN".to_string(), BinaryHeap::new());
257 self.focus = "MAIN".to_string();
258 self.focus_stack.clear();
259 self.fired_rules.clear();
260 self.fired_activation_groups.clear();
261 self.locked_groups.clear();
262 }
263
264 pub fn reset_fired_flags(&mut self) {
266 self.fired_rules.clear();
267 self.fired_activation_groups.clear();
268 self.locked_groups.clear();
269 }
270
271 pub fn activate_ruleflow_group(&mut self, group: String) {
273 self.active_ruleflow_groups.insert(group);
274 }
275
276 pub fn deactivate_ruleflow_group(&mut self, group: &str) {
278 self.active_ruleflow_groups.remove(group);
279 }
280
281 pub fn is_ruleflow_group_active(&self, group: &str) -> bool {
283 self.active_ruleflow_groups.contains(group)
284 }
285
286 pub fn stats(&self) -> AgendaStats {
288 let total_activations: usize = self.activations.values().map(|heap| heap.len()).sum();
289 let groups = self.activations.len();
290
291 AgendaStats {
292 total_activations,
293 groups,
294 focus: self.focus.clone(),
295 fired_rules: self.fired_rules.len(),
296 fired_activation_groups: self.fired_activation_groups.len(),
297 active_ruleflow_groups: self.active_ruleflow_groups.len(),
298 }
299 }
300}
301
302impl Default for AdvancedAgenda {
303 fn default() -> Self {
304 Self::new()
305 }
306}
307
308#[derive(Debug, Clone)]
310pub struct AgendaStats {
311 pub total_activations: usize,
312 pub groups: usize,
313 pub focus: String,
314 pub fired_rules: usize,
315 pub fired_activation_groups: usize,
316 pub active_ruleflow_groups: usize,
317}
318
319impl std::fmt::Display for AgendaStats {
320 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321 write!(
322 f,
323 "Agenda Stats: {} activations, {} groups, focus='{}', {} fired rules",
324 self.total_activations, self.groups, self.focus, self.fired_rules
325 )
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn test_basic_activation() {
335 let mut agenda = AdvancedAgenda::new();
336
337 let act1 = Activation::new("Rule1".to_string(), 10);
338 let act2 = Activation::new("Rule2".to_string(), 20);
339
340 agenda.add_activation(act1);
341 agenda.add_activation(act2);
342
343 let next = agenda.get_next_activation().unwrap();
345 assert_eq!(next.rule_name, "Rule2");
346 }
347
348 #[test]
349 fn test_activation_groups() {
350 let mut agenda = AdvancedAgenda::new();
351
352 let act1 = Activation::new("Rule1".to_string(), 10)
353 .with_activation_group("group1".to_string());
354 let act2 = Activation::new("Rule2".to_string(), 20)
355 .with_activation_group("group1".to_string());
356
357 agenda.add_activation(act1);
358 agenda.add_activation(act2);
359
360 let first = agenda.get_next_activation().unwrap();
362 agenda.mark_rule_fired(&first);
363
364 let second = agenda.get_next_activation();
366 assert!(second.is_none());
367 }
368
369 #[test]
370 fn test_agenda_groups() {
371 let mut agenda = AdvancedAgenda::new();
372
373 let act1 = Activation::new("Rule1".to_string(), 10)
374 .with_agenda_group("group_a".to_string());
375 let act2 = Activation::new("Rule2".to_string(), 20)
376 .with_agenda_group("group_b".to_string());
377
378 agenda.add_activation(act1);
379 agenda.add_activation(act2);
380
381 assert!(agenda.get_next_activation().is_none());
383
384 agenda.set_focus("group_a".to_string());
386 let next = agenda.get_next_activation().unwrap();
387 assert_eq!(next.rule_name, "Rule1");
388 }
389
390 #[test]
391 fn test_auto_focus() {
392 let mut agenda = AdvancedAgenda::new();
393
394 let act = Activation::new("Rule1".to_string(), 10)
395 .with_agenda_group("special".to_string())
396 .with_auto_focus(true);
397
398 agenda.add_activation(act);
399
400 assert_eq!(agenda.get_focus(), "special");
402 }
403
404 #[test]
405 fn test_ruleflow_groups() {
406 let mut agenda = AdvancedAgenda::new();
407
408 let act = Activation::new("Rule1".to_string(), 10)
409 .with_ruleflow_group("flow1".to_string());
410
411 agenda.add_activation(act.clone());
413 assert_eq!(agenda.stats().total_activations, 0);
414
415 agenda.activate_ruleflow_group("flow1".to_string());
417 agenda.add_activation(act);
418 assert_eq!(agenda.stats().total_activations, 1);
419 }
420}