use crate::base::Amount;
use crate::{BaseMoney, BaseOps, CustomMoney, IterOps};
use crate::{Currency, Decimal, base::DecimalNumber, macros::dec};
use rust_decimal::prelude::FromPrimitive;
pub(crate) fn split<M, C>(money: &M, n: u32) -> Option<(M, M)>
where
M: BaseMoney<C> + BaseOps<C> + Default + Amount<C> + Ord,
C: Currency,
{
if n == 0 {
return None;
}
let is_negative = money.is_negative();
let money = if is_negative {
money.abs()
} else {
money.clone()
};
let split_num = Decimal::from_u32(n)?;
let mut equal_part = money.checked_div(split_num)?;
let total = equal_part.checked_mul(split_num)?;
if total.amount() > money.amount() {
let n_usize = n.try_into().ok()?;
let mut parts = vec![equal_part; n_usize];
let scale = total.amount().scale();
let smallest_unit = Decimal::new(1, scale);
let mut i: usize = 0;
while parts.checked_sum()?.amount() > money.amount() {
parts[i] = parts[i].checked_sub(smallest_unit)?;
i += 1;
if i >= parts.len() {
i = 0;
}
}
parts.sort();
let mut new_equal_part = parts[0].clone();
let new_total = new_equal_part.checked_mul(split_num)?;
let mut new_remainder = money.checked_sub(new_total)?;
if is_negative {
new_equal_part = -new_equal_part;
new_remainder = -new_remainder;
}
return Some((new_equal_part, new_remainder));
};
let mut remainder = money.checked_sub(total)?;
if is_negative {
equal_part = -equal_part;
remainder = -remainder;
}
Some((equal_part, remainder))
}
pub(crate) fn split_dist<M, C>(money: &M, n: u32) -> Option<Vec<M>>
where
M: BaseMoney<C> + BaseOps<C> + Default + Amount<C> + Ord,
C: Currency,
{
if n == 0 {
return None;
}
let is_negative = money.is_negative();
let money = if is_negative {
money.abs()
} else {
money.clone()
};
let (equal_part, mut remainder) = split(&money, n)?;
let scale = remainder.amount().scale();
let split_num: usize = n.try_into().ok()?;
let smallest_unit = Decimal::new(1, scale);
let mut parts = vec![equal_part; split_num];
let mut i = 0;
while remainder.amount() >= smallest_unit {
parts[i] = parts[i].checked_add(smallest_unit)?;
remainder = remainder.checked_sub(smallest_unit)?;
i += 1;
if i >= split_num {
i = 0;
}
}
if is_negative {
parts = parts.into_iter().map(|p| -p).collect::<Vec<_>>();
}
Some(parts)
}
pub(crate) fn allocate<M, C, D>(money: &M, pcns: &[D]) -> Option<Vec<M>>
where
M: BaseMoney<C> + BaseOps<C> + Default + Amount<C> + CustomMoney<C>,
C: Currency,
D: DecimalNumber + Copy,
{
if pcns.is_empty() {
return None;
}
let is_negative = money.is_negative();
let money = if is_negative {
money.abs()
} else {
money.clone()
};
let mut total = Decimal::ZERO;
for p in pcns {
total = total.checked_add(p.get_decimal()?)?;
}
if total != dec!(100) {
return None;
}
let mut ret = allocate_by_ratios::<M, C, _>(&money, pcns);
if is_negative && let Some(m) = ret {
ret = Some(m.into_iter().map(|r| -r).collect::<Vec<_>>());
}
ret
}
pub(crate) fn allocate_by_ratios<M, C, D>(money: &M, ratios: &[D]) -> Option<Vec<M>>
where
M: BaseMoney<C> + BaseOps<C> + Default + Amount<C> + CustomMoney<C>,
C: Currency,
D: DecimalNumber + Copy,
{
if ratios.is_empty() {
return None;
}
let is_negative = money.is_negative();
let money = if is_negative {
money.abs()
} else {
money.clone()
};
let total_ratio: Decimal = {
let mut total = Decimal::ZERO;
for d in ratios {
total = total.checked_add(d.get_decimal().unwrap_or_default())?;
}
total
};
let mut parts: Vec<M> = ratios
.iter()
.map(|r| {
let share = money.checked_mul(*r)?.checked_div(total_ratio)?;
Some(share)
})
.collect::<Option<Vec<_>>>()?;
let allocated_total = parts.checked_sum()?;
if allocated_total.amount() > money.amount() {
let mut i = 0;
let scale = allocated_total.amount().scale();
let smallest_unit = Decimal::new(1, scale);
while parts.checked_sum()?.amount() > money.amount() && i < parts.len() {
parts[i] = parts[i].checked_sub(smallest_unit)?;
i += 1;
}
parts.sort_by_key(|b| std::cmp::Reverse(b.amount()));
return Some(parts);
}
let mut remainder = money.checked_sub(allocated_total.clone())?;
let scale = allocated_total.amount().scale();
let smallest_unit = Decimal::new(1, scale);
let mut i = 0;
while remainder.amount() >= smallest_unit && i < parts.len() {
parts[i] = parts[i].checked_add(smallest_unit)?;
remainder = remainder.checked_sub(smallest_unit)?;
i += 1;
}
if is_negative {
parts = parts.into_iter().map(|r| -r).collect::<Vec<_>>();
}
Some(parts)
}