sorting_race/models/
display_mode.rs

1//! Display mode for controlling array visualization
2
3use crate::models::{
4    session::AlgorithmType,
5    traits::Sorter,
6};
7use anyhow::{Result, anyhow};
8use crossterm::event::{KeyEvent, KeyCode, KeyModifiers};
9/// Controls which algorithm's array visualization is shown
10#[derive(Debug, Clone)]
11pub struct DisplayMode {
12    /// Currently viewed algorithm
13    pub viewed_algorithm: AlgorithmType,
14    /// Available algorithms in current race
15    pub available_algorithms: Vec<AlgorithmType>,
16    /// Current index in the available algorithms list
17    pub cycle_index: usize,
18    /// Whether visualization needs update
19    needs_update: bool,
20}
21
22impl DisplayMode {
23    /// Create a new display mode
24    pub fn new() -> Self {
25        let available_algorithms = AlgorithmType::all();
26        Self {
27            viewed_algorithm: available_algorithms[0], // Default to first algorithm (BubbleSort)
28            available_algorithms,
29            cycle_index: 0,
30            needs_update: true,
31        }
32    }
33
34    /// Create display mode with specific algorithm set
35    pub fn with_algorithms(algorithms: Vec<AlgorithmType>) -> Result<Self> {
36        if algorithms.is_empty() {
37            return Err(anyhow!("Cannot create DisplayMode with empty algorithm list"));
38        }
39
40        Ok(Self {
41            viewed_algorithm: algorithms[0],
42            available_algorithms: algorithms,
43            cycle_index: 0,
44            needs_update: true,
45        })
46    }
47
48    /// Handle visualization switch key event
49    pub fn handle_visualization_switch(&mut self, _key_event: KeyEvent) -> Result<()> {
50        self.cycle_to_next_algorithm();
51        Ok(())
52    }
53
54    /// Cycle to the next algorithm
55    pub fn cycle_to_next_algorithm(&mut self) {
56        if !self.available_algorithms.is_empty() {
57            self.cycle_index = (self.cycle_index + 1) % self.available_algorithms.len();
58            self.viewed_algorithm = self.available_algorithms[self.cycle_index];
59            self.needs_update = true;
60        }
61    }
62
63    /// Set viewed algorithm by type
64    pub fn set_viewed_algorithm(&mut self, algorithm_type: AlgorithmType) -> Result<()> {
65        if let Some(index) = self.available_algorithms.iter().position(|&alg| alg == algorithm_type) {
66            self.cycle_index = index;
67            self.viewed_algorithm = algorithm_type;
68            self.needs_update = true;
69            Ok(())
70        } else {
71            Err(anyhow!("Algorithm {:?} is not available in current race", algorithm_type))
72        }
73    }
74
75    /// Set viewed algorithm by index
76    pub fn set_viewed_algorithm_by_index(&mut self, index: usize) -> Result<()> {
77        if index < self.available_algorithms.len() {
78            self.cycle_index = index;
79            self.viewed_algorithm = self.available_algorithms[index];
80            self.needs_update = true;
81            Ok(())
82        } else {
83            Err(anyhow!("Algorithm index {} is out of bounds (max: {})", index, self.available_algorithms.len() - 1))
84        }
85    }
86
87    /// Check if visualization should be updated
88    pub fn should_update_visualization(&self) -> bool {
89        self.needs_update
90    }
91
92    /// Mark visualization as updated
93    pub fn mark_visualization_updated(&mut self) {
94        self.needs_update = false;
95    }
96
97    /// Get current array data from algorithm instances
98    pub fn get_current_array_data<'a>(&self, algorithms: &'a [Box<dyn Sorter>]) -> Option<&'a [i32]> {
99        // Find the algorithm that matches our viewed algorithm
100        for (i, algorithm) in algorithms.iter().enumerate() {
101            if let Some(expected_type) = AlgorithmType::from_index(i)
102                && expected_type == self.viewed_algorithm {
103                    return Some(algorithm.get_array());
104                }
105        }
106        None
107    }
108
109    /// Get array source algorithm name
110    pub fn get_array_source_algorithm(&self) -> String {
111        self.viewed_algorithm.to_string()
112    }
113
114    /// Get currently highlighted algorithm
115    pub fn get_highlighted_algorithm(&self) -> AlgorithmType {
116        self.viewed_algorithm
117    }
118
119    /// Check if specific algorithm is highlighted
120    pub fn is_algorithm_highlighted(&self, algorithm_type: AlgorithmType) -> bool {
121        self.viewed_algorithm == algorithm_type
122    }
123
124    /// Get current algorithm index
125    pub fn get_current_algorithm_index(&self) -> usize {
126        self.cycle_index
127    }
128
129    /// Get total number of available algorithms
130    pub fn get_algorithm_count(&self) -> usize {
131        self.available_algorithms.len()
132    }
133
134    /// Update available algorithms (e.g., when some algorithms fail)
135    pub fn update_available_algorithms(&mut self, algorithms: Vec<AlgorithmType>) -> Result<()> {
136        if algorithms.is_empty() {
137            return Err(anyhow!("Cannot update to empty algorithm list"));
138        }
139
140        self.available_algorithms = algorithms;
141        
142        // Ensure current algorithm is still available
143        if !self.available_algorithms.contains(&self.viewed_algorithm) {
144            self.viewed_algorithm = self.available_algorithms[0];
145            self.cycle_index = 0;
146            self.needs_update = true;
147        } else {
148            // Update cycle index to match current algorithm
149            self.cycle_index = self.available_algorithms
150                .iter()
151                .position(|&alg| alg == self.viewed_algorithm)
152                .unwrap_or(0);
153        }
154
155        Ok(())
156    }
157
158    /// Reset to first algorithm
159    pub fn reset_to_first(&mut self) {
160        if !self.available_algorithms.is_empty() {
161            self.cycle_index = 0;
162            self.viewed_algorithm = self.available_algorithms[0];
163            self.needs_update = true;
164        }
165    }
166
167    /// Get algorithm at specific index
168    pub fn get_algorithm_at_index(&self, index: usize) -> Option<AlgorithmType> {
169        self.available_algorithms.get(index).copied()
170    }
171
172    /// Check if we can cycle to next algorithm
173    pub fn can_cycle_next(&self) -> bool {
174        self.available_algorithms.len() > 1
175    }
176
177    /// Get next algorithm in cycle (without changing current)
178    pub fn peek_next_algorithm(&self) -> Option<AlgorithmType> {
179        if self.available_algorithms.is_empty() {
180            None
181        } else {
182            let next_index = (self.cycle_index + 1) % self.available_algorithms.len();
183            self.available_algorithms.get(next_index).copied()
184        }
185    }
186
187    /// Get previous algorithm in cycle (without changing current)
188    pub fn peek_previous_algorithm(&self) -> Option<AlgorithmType> {
189        if self.available_algorithms.is_empty() {
190            None
191        } else {
192            let prev_index = if self.cycle_index == 0 {
193                self.available_algorithms.len() - 1
194            } else {
195                self.cycle_index - 1
196            };
197            self.available_algorithms.get(prev_index).copied()
198        }
199    }
200
201    /// Create display mode from algorithm instances
202    pub fn from_algorithms(algorithms: &[Box<dyn Sorter>]) -> Self {
203        let available_algorithms = (0..algorithms.len())
204            .filter_map(AlgorithmType::from_index)
205            .collect();
206        
207        Self::with_algorithms(available_algorithms).unwrap_or_default()
208    }
209
210    /// Process visualization key event
211    pub fn process_key_event(&mut self, key_event: KeyEvent) -> Result<bool> {
212        match key_event {
213            KeyEvent {
214                code: KeyCode::Char('v'),
215                modifiers: KeyModifiers::NONE,
216                ..
217            } => {
218                self.handle_visualization_switch(key_event)?;
219                Ok(true) // Event was handled
220            },
221            _ => Ok(false), // Event not handled
222        }
223    }
224}
225
226impl Default for DisplayMode {
227    fn default() -> Self {
228        Self::new()
229    }
230}
231
232/// Memory display value for algorithms
233#[derive(Debug, Clone, PartialEq)]
234pub enum MemoryDisplayValue {
235    /// Actual byte value
236    Bytes(usize),
237    /// Not available (algorithm inactive or error)
238    NotAvailable,
239}
240
241impl MemoryDisplayValue {
242    /// Format as human-readable string
243    pub fn as_string(&self) -> String {
244        match self {
245            MemoryDisplayValue::Bytes(bytes) => {
246                Self::format_bytes(*bytes)
247            },
248            MemoryDisplayValue::NotAvailable => "N/A".to_string(),
249        }
250    }
251
252    /// Format bytes into human-readable string
253    fn format_bytes(bytes: usize) -> String {
254        const UNITS: &[&str] = &["B", "KB", "MB", "GB"];
255        let mut size = bytes as f64;
256        let mut unit_index = 0;
257
258        while size >= 1024.0 && unit_index < UNITS.len() - 1 {
259            size /= 1024.0;
260            unit_index += 1;
261        }
262
263        if unit_index == 0 {
264            format!("{}B", bytes)
265        } else {
266            format!("{:.1}{}", size, UNITS[unit_index])
267        }
268    }
269}
270
271impl std::fmt::Display for MemoryDisplayValue {
272    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273        write!(f, "{}", self.as_string())
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280    use crossterm::event::KeyEventKind;
281
282    #[test]
283    fn test_display_mode_creation() {
284        let display = DisplayMode::new();
285        
286        assert_eq!(display.viewed_algorithm, AlgorithmType::BubbleSort);
287        assert_eq!(display.available_algorithms.len(), 7);
288        assert_eq!(display.cycle_index, 0);
289        assert!(display.should_update_visualization());
290    }
291
292    #[test]
293    fn test_algorithm_cycling() {
294        let mut display = DisplayMode::new();
295        
296        // Should start with BubbleSort
297        assert_eq!(display.viewed_algorithm, AlgorithmType::BubbleSort);
298        
299        // Cycle to next
300        display.cycle_to_next_algorithm();
301        assert_eq!(display.viewed_algorithm, AlgorithmType::SelectionSort);
302        assert_eq!(display.cycle_index, 1);
303        
304        // Cycle through all and wrap around
305        for _ in 0..6 {
306            display.cycle_to_next_algorithm();
307        }
308        assert_eq!(display.viewed_algorithm, AlgorithmType::BubbleSort);
309        assert_eq!(display.cycle_index, 0);
310    }
311
312    #[test]
313    fn test_set_viewed_algorithm() {
314        let mut display = DisplayMode::new();
315        
316        // Set to specific algorithm
317        assert!(display.set_viewed_algorithm(AlgorithmType::QuickSort).is_ok());
318        assert_eq!(display.viewed_algorithm, AlgorithmType::QuickSort);
319        assert_eq!(display.cycle_index, 4);
320        
321        // Try to set unavailable algorithm (should fail for this test, but normally wouldn't)
322        let limited_algorithms = vec![AlgorithmType::BubbleSort, AlgorithmType::QuickSort];
323        let mut limited_display = DisplayMode::with_algorithms(limited_algorithms).unwrap();
324        assert!(limited_display.set_viewed_algorithm(AlgorithmType::ShellSort).is_err());
325    }
326
327    #[test]
328    fn test_algorithm_index_operations() {
329        let mut display = DisplayMode::new();
330        
331        // Set by index
332        assert!(display.set_viewed_algorithm_by_index(3).is_ok());
333        assert_eq!(display.viewed_algorithm, AlgorithmType::MergeSort);
334        
335        // Invalid index
336        assert!(display.set_viewed_algorithm_by_index(10).is_err());
337        
338        // Get algorithm at index
339        assert_eq!(display.get_algorithm_at_index(2), Some(AlgorithmType::InsertionSort));
340        assert_eq!(display.get_algorithm_at_index(10), None);
341    }
342
343    #[test]
344    fn test_peek_operations() {
345        let mut display = DisplayMode::new();
346        display.set_viewed_algorithm_by_index(2).unwrap(); // InsertionSort
347        
348        assert_eq!(display.peek_next_algorithm(), Some(AlgorithmType::MergeSort));
349        assert_eq!(display.peek_previous_algorithm(), Some(AlgorithmType::SelectionSort));
350        
351        // Test wrap-around
352        display.set_viewed_algorithm_by_index(0).unwrap(); // BubbleSort
353        assert_eq!(display.peek_previous_algorithm(), Some(AlgorithmType::ShellSort));
354        
355        display.set_viewed_algorithm_by_index(6).unwrap(); // ShellSort
356        assert_eq!(display.peek_next_algorithm(), Some(AlgorithmType::BubbleSort));
357    }
358
359    #[test]
360    fn test_key_event_processing() {
361        let mut display = DisplayMode::new();
362        
363        let v_key = KeyEvent {
364            code: KeyCode::Char('v'),
365            modifiers: KeyModifiers::NONE,
366            kind: KeyEventKind::Press,
367            state: crossterm::event::KeyEventState::empty(),
368        };
369        
370        let initial_algorithm = display.viewed_algorithm;
371        let handled = display.process_key_event(v_key).unwrap();
372        
373        assert!(handled);
374        assert_ne!(display.viewed_algorithm, initial_algorithm);
375        
376        // Test unhandled key
377        let other_key = KeyEvent {
378            code: KeyCode::Char('x'),
379            modifiers: KeyModifiers::NONE,
380            kind: KeyEventKind::Press,
381            state: crossterm::event::KeyEventState::empty(),
382        };
383        
384        let handled = display.process_key_event(other_key).unwrap();
385        assert!(!handled);
386    }
387
388    #[test]
389    fn test_available_algorithms_update() {
390        let mut display = DisplayMode::new();
391        
392        // Update to limited set
393        let limited = vec![AlgorithmType::BubbleSort, AlgorithmType::QuickSort, AlgorithmType::HeapSort];
394        assert!(display.update_available_algorithms(limited.clone()).is_ok());
395        
396        assert_eq!(display.available_algorithms, limited);
397        assert_eq!(display.get_algorithm_count(), 3);
398        
399        // Empty list should fail
400        assert!(display.update_available_algorithms(vec![]).is_err());
401    }
402
403    #[test]
404    fn test_memory_display_value() {
405        let value_bytes = MemoryDisplayValue::Bytes(1536);
406        assert_eq!(value_bytes.to_string(), "1.5KB");
407        
408        let value_na = MemoryDisplayValue::NotAvailable;
409        assert_eq!(value_na.to_string(), "N/A");
410        
411        // Test byte formatting
412        assert_eq!(MemoryDisplayValue::Bytes(512).to_string(), "512B");
413        assert_eq!(MemoryDisplayValue::Bytes(1024).to_string(), "1.0KB");
414        assert_eq!(MemoryDisplayValue::Bytes(1048576).to_string(), "1.0MB");
415    }
416
417    #[test]
418    fn test_algorithm_highlighting() {
419        let mut display = DisplayMode::new();
420        display.set_viewed_algorithm(AlgorithmType::QuickSort).unwrap();
421        
422        assert!(display.is_algorithm_highlighted(AlgorithmType::QuickSort));
423        assert!(!display.is_algorithm_highlighted(AlgorithmType::BubbleSort));
424        assert_eq!(display.get_highlighted_algorithm(), AlgorithmType::QuickSort);
425    }
426
427    #[test]
428    fn test_reset_to_first() {
429        let mut display = DisplayMode::new();
430        display.cycle_to_next_algorithm(); // Move away from first
431        display.cycle_to_next_algorithm();
432        
433        assert_ne!(display.viewed_algorithm, AlgorithmType::BubbleSort);
434        
435        display.reset_to_first();
436        assert_eq!(display.viewed_algorithm, AlgorithmType::BubbleSort);
437        assert_eq!(display.cycle_index, 0);
438    }
439}