pagat 0.0.2

A library that helps you split the bill
Documentation
use crate::money::Money;
use crate::obligation::{Obligation, Obligations};
use crate::person::Person;
use crate::{Solver, SolverError};

#[derive(Debug, Clone)]
pub struct Payment {
    from: Person,
    amount: Money,
    to: Vec<Person>,
}

impl Payment {
    #[inline(always)]
    pub fn new(from: Person, amount: Money, to: &[Person]) -> Self {
        Self {
            from,
            amount,
            to: to.to_vec(),
        }
    }

    #[inline(always)]
    pub fn builder() -> PaymentBuilder {
        PaymentBuilder::default()
    }
}

#[derive(Debug, Default)]
pub struct PaymentBuilder {
    from: Person,
    amount: Money,
    to: Vec<Person>,
}

impl PaymentBuilder {
    #[inline(always)]
    pub fn new(from: Person, amount: Money, to: &[Person]) -> Self {
        Self {
            from,
            amount,
            to: to.to_vec(),
        }
    }

    #[inline(always)]
    pub fn from(mut self, from: Person) -> Self {
        self.from = from;
        self
    }

    #[inline(always)]
    pub fn to(mut self, to: &[Person]) -> Self {
        self.to = to.to_vec();
        self
    }

    #[inline(always)]
    pub const fn amount(mut self, amount: Money) -> Self {
        self.amount = amount;
        self
    }

    #[inline(always)]
    pub fn build(self) -> Payment {
        Payment {
            from: self.from,
            to: self.to,
            amount: self.amount,
        }
    }
}

#[derive(Default)]
pub struct PaymentsBuilder {
    payments: Vec<Payment>,
}

impl PaymentsBuilder {
    #[inline(always)]
    pub fn new(payments: &[Payment]) -> Self {
        Self {
            payments: payments.to_vec(),
        }
    }

    #[inline(always)]
    pub fn record(&mut self, payment: Payment) -> &mut Self {
        self.payments.push(payment);
        self
    }

    #[inline(always)]
    pub fn build(&mut self) -> Payments {
        Payments::new(&self.payments)
    }
}

#[derive(Debug)]
pub struct Payments(Vec<Payment>);

impl Payments {
    #[inline(always)]
    pub fn builder() -> PaymentsBuilder {
        PaymentsBuilder::default()
    }

    #[inline(always)]
    pub fn new(payments: &[Payment]) -> Self {
        Self(payments.to_vec())
    }

    #[inline(always)]
    pub(crate) fn each_pays(&self) -> Obligations {
        let mut obligations = Obligations::builder();

        for payment in &self.0 {
            let to = &payment.to;

            let included = match to.iter().find(|person| *person == &payment.from) {
                Some(_) => 0,
                None => 1,
            };

            let included = to.len() + included;

            let total = f64::from(payment.amount.raw() / included as i32).ceil() as i32;

            for debtor in to {
                if debtor == &payment.from || total == 0 {
                    continue;
                }

                obligations.record(
                    Obligation::builder()
                        .from(debtor.clone())
                        .to(payment.from.clone())
                        .amount(Money::new(total))
                        .build(),
                );
            }
        }

        obligations.build()
    }

    #[inline(always)]
    pub fn who_pays_whom(&self) -> Result<Obligations, SolverError> {
        Solver::from(self.each_pays()).solve()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_each_pays() {
        let a = Person::new("A");
        let b = Person::new("B");
        let a_spent = Money::new(10);
        let b_spent = Money::new(20);

        let obligations = Payments::builder()
            .record(
                Payment::builder()
                    .from(a.clone())
                    .to(&[b.clone()])
                    .amount(a_spent)
                    .build(),
            )
            .record(
                Payment::builder()
                    .from(b.clone())
                    .to(&[a.clone()])
                    .amount(b_spent)
                    .build(),
            )
            .build()
            .each_pays();

        let expected_a_pays = b_spent.raw() / 2;
        let expected_b_pays = a_spent.raw() / 2;

        for o in obligations.raw() {
            match &o.from {
                _ if o.from == a => {
                    assert_eq!(expected_a_pays, o.amount.raw());
                }
                _ if o.from == b => {
                    assert_eq!(expected_b_pays, o.amount.raw());
                }
                _ => unreachable!(),
            }
        }
    }
}