use super::sort::month_of;
use super::{Estimate, Phases, Proposal};
use crate::params::scale;
use crate::plan::{ColaSpec, Dollars, Expense, Plan, Trigger};
use crate::project::trigger_month;
pub fn apply(plan: &mut Plan, estimate: &Estimate) {
let end = estimate.through.map(month_of);
for proposal in &estimate.proposals {
let at = (!proposal.is_new)
.then(|| plan.expenses.iter().position(|e| e.id == proposal.id))
.flatten();
match at {
Some(at) => {
let in_force = end.and_then(|end| phase_in_force(plan, &plan.expenses[at], end));
set_amount(&mut plan.expenses[at], proposal, in_force);
}
None => plan.expenses.push(new_expense(proposal)),
}
}
}
pub(super) fn in_force_amount(plan: &Plan, expense: &Expense, end: i32) -> Dollars {
phase_in_force(plan, expense, end).map_or(expense.amount, |at| expense.phases[at].amount)
}
fn phase_in_force(plan: &Plan, expense: &Expense, end: i32) -> Option<usize> {
let fired = expense.phases.iter().enumerate().filter_map(|(at, phase)| {
let month = trigger_month(plan, &phase.from).filter(|&month| month <= end)?;
Some((month, at))
});
fired.max_by_key(|&(month, _)| month).map(|(_, at)| at)
}
fn set_amount(expense: &mut Expense, proposal: &Proposal, in_force: Option<usize>) {
let current = in_force.map_or(expense.amount, |at| expense.phases[at].amount);
let scale = |amount: Dollars| {
if current == 0 {
proposal.amount
} else {
scale(amount, proposal.amount as f64 / current as f64)
}
};
match proposal.phases {
Phases::This => match in_force {
Some(at) => expense.phases[at].amount = proposal.amount,
None => expense.amount = proposal.amount,
},
Phases::Scaled => {
expense.amount = scale(expense.amount);
for phase in &mut expense.phases {
phase.amount = scale(phase.amount);
}
}
Phases::All => {
expense.amount = proposal.amount;
for phase in &mut expense.phases {
phase.amount = proposal.amount;
}
}
}
}
fn new_expense(proposal: &Proposal) -> Expense {
Expense {
id: proposal.id.clone(),
name: Some(proposal.name.clone()),
amount: proposal.amount,
start: None,
end: None,
on: proposal.one_time.then_some(Trigger {
date: Some(proposal.last),
age: None,
owner: None,
event: None,
income: None,
offset: None,
}),
cola: ColaSpec::default(),
medical: false,
essential: false,
phases: Vec::new(),
}
}