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
use crate::STORE_MUTEX;
use clap::{arg, Command};
use std::io::Write;

/// Runs the REPL loop, reading user input and responding accordingly.
///
/// # Returns
/// * `Ok(())` if the REPL exits successfully.
/// * `Err(String)` if an error occurs during execution.
pub fn run() -> Result<(), String> {
    loop {
        let line: String = readline()?;
        let line: &str = line.trim();
        if line.is_empty() {
            continue;
        }

        match respond(line) {
            Ok(quit) => {
                if quit {
                    break;
                }
            }
            Err(err) => {
                write!(std::io::stdout(), "{err}").map_err(|e| e.to_string())?;
                std::io::stdout().flush().map_err(|e| e.to_string())?;
            }
        }
    }

    Ok(())
}

/// Processes the user input and executes the corresponding command.
///
/// # Arguments
/// * `line` - The input line entered by the user.
///
/// # Returns
/// * `Ok(bool)` - A boolean indicating whether to quit the REPL.
/// * `Err(String)` - An error message if the input processing fails.
fn respond(line: &str) -> Result<bool, String> {
    let args = shlex::split(line).ok_or("error: Invalid quoting")?;
    let matches = cli()
        .try_get_matches_from(args)
        .map_err(|e| e.to_string())?;

    let mut store: std::sync::MutexGuard<crate::Store> = STORE_MUTEX.lock().unwrap();

    match matches.subcommand() {
        Some(("insert", sub_matches)) => {
            // Handle the 'insert' command to add a new key-value pair to the store
            let key: &str = sub_matches
                .get_one::<String>("key")
                .map(|s| s.as_str())
                .unwrap();

            let value: &str = sub_matches
                .get_one::<String>("value")
                .map(|s| s.as_str())
                .unwrap();

            match store.insert(key, value) {
                Ok(_) => println!("Inserted entry {{'{key}': '{value}'}}"),
                Err(e) => println!("Error {}", e),
            }
        }

        Some(("get", sub_matches)) => {
            // Handle the 'get' command to retrieve a value by its key from the store
            let key = sub_matches
                .get_one::<String>("key")
                .map(|s| s.as_str())
                .unwrap();

            match store.get(key) {
                Ok(pair) => println!("Entry: {{\"{key}\" : \"{}\"}}", pair.value),
                Err(e) => println!("Error: {}", e),
            }
        }

        Some(("update", sub_matches)) => {
            // Handle the 'update' command to modify the value associated with a key
            let key: &str = sub_matches
                .get_one::<String>("key")
                .map(|s| s.as_str())
                .unwrap();

            let value: &str = sub_matches
                .get_one::<String>("value")
                .map(|s| s.as_str())
                .unwrap();

            match store.update(key, value) {
                Ok(_) => println!("Updated entry {{'{key}' : '{value}'}}"),
                Err(e) => println!("Error {}", e),
            }
        }

        Some(("delete", sub_matches)) => {
            // Handle the 'delete' command to remove a key-value pair from the store
            let key: &str = sub_matches
                .get_one::<String>("key")
                .map(|s| s.as_str())
                .unwrap();

            store.delete(key);
            println!("Entry deleted successfully")
        }
        Some(("quit", _matches)) => {
            // Handle the 'quit' command to exit the REPL
            write!(std::io::stdout(), "Exiting ...").map_err(|e| e.to_string())?;
            std::io::stdout().flush().map_err(|e| e.to_string())?;
            return Ok(true);
        }
        Some((name, _matches)) => {
            // Handle any unimplemented commands
            println!("Method [{name}] Not implmeneted")
        }
        // This case should never occur because `subcommand_required(true)` ensures a subcommand is always provided.
        None => unreachable!("subcommand required"),
    }

    Ok(false)
}

/// Defines the command-line interface using Clap.
///
/// # Returns
/// A `Command` instance representing the CLI structure.
fn cli() -> Command {
    Command::new("safina_db")
        .version("1.0.0")
        .multicall(true)
        .about("'Safina DB' a persistent Key-Value store")
        .subcommand_required(true)
        .arg_required_else_help(true)
        .allow_external_subcommands(true)
        .subcommand(
            Command::new("insert")
                .about("Inserts a new entry")
                .arg_required_else_help(true)
                .arg(arg!(key: [KEY]).required(true))
                .arg(arg!(value: [VALUE]).required(true)),
        )
        .subcommand(
            Command::new("get")
                .about("get entry value by key")
                .arg_required_else_help(true)
                .arg(arg!(key: [KEY]).required(true)),
        )
        .subcommand(
            Command::new("delete")
                .about("delete entry value by key")
                .arg_required_else_help(true)
                .arg(arg!(key: [KEY]).required(true)),
        )
        .subcommand(
            Command::new("update")
                .about("update entry value")
                .arg_required_else_help(true)
                .arg(arg!(key: [KEY]).required(true))
                .arg(arg!(value: [VALUE]).required(true)),
        )
        .subcommand(
            Command::new("quit")
                .alias("exit")
                .alias("by")
                .about("Quit the REPL"),
        )
}

/// Reads a line of input from the user.
///
/// # Returns
/// * `Ok(String)` - The input line entered by the user.
/// * `Err(String)` - An error message if reading input fails.
fn readline() -> Result<String, String> {
    write!(std::io::stdout(), "\n(safinaDB) ➜ ").map_err(|e| e.to_string())?;
    std::io::stdout().flush().map_err(|e| e.to_string())?;
    let mut buffer = String::new();
    std::io::stdin()
        .read_line(&mut buffer)
        .map_err(|e| e.to_string())?;
    Ok(buffer)
}