use colored::{ColoredString, Colorize};
use std::io::{self, Write};
use std::process::Command;
use anyhow::Result;
use chrono::Utc;
use errors::TErrors;
use todos::Priority;
use todos::Todo;
use crate::sorting::sorting_menu;
pub mod commands;
pub mod errors;
pub mod persistence;
pub mod sorting;
pub mod todos;
pub fn user_input() -> Result<String> {
let mut buffer = String::new();
io::stdout().flush().map_err(TErrors::ReadUserInput)?;
io::stdin()
.read_line(&mut buffer)
.map_err(TErrors::ReadUserInput)?;
Ok(buffer)
}
pub fn main_menu() {
println!(
"\n{:>5} {}\n{:>5} {}\n{:>5} {}\n{:>5} {}\n{:>5} {}\n{:>5} {}\n{:>5} {}\n",
"[1]".blue(),
"Show Todos".yellow(),
"[2]".blue(),
"Add Todos".yellow(),
"[3]".blue(),
"Update Todos".yellow(),
"[4]".blue(),
"Remove Todos".yellow(),
"[5]".blue(),
"Import Todos".yellow(),
"[6]".blue(),
"Export Todos".yellow(),
"[7]".blue(),
"Exit".yellow(),
);
}
pub fn sub_menu(todos: &mut Vec<Todo>) -> Result<bool> {
print!(
"\n\n{}{:^4} {}{:^3} {}{:^5} {}{:^3} {}{:^3} {}{:^5} {}{:^5} {}",
"[1]".blue().bold(),
"Menu".white().bold(),
"[2]".blue().bold(),
"New".white().bold(),
"[3]".blue().bold(),
"Remove".white().bold(),
"[4]".blue().bold(),
"Done".white().bold(),
"[5]".blue().bold(),
"Undone".white().bold(),
"[6]".blue().bold(),
"Priority".white().bold(),
"[7]".blue().bold(),
"Sort".white().bold(),
"-> ".green().bold(),
);
let id = user_input()
.map_err(|e| TErrors::SubMenu(e.to_string()))?
.trim()
.to_string();
if id.contains('1') {
show_todos(todos);
Ok(false)
} else if id.contains('2') {
add_todo(todos)?;
Ok(true)
} else if id.contains('3') {
remove_todo(todos)?;
Ok(true)
} else if id.contains('4') {
set_done(todos)?;
Ok(true)
} else if id.contains('5') {
set_undone(todos)?;
Ok(true)
} else if id.contains('6') {
set_priority(todos)?;
Ok(true)
} else if id.contains('7') {
sorting_menu(todos)?;
Ok(true)
} else {
Ok(false)
}
}
pub fn add_todo(td: &mut Vec<Todo>) -> Result<()> {
print!("{}{}", "Title ".white().bold(), "-> ".green().bold());
let title = user_input()
.map_err(|e| TErrors::AddTodo(e.to_string()))?
.trim()
.to_string();
print!("{}{}", "Description ".white().bold(), "-> ".green().bold());
let desc = user_input()
.map_err(|e| TErrors::AddTodo(e.to_string()))?
.trim()
.to_string();
td.push(Todo::new(
title,
desc,
false,
Priority::LOW,
Utc::now().to_rfc2822(),
));
Ok(())
}
pub fn show_todos(todos: &mut [Todo]) {
Command::new("clear").status().unwrap();
println!(
"{:^10} {:^40} {:^40} {:^10} {:^32}",
"ID".blue().bold(),
"Title".blue().bold(),
"Description".blue().bold(),
"Priority".blue().bold(),
"Creation Date".blue().bold(),
);
println!(
"{:-^10} {:-^40} {:-^40} {:-^10} {:-^32}",
"".blue().bold(),
"".blue().bold(),
"".blue().bold(),
"".blue().bold(),
"".blue().bold(),
);
for (i, x) in todos.iter().enumerate() {
if x.done.eq(&true) {
println!(
"{:^10} {:^40} {:^40} {:^10} {:^32}",
i.to_string().strikethrough().red().bold(),
x.title.strikethrough().red().bold(),
x.description.strikethrough().red().bold(),
(get_priority(&x.time).strikethrough().red()),
x.date
.split('+')
.next()
.unwrap_or_default()
.strikethrough()
.red(),
);
} else {
println!(
"{:^10} {:^40} {:^40} {:^10} {:^32}",
i.to_string().blue().bold(),
x.title.cyan().bold(),
x.description.yellow().bold(),
(get_priority(&x.time)),
x.date
.split('+')
.next()
.unwrap_or_default()
.magenta()
.bold()
);
}
}
}
pub fn remove_todo(todos: &mut Vec<Todo>) -> Result<()> {
print!(
"\n{} {}{}",
"ID".red().bold(),
"to remove ".white().bold(),
"-> ".green().bold()
);
let rm = user_input()
.map_err(|e| TErrors::RemoveTodo(e.to_string()))?
.trim()
.to_string()
.parse::<usize>()
.map_err(|_| TErrors::ParseID)?;
if rm < todos.len() && !todos.is_empty() {
todos.remove(rm);
};
Ok(())
}
pub fn update_todo(todos: &mut [Todo]) -> Result<()> {
print!(
"\n{} {}{}",
"ID".red().bold(),
" to update ".white().bold(),
"-> ".green().bold()
);
let id = user_input()?.trim().to_string();
print!("{}{}", "Title ".white().bold(), "-> ".green().bold());
let title = user_input()?.trim().to_string();
print!("{}{}", "Description ".white().bold(), "-> ".white().bold());
let desc = user_input()
.map_err(|e| TErrors::UpdateTodo(e.to_string()))?
.trim()
.to_string();
let id = id
.trim()
.to_string()
.parse::<usize>()
.map_err(|_| TErrors::ParseID)?;
for (i, z) in todos.iter_mut().enumerate() {
if i == id {
z.title = title.to_string();
z.description = desc.to_string();
}
}
Ok(())
}
pub fn save_and_exit(todos: &mut Vec<Todo>) {
if let Err(e) = persistence::write_to_file(todos) {
println!("Error save to file!!! {e}");
}
std::process::exit(0);
}
pub fn set_done(td: &mut [Todo]) -> Result<()> {
print!(
"\n{} {}{}",
"ID".red().bold(),
"to mark it as done ".white().bold(),
"-> ".green().bold()
);
let id = user_input()
.map_err(|e| TErrors::SetDone(e.to_string()))?
.trim()
.to_string()
.parse::<usize>()
.map_err(|_| TErrors::ParseID)?;
td[id].done = true;
Ok(())
}
pub fn set_undone(td: &mut [Todo]) -> Result<()> {
print!(
"\n{} {}{}",
"ID".red().bold(),
"to mark it as undone ".white().bold(),
"-> ".green().bold()
);
let id = user_input()
.map_err(|e| TErrors::SetUnDone(e.to_string()))?
.trim()
.to_string()
.parse::<usize>()
.map_err(|_| TErrors::ParseID)?;
td[id].done = false;
Ok(())
}
pub fn get_priority(pr: &Priority) -> ColoredString {
match pr {
Priority::LOW => "LOW".green().bold(),
Priority::MEDIUM => "MEDIUM".yellow().bold(),
Priority::HIGH => "HIGH".red().bold(),
}
}
pub fn set_priority(td: &mut [Todo]) -> Result<()> {
print!(
"\n{} {}{}",
"ID".red().bold(),
"to set the Priority ".white().bold(),
"-> ".green().bold()
);
let id = user_input()
.map_err(|e| TErrors::SetPriority(e.to_string()))?
.trim()
.to_string()
.parse::<usize>()
.map_err(|_| TErrors::ParseID)?;
print!(
"\n{}{} {}{} {}{} {}",
"[1]".blue().bold(),
"Low".green().bold(),
"[2]".blue().bold(),
"MEDIUM ".yellow().bold(),
"[3]".blue().bold(),
"HIGH ".red().bold(),
"-> ".green().bold(),
);
let pr = user_input()
.map_err(|e| TErrors::SetPriority(e.to_string()))?
.trim()
.to_string();
if pr.contains('1') {
td[id].time = Priority::LOW;
} else if pr.contains('2') {
td[id].time = Priority::MEDIUM;
} else if pr.contains('3') {
td[id].time = Priority::HIGH;
}
Ok(())
}