use std::fmt::Display;
use std::time::Duration;
use crate::cycle::Cycle;
use crate::envelope::{current_time, Transaction};
use crate::target::SavingGoal;
use euro::{Currency, Euros};
use crate::euro;
use crate::{bank::Bank, envelope::Envelope};
pub fn run() {
let mut bank = Bank::load();
loop {
bank_info(&bank);
let mut envelopes = Envelope::fetch_all();
let msg = if envelopes.is_empty() {
"\nadd new envelope by writing 'n'"
} else {
""
};
view_envelopes(&bank, msg);
action(&mut bank, &mut envelopes);
}
}
pub enum Color {
Red,
Green,
Yellow,
}
impl Color {
const RESET: &str = "\x1b[0m";
}
impl Display for Color {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let color_code = match self {
Color::Red => "\x1b[31m",
Color::Green => "\x1b[32m",
Color::Yellow => "\x1b[33m",
};
write!(f, "{}", color_code)
}
}
fn println_with_color(text: &str, color: Color) {
print_with_color(text, color);
println!();
}
fn print_with_color(text: &str, color: Color) {
print!("{}{}{}", color, text, Color::RESET)
}
fn clear_window() {
print!("\x1B[2J\x1B[1;1H");
}
fn view_envelopes(bank: &Bank, msg: &str) {
let envelopes = Envelope::fetch_all();
println!("{msg}");
envelope_printer(bank, &envelopes);
}
pub fn envelope_printer(bank: &Bank, envelopes: &[Envelope]) {
for (idx, envelope) in envelopes.iter().enumerate() {
let s = format!(
"{}: {} {}-> {}\n",
idx,
envelope.name,
envelope.assigned(bank).print(),
envelope.goal.display(bank, envelope)
);
let color = envelope.goal.color_status(bank, envelope.id());
println_with_color(s.as_str(), color);
}
}
fn get_input_string() -> String {
let mut input = String::new();
std::io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
input.pop();
input
}
pub fn index_splitter(s: String) -> (Option<usize>, String) {
match s.find(|c: char| !c.is_numeric()) {
Some(pos) if pos != 0 => {
let (index, rest) = s.split_at(pos);
(index.parse::<usize>().unwrap().into(), rest.into())
}
_ => match s.parse::<usize>() {
Ok(index) => (index.into(), "".into()),
Err(_) => (None, s),
},
}
}
fn msg(msg: &str) {
clear_window();
println!("{}", msg);
}
fn msg_with_break(msg: &str) {
clear_window();
println!("{}", msg);
get_input_string();
}
fn inspect_envelope(envelope: &Envelope) {
let s = format!("{:?}", envelope);
msg_with_break(s.as_str());
}
fn pick_envelope(bank: &Bank, msg: &str) -> Envelope {
let envelopes = &Envelope::fetch_all();
if envelopes.is_empty() {
panic!();
}
clear_window();
view_envelopes(bank, msg);
loop {
let Ok(index) = get_input_string().parse::<usize>() else {continue};
if let Some(envelope) = envelopes.get(index) {
return envelope.to_owned();
}
}
}
fn howmanydays(message: &str) -> Duration {
msg(message);
loop {
let input = get_input_string();
if let Ok(qty) = input.parse::<f32>() {
return Duration::from_secs_f32(qty * 86400.);
}
}
}
fn howmanyeuros(message: &str) -> Euros {
msg(message);
loop {
let input = get_input_string();
if let Ok(qty) = Currency::try_from(input) {
return Euros::from(qty);
}
}
}
fn choose_goal() -> Option<SavingGoal> {
msg("Choose goal for envelope");
let goals: Vec<&str> = vec!["none", "amount", "saving", "spending", "saving building"];
for (idx, goal) in goals.iter().enumerate() {
println!("{} -> {}", idx, goal);
}
let idx = get_input_string().parse::<usize>().ok()?;
match idx {
0 => Some(SavingGoal::None),
1 => {
let amount = howmanyeuros("Amount of euros?");
Some(SavingGoal::Amount(amount))
}
2 => {
let start = current_time();
let end = current_time() + howmanydays("how many days until this goal?");
let amount = howmanyeuros("How many euros is the goal?");
Some(SavingGoal::SavingGoal { start, end, amount })
}
3 => {
println!("0 weekly\n1 monthly");
let cycle = match get_input_string().trim() {
"0" => Cycle::Week,
"1" => Cycle::Month,
_ => return None,
};
let amount = howmanyeuros("saving goals");
Some(SavingGoal::Spending { cycle, amount })
}
4 => {
let amount = howmanyeuros("How much money per month do you wanna save up?");
Some(SavingGoal::SavingBuilding { amount })
}
_ => None,
}
}
fn action_on_envelope(bank: &mut Bank, envelope: &mut Envelope, action: &str) {
match action.trim() {
"a" => {
msg("How much money to assign?");
let Ok(input) = get_input_string().parse::<f32>() else {return};
let amount: Euros = input.into();
bank.assign_money(envelope.id(), amount);
}
"A" => {
msg("How much money to remove from assignment?");
let Ok(input) = get_input_string().parse::<f32>() else {return};
let amount: Euros = input.into();
bank.remove_assign(envelope.id(), amount);
}
"t" => {
msg("how many euros is this transaction?");
let Ok(currency) = Currency::try_from(get_input_string()) else {return};
let trans = Transaction::new(currency, envelope.id());
bank.new_transaction(trans);
}
"m" => {
let new_envelope = pick_envelope(bank, "which envelope to move money to?");
if envelope.id() != new_envelope.id() {
msg("how many euros to move?");
let Ok(input) = get_input_string().parse::<f32>() else {return};
let amount: Euros = input.into();
bank.remove_assign(envelope.id(), amount);
bank.assign_money(new_envelope.id(), amount);
}
}
"g" => {
if let Some(goal) = choose_goal() {
envelope.goal = goal;
envelope.persist();
}
}
"d" => {
envelope.clone().delete();
msg("envelope deleted :O ");
bank.clear_assign(envelope.id());
}
"" => inspect_envelope(envelope),
_ => {}
}
}
fn action_no_envelope(action: &str, bank: &mut Bank, _envelopes: &[Envelope]) {
match action {
"i" => {
msg("insert money to bank!");
if let Ok(money) = get_input_string().parse::<f32>() {
let money: Euros = money.into();
bank.insert_money(money);
}
}
"I" => {
msg("Remove money from bank :(");
if let Ok(money) = get_input_string().parse::<f32>() {
let money: Euros = money.into();
bank.remove_money(money);
}
}
"n" => {
msg("add new envelope");
let name = get_input_string();
let envelope = Envelope::new_empty(&name);
envelope.persist();
}
_ => {}
}
}
pub fn action(bank: &mut Bank, envelopes: &mut [Envelope]) {
let input = get_input_string();
let (index, rest) = index_splitter(input);
match index {
Some(index) => {
let Some(envelope) = envelopes.get_mut(index) else {return};
action_on_envelope(bank, envelope, rest.as_str());
}
None => action_no_envelope(rest.as_str(), bank, envelopes),
}
}
pub fn bank_info(bank: &Bank) {
clear_window();
println!(
"total money: {}\nUnassigned: {}",
bank.total_money().euros(),
bank.unassigned().euros()
);
}