sorting_race/models/
interactive_mode.rs

1//! Interactive mode state machine for terminal interface
2
3use crate::models::{
4    configuration::{ConfigurationState, DistributionType},
5};
6use anyhow::{Result, anyhow};
7use std::time::Instant;
8
9/// Application mode states for the interactive interface
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ApplicationMode {
12    /// User is setting parameters
13    Configuration,
14    /// Algorithms are actively running
15    Racing,
16    /// Race paused, can view/configure
17    Paused,
18    /// Race finished, can reconfigure
19    Complete,
20}
21
22/// Configuration field focus for interactive input
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum ConfigurationField {
25    ArraySize,
26    Distribution,
27    FairnessMode,
28    BudgetParam,
29    AlphaParam,
30    BetaParam,
31    LearningRateParam,
32}
33
34/// Interactive mode state management
35#[derive(Debug, Clone)]
36pub struct InteractiveMode {
37    /// Current application mode
38    pub current_mode: ApplicationMode,
39    /// Current configuration focus (None means main menu)
40    pub config_focus: Option<ConfigurationField>,
41    /// Currently selected algorithm for array visualization
42    pub array_view_algorithm: usize,
43    /// Whether help overlay is visible
44    pub help_visible: bool,
45    /// Current configuration state
46    configuration: ConfigurationState,
47    /// Race start time for timing
48    race_start_time: Option<Instant>,
49    /// Whether race timer is paused
50    race_timer_paused: bool,
51    /// Error message to display
52    error_message: Option<String>,
53    /// Whether display needs update
54    needs_update: bool,
55}
56
57impl InteractiveMode {
58    /// Create a new interactive mode
59    pub fn new() -> Self {
60        Self {
61            current_mode: ApplicationMode::Configuration,
62            config_focus: None,
63            array_view_algorithm: 0,
64            help_visible: false,
65            configuration: ConfigurationState::new(),
66            race_start_time: None,
67            race_timer_paused: false,
68            error_message: None,
69            needs_update: true,
70        }
71    }
72
73    /// Get current configuration
74    pub fn get_current_config(&self) -> &ConfigurationState {
75        &self.configuration
76    }
77
78    /// Set configuration
79    pub fn set_config(&mut self, config: ConfigurationState) {
80        self.configuration = config;
81        self.needs_update = true;
82    }
83
84    /// Check if currently in configuration mode
85    pub fn is_configuration_mode(&self) -> bool {
86        self.current_mode == ApplicationMode::Configuration
87    }
88
89    /// Check if race is currently active
90    pub fn is_race_active(&self) -> bool {
91        self.current_mode == ApplicationMode::Racing
92    }
93
94    /// Check if race is paused
95    pub fn is_race_paused(&self) -> bool {
96        self.current_mode == ApplicationMode::Paused
97    }
98
99    /// Check if race is complete
100    pub fn is_race_complete(&self) -> bool {
101        self.current_mode == ApplicationMode::Complete
102    }
103
104    /// Check if race timer is active
105    pub fn is_race_timer_active(&self) -> bool {
106        self.race_start_time.is_some() && !self.race_timer_paused
107    }
108
109    /// Check if race timer is paused
110    pub fn is_race_timer_paused(&self) -> bool {
111        self.race_timer_paused
112    }
113
114    /// Check if menu is currently visible
115    pub fn is_menu_visible(&self) -> bool {
116        self.config_focus.is_some()
117    }
118
119    /// Check if help overlay should be shown
120    pub fn should_show_help_overlay(&self) -> bool {
121        self.help_visible
122    }
123
124    /// Check if display needs update
125    pub fn should_update_display(&self) -> bool {
126        self.needs_update
127    }
128
129    /// Check if selection has changed (for visual feedback)
130    pub fn has_selection_changed(&self) -> bool {
131        self.needs_update
132    }
133
134    /// Check if error message is visible
135    pub fn is_error_message_visible(&self) -> bool {
136        self.error_message.is_some()
137    }
138
139    /// Get current error message
140    pub fn get_error_message(&self) -> Option<&str> {
141        self.error_message.as_deref()
142    }
143
144    /// Clear error message
145    pub fn clear_error_message(&mut self) {
146        if self.error_message.is_some() {
147            self.error_message = None;
148            self.needs_update = true;
149        }
150    }
151
152    /// Set error message
153    pub fn set_error_message(&mut self, message: String) {
154        self.error_message = Some(message);
155        self.needs_update = true;
156    }
157
158    /// Get race elapsed time
159    pub fn get_race_elapsed_time(&self) -> Option<std::time::Duration> {
160        self.race_start_time.map(|start| start.elapsed())
161    }
162
163    /// Get race start time
164    pub fn get_race_start_time(&self) -> Option<Instant> {
165        self.race_start_time
166    }
167
168    /// Transition to Configuration mode
169    pub fn transition_to_configuration(&mut self) -> Result<()> {
170        match self.current_mode {
171            ApplicationMode::Complete => {
172                self.current_mode = ApplicationMode::Configuration;
173                self.race_start_time = None;
174                self.race_timer_paused = false;
175                self.clear_error_message();
176                self.needs_update = true;
177                Ok(())
178            },
179            ApplicationMode::Configuration => Ok(()), // Already in configuration
180            _ => Err(anyhow!("Cannot transition to Configuration from {:?}", self.current_mode)),
181        }
182    }
183
184    /// Transition to Racing mode
185    pub fn transition_to_racing(&mut self) -> Result<()> {
186        match self.current_mode {
187            ApplicationMode::Configuration => {
188                // Validate configuration first
189                if let Err(e) = self.configuration.validate() {
190                    self.set_error_message(format!("Configuration error: {}", e));
191                    return Err(e);
192                }
193
194                // Only transition if no config focus is active
195                if self.config_focus.is_some() {
196                    return Ok(()); // Ignore transition while in configuration focus
197                }
198
199                self.current_mode = ApplicationMode::Racing;
200                self.race_start_time = Some(Instant::now());
201                self.race_timer_paused = false;
202                self.clear_error_message();
203                self.needs_update = true;
204                Ok(())
205            },
206            ApplicationMode::Paused => {
207                self.current_mode = ApplicationMode::Racing;
208                self.race_timer_paused = false;
209                self.needs_update = true;
210                Ok(())
211            },
212            _ => Err(anyhow!("Cannot transition to Racing from {:?}", self.current_mode)),
213        }
214    }
215
216    /// Transition to Paused mode
217    pub fn transition_to_paused(&mut self) -> Result<()> {
218        match self.current_mode {
219            ApplicationMode::Racing => {
220                self.current_mode = ApplicationMode::Paused;
221                self.race_timer_paused = true;
222                self.needs_update = true;
223                Ok(())
224            },
225            _ => Err(anyhow!("Cannot transition to Paused from {:?}", self.current_mode)),
226        }
227    }
228
229    /// Transition to Complete mode
230    pub fn transition_to_complete(&mut self) -> Result<()> {
231        match self.current_mode {
232            ApplicationMode::Racing | ApplicationMode::Paused => {
233                self.current_mode = ApplicationMode::Complete;
234                self.race_timer_paused = true;
235                self.needs_update = true;
236                Ok(())
237            },
238            _ => Err(anyhow!("Cannot transition to Complete from {:?}", self.current_mode)),
239        }
240    }
241
242    /// Set configuration focus
243    pub fn set_config_focus(&mut self, field: ConfigurationField) -> Result<()> {
244        if self.current_mode == ApplicationMode::Racing {
245            return Err(anyhow!("Cannot change focus while racing"));
246        }
247        self.config_focus = Some(field);
248        self.clear_error_message();
249        self.needs_update = true;
250        Ok(())
251    }
252
253    /// Clear configuration focus (return to main menu)
254    pub fn clear_config_focus(&mut self) {
255        if self.config_focus.is_some() {
256            self.config_focus = None;
257            self.needs_update = true;
258        }
259    }
260
261    /// Toggle help visibility
262    pub fn toggle_help(&mut self) {
263        self.help_visible = !self.help_visible;
264        self.needs_update = true;
265    }
266
267    /// Set array view algorithm
268    pub fn set_array_view_algorithm(&mut self, algorithm_index: usize) {
269        if self.array_view_algorithm != algorithm_index {
270            self.array_view_algorithm = algorithm_index;
271            self.needs_update = true;
272        }
273    }
274
275    /// Cycle to next array view algorithm
276    pub fn cycle_array_view_algorithm(&mut self, algorithm_count: usize) {
277        if algorithm_count > 0 {
278            self.array_view_algorithm = (self.array_view_algorithm + 1) % algorithm_count;
279            self.needs_update = true;
280        }
281    }
282
283    /// Mark display as updated
284    pub fn mark_display_updated(&mut self) {
285        self.needs_update = false;
286    }
287
288    /// Set array size interactively
289    pub fn set_array_size_interactive(&mut self, size: u32) -> Result<()> {
290        self.configuration.set_array_size(size)?;
291        self.clear_error_message();
292        self.needs_update = true;
293        Ok(())
294    }
295
296    /// Attempt to set array size with error handling
297    pub fn attempt_set_array_size(&mut self, size: u32) -> bool {
298        match self.set_array_size_interactive(size) {
299            Ok(()) => true,
300            Err(e) => {
301                self.set_error_message(e.to_string());
302                false
303            }
304        }
305    }
306
307    /// Set distribution interactively
308    pub fn set_distribution_interactive(&mut self, distribution: DistributionType) {
309        self.configuration.distribution = distribution;
310        self.clear_error_message();
311        self.needs_update = true;
312    }
313
314    /// Set fairness mode interactively
315    pub fn set_fairness_mode_interactive(&mut self, mode: crate::models::config::FairnessMode) {
316        self.configuration.set_fairness_mode(mode);
317        self.clear_error_message();
318        self.needs_update = true;
319    }
320
321    /// Set budget parameter
322    pub fn set_budget_parameter(&mut self, budget: u32) -> Result<()> {
323        if budget == 0 {
324            return Err(anyhow!("Budget must be greater than 0"));
325        }
326        self.configuration.budget = Some(budget);
327        self.clear_error_message();
328        self.needs_update = true;
329        Ok(())
330    }
331
332    /// Get help overlay content
333    pub fn get_help_overlay_content(&self) -> String {
334        let mut content = String::new();
335        content.push_str("Keyboard Shortcuts:\n\n");
336        content.push_str("k - Array size configuration\n");
337        content.push_str("b - Distribution configuration\n");
338        content.push_str("f - Fairness mode configuration\n");
339        content.push_str("v - Switch array visualization\n");
340        content.push_str("Space - Start/Pause race\n");
341        content.push_str("? - Toggle help\n");
342        content.push_str("Arrow keys - Navigate menus\n");
343        content.push_str("Enter - Confirm selection\n");
344        content.push_str("q - Quit\n");
345        content
346    }
347}
348
349impl Default for InteractiveMode {
350    fn default() -> Self {
351        Self::new()
352    }
353}
354
355#[cfg(test)]
356mod tests {
357    use super::*;
358
359    #[test]
360    fn test_interactive_mode_creation() {
361        let mode = InteractiveMode::new();
362        assert_eq!(mode.current_mode, ApplicationMode::Configuration);
363        assert_eq!(mode.config_focus, None);
364        assert_eq!(mode.array_view_algorithm, 0);
365        assert!(!mode.help_visible);
366        assert!(!mode.is_error_message_visible());
367    }
368
369    #[test]
370    fn test_application_mode_transitions() {
371        let mut mode = InteractiveMode::new();
372        
373        // Configuration -> Racing
374        assert!(mode.transition_to_racing().is_ok());
375        assert_eq!(mode.current_mode, ApplicationMode::Racing);
376        assert!(mode.is_race_timer_active());
377        
378        // Racing -> Paused
379        assert!(mode.transition_to_paused().is_ok());
380        assert_eq!(mode.current_mode, ApplicationMode::Paused);
381        assert!(mode.is_race_timer_paused());
382        
383        // Paused -> Racing
384        assert!(mode.transition_to_racing().is_ok());
385        assert_eq!(mode.current_mode, ApplicationMode::Racing);
386        assert!(mode.is_race_timer_active());
387        
388        // Racing -> Complete
389        assert!(mode.transition_to_complete().is_ok());
390        assert_eq!(mode.current_mode, ApplicationMode::Complete);
391        
392        // Complete -> Configuration
393        assert!(mode.transition_to_configuration().is_ok());
394        assert_eq!(mode.current_mode, ApplicationMode::Configuration);
395    }
396
397    #[test]
398    fn test_configuration_focus() {
399        let mut mode = InteractiveMode::new();
400        
401        assert!(mode.set_config_focus(ConfigurationField::ArraySize).is_ok());
402        assert_eq!(mode.config_focus, Some(ConfigurationField::ArraySize));
403        assert!(mode.is_menu_visible());
404        
405        mode.clear_config_focus();
406        assert_eq!(mode.config_focus, None);
407        assert!(!mode.is_menu_visible());
408    }
409
410    #[test]
411    fn test_focus_blocked_during_racing() {
412        let mut mode = InteractiveMode::new();
413        
414        mode.transition_to_racing().unwrap();
415        assert!(mode.set_config_focus(ConfigurationField::ArraySize).is_err());
416    }
417
418    #[test]
419    fn test_help_toggle() {
420        let mut mode = InteractiveMode::new();
421        
422        assert!(!mode.should_show_help_overlay());
423        
424        mode.toggle_help();
425        assert!(mode.should_show_help_overlay());
426        
427        mode.toggle_help();
428        assert!(!mode.should_show_help_overlay());
429    }
430
431    #[test]
432    fn test_array_view_cycling() {
433        let mut mode = InteractiveMode::new();
434        
435        assert_eq!(mode.array_view_algorithm, 0);
436        
437        mode.cycle_array_view_algorithm(7);
438        assert_eq!(mode.array_view_algorithm, 1);
439        
440        mode.set_array_view_algorithm(6);
441        mode.cycle_array_view_algorithm(7);
442        assert_eq!(mode.array_view_algorithm, 0); // Should wrap around
443    }
444
445    #[test]
446    fn test_error_handling() {
447        let mut mode = InteractiveMode::new();
448        
449        assert!(!mode.is_error_message_visible());
450        
451        mode.set_error_message("Test error".to_string());
452        assert!(mode.is_error_message_visible());
453        assert_eq!(mode.get_error_message(), Some("Test error"));
454        
455        mode.clear_error_message();
456        assert!(!mode.is_error_message_visible());
457        assert_eq!(mode.get_error_message(), None);
458    }
459
460    #[test]
461    fn test_array_size_validation() {
462        let mut mode = InteractiveMode::new();
463        
464        assert!(mode.set_array_size_interactive(100).is_ok());
465        assert!(!mode.attempt_set_array_size(5)); // Too small
466        assert!(mode.is_error_message_visible());
467        
468        mode.clear_error_message();
469        assert!(mode.attempt_set_array_size(500)); // Valid
470        assert!(!mode.is_error_message_visible());
471    }
472
473    #[test]
474    fn test_help_content() {
475        let mode = InteractiveMode::new();
476        let help_content = mode.get_help_overlay_content();
477        
478        assert!(help_content.contains("k - Array size configuration"));
479        assert!(help_content.contains("b - Distribution configuration"));
480        assert!(help_content.contains("f - Fairness mode configuration"));
481        assert!(help_content.contains("v - Switch array visualization"));
482        assert!(help_content.contains("Space - Start/Pause race"));
483        assert!(help_content.contains("? - Toggle help"));
484        assert!(help_content.contains("q - Quit"));
485    }
486}