/* * Copyright (C) 2022 Aravinth Manivannan * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use std::collections::HashMap; use std::rc::Rc; use uuid::Uuid; pub trait Purchasable { fn cost(&self) -> f64; fn name(&self) -> &str; fn paid_by(&self) -> Rc; } pub fn money_owned(splits: &[Split]) { for split in splits.iter() {} } #[derive(Clone)] pub struct Split { purchase: Rc, split_strategy: SplitStrategy, parties: Vec>, } //#[derive(Clone)] //pub struct Split { // shares: ShareHashMap, // paid_by: Rc, //} impl Split { pub fn new(purchase: Rc, split_strategy: SplitStrategy, parties: Vec>) -> Self { Self { split_strategy, parties, purchase } } // pub fn iter(&self) -> SplitIter { // let iter = self.shares.iter(); // let paid_by = self.paid_by; // SplitIter { iter, paid_by} // } // // pub fn insert(&mut self, actor: Rc, amount: f64) { // let name = actor.name().to_owned(); // self.shares.insert(name, (actor, amount)); // } pub fn get(&mut self, actor: Rc) -> Option { unimplemented!(); // if let Some((_actor, cost)) = self.shares.get(actor.name()) { // Some(*cost) // } else { // None // } } } //pub struct SplitIter<'a> { // iter: std::collections::hash_map::Iter<'a, String, (Rc, f64)>, // paid_by: Rc, //} // //impl<'a> Iterator for SplitIter<'a> { // type Item = (Rc, f64); // // fn next(&mut self) -> Option { // if let Some((_, (actor, cost))) = self.iter.next() { // Some((actor.clone(), *cost)) // } else { // None // } // } //} impl Split { pub fn validate(&self) -> bool { self.split_strategy.validate(&self) } } // key is a string for getting Eq and PartialEq w object safety pub type ShareHashMap = HashMap, f64)>; /* * SPLITTING STRATEGIES: * Unequal: Each actor's split is specified in specific amounts * Shares: Each actor's split is specified in specific percentage * Equal: Each actor's split is computed by splitting total expense among all participating actors */ #[derive(Clone)] pub enum SplitStrategy { Unequal(ShareHashMap), Shares(ShareHashMap), Equal, } impl SplitStrategy { pub fn validate(&self, split: &Split) -> bool { match self { SplitStrategy::Unequal(shares) => { let mut total = 0.00; for (_actor_str, (_actor_obj, share)) in shares.iter() { total += share; } let cost = split.purchase.cost(); println!("Unequal sharing: expected {cost} got {total}"); total == cost } SplitStrategy::Shares(shares) => { let mut total = 0.00; let cost = split.purchase.cost(); let mut total_shares = 0.00; for (_actor_str, (_actor_obj, share)) in shares.iter() { total_shares += share; total += cost * share; } println!("Unequal sharing: expected shares 1 got {total_shares}"); println!("Unequal sharing: expected {cost} got {total}"); total_shares == 1.00 && total == cost } SplitStrategy::Equal => true, } } pub fn money_owed(&self, split: &Split) -> ShareHashMap { match self { SplitStrategy::Unequal(shares) => { let mut s = shares.clone(); let payer = split.purchase.paid_by(); if let Some((_,money)) = s.get_mut(payer.name()) { *money = 0.0; } s } SplitStrategy::Equal => { let mut shares = ShareHashMap::default(); let cost = split.purchase.cost(); for actor in split.parties.iter() { if *actor == split.purchase.paid_by() { shares.insert(actor.name().to_owned(), (actor.clone(), 0.00)); } else { shares.insert(actor.name().to_owned(), (actor.clone(), cost / split.parties.len() as f64)); } } shares } SplitStrategy::Shares(c) => { let mut shares = ShareHashMap::default(); let cost = split.purchase.cost(); for (actor_name,(actor, share)) in c.iter() { //if actor.name() == split.purchase.paid_by().name() { if *actor == split.purchase.paid_by() { shares.insert(actor_name.clone(), (actor.clone(), 0.00)); } else { shares.insert(actor_name.clone(), (actor.clone(), cost * share)); } } shares } } } } pub trait Actor { fn spends(&mut self, i: Split); fn owes(&mut self, i: Split); // name must be unique in a namespace fn name(&self) -> &str; fn id(&self) -> &Uuid; } impl PartialEq for dyn Actor { fn eq(&self, other: &Self) -> bool { self.id() == other.id() } } pub struct Person { name: String, spent: Vec, owes: Vec, id: Uuid, } impl Person { pub fn new(name: String, id: Uuid) -> Self { let owes = Vec::default(); let spent = Vec::default(); Self { name, spent, owes, id} } } impl Actor for Person { fn spends(&mut self, split: Split) { if split.purchase.paid_by().name() == self.name() { self.spent.push(split); } else { unimplemented!("Raise error: Actor didn't pay for purchase") } } fn owes(&mut self, split: Split) { if split.purchase.paid_by().name() == self.name() { unimplemented!("Raise error: Actor didn't paid for purchase; can't own money") } else { self.owes.push(split); } } fn name(&self) -> &str { &self.name } fn id(&self) -> &Uuid { &self.id } } pub struct Item { cost: f64, name: String, payer: Rc, } impl Item { pub fn new(name: String, cost: f64, payer: Rc) -> Self { Self { cost, name, payer } } } impl Purchasable for Item { fn cost(&self) -> f64 { self.cost } fn name(&self) -> &str { &self.name } fn paid_by(&self) -> Rc { self.payer.clone() } } //impl Actor for Person { // fn spends(&self, i: &I); // fn owes(&self, amount: usize); //} #[cfg(test)] mod tests { use super::*; #[test] fn split_works() { let person1: Rc = Rc::new(Person::new("person1".into(), Uuid::new_v4())); let person2: Rc = Rc::new(Person::new("person2".into(), Uuid::new_v4())); let person3: Rc = Rc::new(Person::new("person3".into(), Uuid::new_v4())); let person4: Rc = Rc::new(Person::new("person4".into(), Uuid::new_v4())); let item = Item::new("test item".into(), 240.00, person1.clone()); let actors = vec![ person1.clone(), person2.clone(), person3.clone(), person4.clone(), ]; // equal split let strategy = SplitStrategy::Equal; let mut split = Split::new(Rc::new(item), strategy, actors); assert!(split.split_strategy.validate(&split)); let dues = split.split_strategy.money_owed(&split); for (_actor_name,(actor, due)) in dues.iter() { if split.purchase.paid_by().name() == actor.name() { assert_eq!(*due, 0.00); } else { assert_eq!(*due, split.purchase.cost() / split.parties.len() as f64); } } // share split let mut shares = ShareHashMap::default(); shares.insert(person1.name().to_owned(), (person1.clone(), 0.3)); shares.insert(person2.name().to_owned(), (person2.clone(), 0.4)); shares.insert(person3.name().to_owned(), (person3.clone(), 0.2)); shares.insert(person4.name().to_owned(), (person4.clone(), 0.1)); let strategy = SplitStrategy::Shares(shares.clone()); split.split_strategy = strategy; assert!(split.split_strategy.validate(&split)); let dues = split.split_strategy.money_owed(&split); for (actor_name, (actor, due)) in dues.iter() { println!("Testing for actor: {actor_name} with due: {due}"); if split.purchase.paid_by().name() == actor.name() { assert_eq!(*due, 0.00); } else { let (_, share) = shares.get(actor_name).unwrap(); assert_eq!(*due, split.purchase.cost() * *share); } } // Unequal split let mut shares = ShareHashMap::default(); shares.insert(person1.name().to_owned(), (person1.clone(), 0.3 * split.purchase.cost())); shares.insert(person2.name().to_owned(), (person2.clone(), 0.4 * split.purchase.cost())); shares.insert(person3.name().to_owned(), (person3.clone(), 0.2 * split.purchase.cost())); shares.insert(person4.name().to_owned(), (person4.clone(), 0.1 * split.purchase.cost())); let strategy = SplitStrategy::Unequal(shares); split.split_strategy = strategy; assert!(split.split_strategy.validate(&split)); } }