1use crate::error::BalanceError;
2use crate::models::{Currency, Money};
3use std::collections::hash_map::Iter;
4use std::collections::HashMap;
5use std::error::Error;
6use std::fmt;
7use std::fmt::{Display, Formatter};
8use std::ops::{Add, Neg, Sub};
9use std::rc::Rc;
10
11#[derive(Debug, Clone)]
13pub struct Balance {
14 pub balance: HashMap<Option<Rc<Currency>>, Money>,
15}
16impl Default for Balance {
17 fn default() -> Self {
18 Self::new()
19 }
20}
21
22#[derive(Debug)]
23pub struct TooManyCurrenciesError {
24 pub num_currencies: usize,
25}
26impl Error for TooManyCurrenciesError {}
27impl Display for TooManyCurrenciesError {
28 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
29 write!(
30 f,
31 "{} currencies found, expecting only one.",
32 self.num_currencies
33 )
34 }
35}
36
37impl Balance {
38 pub fn new() -> Balance {
39 Balance {
40 balance: HashMap::new(),
41 }
42 }
43
44 pub fn to_money(&self) -> Result<Money, BalanceError> {
47 let vec = self
48 .balance
49 .values()
50 .filter(|x| !x.is_zero())
51 .collect::<Vec<&Money>>();
52 match vec.len() {
53 0 => Ok(Money::Zero),
54 1 => Ok(vec[0].clone()),
55 _ => Err(BalanceError::TooManyCurrencies(self.clone())),
56 }
57 }
58 pub fn is_zero(&self) -> bool {
59 match self.balance.is_empty() {
60 true => true,
61 false => {
62 for (_, money) in self.balance.iter() {
63 if !money.is_zero() {
64 return false;
65 }
66 }
67 true
68 }
69 }
70 }
71
72 pub fn can_be_zero(&self) -> bool {
75 if self.is_zero() {
76 return true;
77 }
78 let mut positive = false;
79 let mut negative = false;
80 for (_, m) in self.balance.iter() {
81 positive |= m.is_positive();
82 negative |= m.is_negative();
83 if positive & negative {
84 return true;
85 }
86 }
87 false
88 }
89 pub fn len(&self) -> usize {
90 self.balance.len()
91 }
92 pub fn is_empty(&self) -> bool {
93 self.balance.is_empty()
94 }
95 pub fn iter(&self) -> Iter<'_, Option<Rc<Currency>>, Money> {
96 self.balance.iter()
97 }
98}
99
100impl Display for Balance {
101 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
102 let mut string = String::new();
103 for (_, v) in self.balance.iter() {
104 string.push_str(format!("{}", v).as_str());
105 }
106 write!(f, "{}", string)
107 }
108}
109
110impl<'a> Neg for Balance {
111 type Output = Balance;
112
113 fn neg(self) -> Self::Output {
114 let mut balance: HashMap<Option<Rc<Currency>>, Money> = HashMap::new();
115 for (k, v) in self.balance {
116 balance.insert(k, -v);
117 }
118 Balance { balance }
119 }
120}
121
122impl Add for Balance {
123 type Output = Balance;
124
125 fn add(self, rhs: Self) -> Self::Output {
126 let mut total: HashMap<Option<Rc<Currency>>, Money> = HashMap::new();
127 let balances = vec![self, rhs];
128 for bal in balances.iter() {
129 for (cur, money) in bal.balance.iter() {
130 match total.to_owned().get(cur) {
131 None => total.insert(cur.clone(), money.clone()),
132 Some(total_money) => match total_money {
133 Money::Zero => total.insert(cur.clone(), money.clone()),
134 Money::Money {
135 amount: already, ..
136 } => match money {
137 Money::Zero => None,
138 Money::Money { amount, .. } => total.insert(
139 cur.clone(),
140 Money::from((
141 cur.as_ref().unwrap().clone(),
142 amount + already.to_owned(),
143 )),
144 ),
145 },
146 },
147 };
148 }
149 }
150
151 Balance {
152 balance: total.into_iter().filter(|(_, v)| !v.is_zero()).collect(),
153 }
154 }
155}
156
157impl Sub for Balance {
158 type Output = Balance;
159
160 fn sub(self, rhs: Self) -> Self::Output {
161 let negative = -rhs;
162 self + negative
163 }
164}
165
166impl From<Money> for Balance {
168 fn from(money: Money) -> Self {
169 let mut balance: HashMap<Option<Rc<Currency>>, Money> = HashMap::new();
170 match money {
171 Money::Zero => balance.insert(None, Money::Zero),
172 Money::Money { ref currency, .. } => {
173 balance.insert(Some(currency.clone()), money.clone())
174 }
175 };
176 Balance { balance }
177 }
178}
179
180impl PartialEq for Balance {
181 fn eq(&self, other: &Self) -> bool {
182 for (k, v) in self.balance.iter() {
183 let other_money = other.balance.get(k);
184 match other_money {
185 None => {
186 if !v.is_zero() {
187 return false;
188 }
189 }
190 Some(money) => {
191 if money != v {
192 return false;
193 }
194 }
195 }
196 }
197 true
198 }
199}