use std::{collections::BTreeMap, time::Duration};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::{
bank_path,
envelope::{current_time, Transaction},
euro::Euros,
EnvelopeId,
};
pub type BankId = Uuid;
#[derive(Default, Serialize, Deserialize)]
struct BankDeposit {
amount: Euros,
time: Duration,
}
impl BankDeposit {
fn new(amount: Euros) -> Self {
let time = current_time();
Self { amount, time }
}
}
#[derive(Default, Serialize, Deserialize, Clone, Copy)]
struct TimedEuros {
amount: Euros,
time: Duration,
}
impl TimedEuros {
pub fn new_current(amount: Euros) -> Self {
let time = current_time();
Self { amount, time }
}
pub fn sum_up(vars: &[Self]) -> Euros {
vars.iter().fold(Euros::default(), |sum, t| sum + t.amount)
}
pub fn sum_between(vars: &[Self], start: Duration, end: Duration) -> Euros {
vars.iter().fold(Euros::default(), |sum, t| {
sum + if t.time > start && t.time < end {
t.amount
} else {
Euros::default()
}
})
}
pub fn _sum_until(vars: &[Self], end: Duration) -> Euros {
Self::sum_between(vars, Duration::ZERO, end)
}
pub fn _sum_from(vars: &[Self], start: Duration) -> Euros {
Self::sum_between(vars, start, Duration::MAX)
}
}
#[derive(Default, Serialize, Deserialize)]
pub struct Bank {
withdrawals: Vec<Transaction>,
deposits: Vec<BankDeposit>,
assigned: BTreeMap<EnvelopeId, Vec<TimedEuros>>,
}
impl Bank {
pub fn load() -> Self {
let path = bank_path();
if path.exists() {
let s = std::fs::read_to_string(path.as_path()).unwrap();
serde_json::from_str(s.as_str()).unwrap()
} else {
let bank = Self::default();
bank.persist();
bank
}
}
pub fn assigned(&self, envelope: EnvelopeId) -> Euros {
let vec = self.assigned.get(&envelope).cloned().unwrap_or_default();
TimedEuros::sum_up(&vec)
}
pub fn assigned_in_timeframe(
&self,
envelope: EnvelopeId,
start: Duration,
end: Duration,
) -> Euros {
let vec = self.assigned.get(&envelope).cloned().unwrap_or_default();
TimedEuros::sum_between(&vec, start, end)
}
pub fn money_spent_in_timeframe(
&self,
envelope: EnvelopeId,
start: Duration,
end: Duration,
) -> Euros {
self.withdrawals.iter().fold(Euros::default(), |sum, t| {
sum + if t.time > start && t.time < end && t.envelope == envelope {
t.amount
} else {
Euros::default()
}
})
}
pub fn clear_assign(&mut self, envelope: EnvelopeId) {
self.assigned.remove(&envelope);
self.persist();
}
fn persist(&self) {
let path = bank_path();
let s = serde_json::to_string_pretty(self).unwrap();
std::fs::write(path, s).unwrap();
}
pub fn new_transaction(&mut self, transaction: Transaction) {
self.remove_assign(transaction.envelope, transaction.amount);
self.withdrawals.push(transaction);
self.persist();
}
pub fn remove_money(&mut self, amount: Euros) {
assert!(amount.euros() > 0);
let amount = amount.as_negative();
self.deposits.push(BankDeposit::new(amount));
self.persist();
}
pub fn insert_money(&mut self, amount: Euros) {
assert!(amount.euros() > 0);
self.deposits.push(BankDeposit::new(amount));
self.persist();
}
pub fn remove_assign(&mut self, envelope: EnvelopeId, amount: Euros) {
let amount = amount.as_negative();
self.assign_money(envelope, amount);
}
pub fn assign_money(&mut self, envelope: EnvelopeId, amount: Euros) {
let amount = TimedEuros::new_current(amount);
self.assigned
.entry(envelope)
.and_modify(|e| e.push(amount))
.or_insert(vec![amount]);
self.persist();
}
pub fn total_money(&self) -> Euros {
self.inputs_euros() - self.transactions_euros()
}
pub fn unassigned(&self) -> Euros {
self.total_money() - self.assigned_euros()
}
fn assigned_euros(&self) -> Euros {
self.assigned
.iter()
.fold(Euros::default(), |sum, t| sum + TimedEuros::sum_up(t.1))
}
fn inputs_euros(&self) -> Euros {
self.deposits
.iter()
.fold(Euros::default(), |sum, t| sum + t.amount)
}
fn transactions_euros(&self) -> Euros {
self.withdrawals
.iter()
.fold(Euros::default(), |sum, t| sum + t.amount)
}
}