1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
//! Input Utilities
//!
//! This module provides functions for handling user input in console applications, including reading user input,
//! selecting options from a list, displaying spinners, and gradually revealing strings.
use std::{io, str::FromStr, thread, time::Duration};

use crate::{
    control::*,
    read::{read_key, Key},
};

/// A Wrapper for empty inputs returning a None
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, Default)]
pub enum Empty<T> {
    Some(T),
    #[default]
    None,
}

impl<T> FromStr for Empty<T>
where
    T: FromStr,
    T::Err: std::fmt::Debug,
{
    type Err = T::Err;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.trim().is_empty() {
            Ok(Empty::None)
        } else {
            s.trim().parse::<T>().map(Empty::Some)
        }
    }
}

/// Reads user input from the console.
///
/// This function prompts the user with a message (`before`) and reads a line of input from the
/// console. The input can be empty.
///
/// # Arguments
///
/// * `before` - The text to display before prompting for input. Add here `\n` for a new line.
///
/// # Returns
///
/// Returns an `T` containing the user's input converted to the specified type.
pub fn input<T>(before: &str) -> T
where
    T: std::str::FromStr,
    T::Err: std::fmt::Debug,
{
    loop {
        print!("\x1b[31m?\x1b[0m {before} \x1b[90m›\x1b[0m ");
        flush();

        let mut cli = String::new();
        io::stdin().read_line(&mut cli).unwrap();

        match cli.parse() {
            Ok(value) => return value,
            Err(_) => println!("\nInvalid Input Type\n"),
        }
    }
}

/// Allows the user to select one option from a list using the console.
///
/// This function displays a list of options. The user can navigate through the
/// options using arrow keys or 'w' and 's' keys. If the user presses Enter, the
/// function returns the selected option.
///
/// # Arguments
///
/// * `before` - The text to display before the list of options.
/// * `options` - A vector of strings representing the available options.
///
/// # Returns
///
/// Returns an `usize` as an index of the inputted array `options`
pub fn select<'a>(before: &'a str, options: &'a [&'a str]) -> usize {
    let mut i = 0;

    // print everything
    println!("\x1b[31m?\x1b[0m {before} \x1b[90m›\x1b[0m ");

    populate(options, None, 0);

    // hide cursor
    Visibility::hide_cursor();

    loop {
        if let Ok(character) = read_key() {
            match character {
                Key::ArrowUp | Key::Char('w') | Key::Char('W') => {
                    if i > 0 {
                        i -= 1;
                        populate(options, None, i);
                    }
                }
                Key::ArrowDown | Key::Char('s') | Key::Char('S') => {
                    if i < options.len() - 1 {
                        i += 1;
                        populate(options, None, i);
                    }
                }
                Key::Enter => {
                    break;
                }
                _ => {}
            }
        }
    }

    // reset cursor
    Visibility::show_cursor();
    move_cursor_down(options.len());

    i
}

/// Allows the user to select multiple options from a list using the console.
///
/// This function displays a list of options with checkboxes. The user can navigate through the
/// options using arrow keys or 'w' and 's' keys. Pressing the spacebar toggles the selection of
/// the current option. If the user presses Enter, the function returns a vector of booleans
/// indicating which options were selected.
///
/// # Arguments
///
/// * `before` - The text to display before the list of options.
/// * `options` - A vector of strings representing the available options.
///
/// # Returns
///
/// Returns an `Vec<bool>` containing a vector of booleans indicating which options were
/// selected.
pub fn multiselect(before: &str, options: &[&str]) -> Vec<bool> {
    let mut matrix: Vec<bool> = vec![false; options.len()];
    let mut i = 0;

    // print everything
    println!("\x1b[31m?\x1b[0m {before} \x1b[90m›\x1b[0m ");

    populate(options, Some(&matrix), 0);

    // hide cursor
    Visibility::hide_cursor();

    loop {
        if let Ok(character) = read_key() {
            match character {
                Key::ArrowUp | Key::Char('w') | Key::Char('W') => {
                    if i > 0 {
                        i -= 1;
                        populate(options, Some(&matrix), i);
                    }
                }
                Key::ArrowDown | Key::Char('s') | Key::Char('S') => {
                    if i < options.len() - 1 {
                        i += 1;
                        populate(options, Some(&matrix), i);
                    }
                }
                Key::Char(' ') => {
                    move_cursor_down(i);
                    clear_line();
                    matrix[i] = !matrix[i];
                    flush();
                    move_cursor_up(i);
                    populate(options, Some(&matrix), i);
                }
                Key::Enter => {
                    break;
                }
                _ => {}
            }
        }
    }

    // reset cursor
    Visibility::show_cursor();
    move_cursor_down(options.len());

    matrix
}

/// Populate function for multiselect
fn populate(options: &[&str], matrix: Option<&[bool]>, cursor: usize) {
    for (i, option) in options.iter().enumerate() {
        clear_line();
        if i == cursor {
            println!(
                "\x1b[36m ›\x1b[0m\x1b[3{}m {}\x1b[0m",
                if matrix.is_some() && matrix.unwrap()[i] {
                    "2"
                } else {
                    "6"
                },
                option
            );
        } else if matrix.is_some() && matrix.unwrap()[i] {
            println!("\x1b[32m   {}\x1b[0m", option);
        } else {
            println!("   {}", option);
        }
    }
    move_cursor_up(options.len());
}

/// Enumeration representing different types of spinners.
#[derive(Debug, Clone)]
pub enum SpinnerType {
    Standard,
    Dots,
    Box,
    Flip,
    Custom(Vec<&'static str>),
}

impl SpinnerType {
    /// Converts the spinner type to a vector of frames, gives back the following variants:
    ///  - `SpinnerType::Standard`: Standard spinner with characters / - \ |.
    ///  - `SpinnerType::Dots`: Spinner with dots . .. ... .....
    ///  - `SpinnerType::Box`: Spinner with box characters ▌ ▀ ▐ ▄.
    ///  - `SpinnerType::Flip`: Spinner with flip characters _ _ _ - \ ' ´ - _ _ _.
    ///  - `SpinnerType::Custom(frames)`: Custom spinner with user-defined frames.
    pub fn to_frames(&self) -> Vec<&'static str> {
        match self {
            SpinnerType::Standard => vec!["/", "-", "\\", "|"],
            SpinnerType::Dots => vec![".", "..", "...", "....", "...", ".."],
            SpinnerType::Box => vec!["▌", "▀", "▐", "▄"],
            SpinnerType::Flip => vec!["_", "_", "_", "-", "`", "`", "'", "´", "-", "_", "_", "_"],
            SpinnerType::Custom(frames) => frames.to_owned(),
        }
    }
}

/// Displays a console-based spinner animation.
///
/// A spinner is a visual indicator of a long-running process. It consists of a set of frames
/// that are displayed sequentially to create the appearance of motion.
///
/// # Parameters
///
/// - `time`: A floating-point number representing the duration of the spinner animation in seconds.
/// - `spinner_type`: The type of spinner to display.
pub fn spinner(mut time: f64, spinner_type: SpinnerType) {
    let frames = spinner_type.to_frames();
    let mut i = 0;

    while time > 0.0 {
        clear_line();
        print!("{}", frames[i]);
        flush();
        thread::sleep(Duration::from_secs_f64(0.075));
        time -= 0.075;
        if i < frames.len() - 1 {
            i += 1
        } else {
            i = 0
        }
    }

    clear_line();
}

/// Reveals a string gradually, printing one character at a time with a specified time interval.
///
/// This function is useful for creating a typing effect or slowly displaying information to the user.
///
/// # Arguments
///
/// * `str` - The string to reveal gradually. Add here `\n` for a new line.
/// * `time_between` - The time interval (in seconds) between each revealed character.
pub fn reveal(str: &str, time_between: f64) {
    for i in 0..str.len() {
        print!("{}", str.chars().nth(i).unwrap_or(' '));
        flush();
        thread::sleep(Duration::from_secs_f64(time_between));
    }
}