use crate::sum::Sum;
use std::{
collections::BTreeMap,
fmt,
ops::{Add, AddAssign, Sub, SubAssign},
};
#[derive(PartialEq, Clone)]
pub struct Balance<Unit, Number>(pub(crate) BTreeMap<Unit, Number>);
impl<Unit, Number> Balance<Unit, Number>
where
Unit: Ord + Clone,
{
fn apply_sum_operation<SumNumber>(
&mut self,
rhs: &Sum<Unit, SumNumber>,
amount_op: fn(Number, SumNumber) -> Number,
) where
Number: Default + Clone,
SumNumber: Clone,
{
rhs.0.iter().for_each(|(unit, amount)| {
self.apply_unit_operation(
&(unit.clone(), amount.clone()),
amount_op,
)
});
}
fn apply_unit_operation<SumNumber>(
&mut self,
(unit, amount): &(Unit, SumNumber),
amount_op: fn(Number, SumNumber) -> Number,
) where
Number: Default + Clone,
SumNumber: Clone,
{
self.0
.entry(unit.clone())
.and_modify(|balance| {
*balance = amount_op(balance.clone(), amount.clone());
})
.or_insert_with(|| amount_op(Default::default(), amount.clone()));
}
pub fn amounts(&self) -> impl Iterator<Item = (&Unit, &Number)> {
self.0.iter()
}
pub fn unit_amount(&self, unit: &Unit) -> Option<&Number> {
self.0.get(unit)
}
}
impl<Unit, Number> Default for Balance<Unit, Number>
where
Unit: Ord,
{
fn default() -> Self {
Self(Default::default())
}
}
impl<Unit, Number> fmt::Debug for Balance<Unit, Number>
where
Unit: fmt::Debug,
Number: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Balance(")?;
f.debug_map().entries(self.0.iter()).finish()?;
f.write_str(")")
}
}
impl<Unit, Number, SumNumber> SubAssign<&Sum<Unit, SumNumber>>
for Balance<Unit, Number>
where
Unit: Ord + Clone,
Number: Default + Sub<Output = Number> + Clone,
SumNumber: Clone + Into<Number>,
{
fn sub_assign(&mut self, sum: &Sum<Unit, SumNumber>) {
self.apply_sum_operation(sum, |balance_amount, sum_amount| {
balance_amount - sum_amount.into()
});
}
}
impl<Unit, Number, SumNumber> SubAssign<&(Unit, SumNumber)>
for Balance<Unit, Number>
where
Unit: Ord + Clone,
Number: Default + Sub<Output = Number> + Clone,
SumNumber: Clone + Into<Number>,
{
fn sub_assign(&mut self, unit_amount: &(Unit, SumNumber)) {
self.apply_unit_operation(unit_amount, |balance, amount| {
balance - amount.into()
});
}
}
impl<Unit, Number, SumNumber> Sub<&Sum<Unit, SumNumber>>
for Balance<Unit, Number>
where
Unit: Ord + Clone,
Number: Default + Sub<Output = Number> + Clone,
SumNumber: Clone + Into<Number>,
{
type Output = Self;
fn sub(mut self, sum: &Sum<Unit, SumNumber>) -> Self::Output {
self -= sum;
self
}
}
impl<Unit, Number, SumNumber> Sub<&(Unit, SumNumber)> for Balance<Unit, Number>
where
Unit: Ord + Clone,
Number: Default + Sub<Output = Number> + Clone,
SumNumber: Clone + Into<Number>,
{
type Output = Self;
fn sub(mut self, unit_amount: &(Unit, SumNumber)) -> Self::Output {
self -= unit_amount;
self
}
}
impl<Unit, Number, SumNumber> AddAssign<&Sum<Unit, SumNumber>>
for Balance<Unit, Number>
where
Unit: Ord + Clone,
Number: Default + Add<Output = Number> + Clone,
SumNumber: Clone + Into<Number>,
{
fn add_assign(&mut self, sum: &Sum<Unit, SumNumber>) {
self.apply_sum_operation(sum, |balance_amount, sum_amount| {
balance_amount + sum_amount.into()
});
}
}
impl<Unit, Number, SumNumber> AddAssign<&(Unit, SumNumber)>
for Balance<Unit, Number>
where
Unit: Ord + Clone,
Number: Default + Add<Output = Number> + Clone,
SumNumber: Clone + Into<Number>,
{
fn add_assign(&mut self, unit_amount: &(Unit, SumNumber)) {
self.apply_unit_operation(unit_amount, |balance, amount| {
balance + amount.into()
});
}
}
impl<Unit, Number, SumNumber> Add<&Sum<Unit, SumNumber>>
for Balance<Unit, Number>
where
Unit: Ord + Clone,
Number: Default + Add<Output = Number> + Clone,
SumNumber: Clone + Into<Number>,
{
type Output = Self;
fn add(mut self, sum: &Sum<Unit, SumNumber>) -> Self::Output {
self += sum;
self
}
}
impl<Unit, Number, SumNumber> Add<&(Unit, SumNumber)> for Balance<Unit, Number>
where
Unit: Ord + Clone,
Number: Default + Add<Output = Number> + Clone,
SumNumber: Clone + Into<Number>,
{
type Output = Self;
fn add(mut self, unit_amount: &(Unit, SumNumber)) -> Self::Output {
self += unit_amount;
self
}
}
#[cfg(test)]
mod test {
use super::Balance;
use crate::test_utils::TestBalance;
use maplit::btreemap;
#[test]
fn default() {
let actual = TestBalance::default();
let expected = Balance(btreemap!());
assert_eq!(actual, expected);
}
#[test]
fn apply_sum_operation() {
let mut actual: TestBalance = Default::default();
let usd = "USD";
let thb = "THB";
let sum = sum!(2, usd; 3, thb);
actual.apply_sum_operation(&sum, |balance, amount| {
let rhs: i128 = amount.into();
balance + rhs
});
let sum = sum!(2, usd; 3, thb);
actual.apply_sum_operation(&sum, |balance, amount| {
let rhs: i128 = amount.into();
balance * rhs
});
let expected = Balance(btreemap! {
usd => 4,
thb => 9,
});
assert_eq!(actual, expected);
}
#[test]
fn fmt_debug() {
let usd = "USD";
let amount_usd = 76;
let thb = "THB";
let amount_thb = 45;
let sum = sum!(amount_usd, usd; amount_thb, thb);
let balance = TestBalance::default() + ∑
let actual = format!("{:?}", balance);
let expected = format!(
"Balance({{{:?}: {:?}, {:?}: {:?}}})",
thb, amount_thb, usd, amount_usd
);
assert_eq!(actual, expected);
}
#[test]
fn sub_assign_sum() {
let usd = "USD";
let mut actual: TestBalance = Default::default();
actual -= &sum!(9, usd);
let expected = Balance(btreemap! {
usd => -9,
});
assert_eq!(actual, expected);
}
#[test]
fn sub_sum() {
let usd = "USD";
let immutable: TestBalance = Default::default();
let actual = immutable - &sum!(9, usd);
let expected = Balance(btreemap! {
usd => -9,
});
assert_eq!(actual, expected);
}
#[test]
fn add_assign_sum() {
let usd = "USD";
let mut actual: TestBalance = Default::default();
actual += &sum!(9, usd);
let expected = Balance(btreemap! {
usd => 9,
});
assert_eq!(actual, expected);
}
#[test]
fn add_sum() {
let usd = "USD";
let immutable: TestBalance = Default::default();
let actual = immutable + &sum!(9, usd);
let expected = Balance(btreemap! {
usd => 9,
});
assert_eq!(actual, expected);
}
#[test]
fn amounts() {
let usd = "USD";
let thb = "THB";
let ils = "ILS";
let balance = TestBalance::default()
+ &sum! {
100, usd; 200, thb; 300, ils
};
let actual = balance.amounts().collect::<Vec<_>>();
let expected = vec![(&ils, &300), (&thb, &200), (&usd, &100)];
assert_eq!(actual, expected);
}
#[test]
fn unit_amount() {
let usd = "USD";
let thb = "THB";
let ils = "ILS";
let balance = TestBalance::default() + &sum!(200, usd; 100, thb);
assert_eq!(balance.unit_amount(&usd).unwrap(), &200);
assert_eq!(balance.unit_amount(&thb).unwrap(), &100);
assert_eq!(balance.unit_amount(&ils), None);
}
}