use serde::Serialize;
use crate::params::TaxTables;
use crate::plan::{Plan, PlanError, SCHEMA_VERSION, TreatmentClass};
use crate::project::{Projection, project};
use crate::search::{Progress, RunError, run_all};
const FEWEST_ORDERED: usize = 2;
#[derive(Debug, Clone)]
pub struct OrderCandidate {
pub order: Vec<TreatmentClass>,
pub projection: Projection,
}
#[derive(Debug, Clone)]
pub struct OrderSearch {
pub baseline: Projection,
pub candidates: Vec<OrderCandidate>,
}
impl OrderSearch {
#[must_use]
pub fn best(&self) -> &OrderCandidate {
&self.candidates[0]
}
}
pub fn optimize_order(
plan: &Plan,
tables: &TaxTables,
progress: &Progress,
) -> Result<OrderSearch, RunError> {
let orders = orders_of(plan).ok_or_else(|| {
RunError::refused(
"plan.withdrawal_order",
"fewer than two of the listed classes hold an account without a drain priority, so \
there is nothing to order",
)
})?;
let projections = run_all(orders.len(), progress, |at| {
let mut ordered = plan.clone();
apply_order(&mut ordered, &orders[at]);
Some(project(&ordered, tables))
})?;
let baseline = projections[0].clone();
let mut candidates: Vec<OrderCandidate> = Vec::new();
for (order, projection) in orders.into_iter().zip(projections) {
if !candidates.iter().any(|kept| kept.projection == projection) {
candidates.push(OrderCandidate { order, projection });
}
}
candidates.sort_by_cached_key(|candidate| super::rank_key(&candidate.projection));
Ok(OrderSearch {
baseline,
candidates,
})
}
fn orders_of(plan: &Plan) -> Option<Vec<Vec<TreatmentClass>>> {
let stated = &plan.plan.withdrawal_order;
let is_ordered = |class: &TreatmentClass| {
plan.accounts
.iter()
.any(|account| account.drain_priority.is_none() && account.treatment() == *class)
};
let (ordered, trailing): (Vec<TreatmentClass>, Vec<TreatmentClass>) =
stated.iter().partition(|&class| is_ordered(class));
if ordered.len() < FEWEST_ORDERED {
return None;
}
let mut orders = vec![stated.clone()];
for mut order in permutations(&ordered) {
order.extend_from_slice(&trailing);
if order != *stated {
orders.push(order);
}
}
Some(orders)
}
fn permutations(classes: &[TreatmentClass]) -> Vec<Vec<TreatmentClass>> {
if classes.len() <= 1 {
return vec![classes.to_vec()];
}
let mut arranged = Vec::new();
for (at, &first) in classes.iter().enumerate() {
let mut rest = classes.to_vec();
rest.remove(at);
for mut tail in permutations(&rest) {
tail.insert(0, first);
arranged.push(tail);
}
}
arranged
}
pub fn apply_order(plan: &mut Plan, order: &[TreatmentClass]) {
plan.plan.withdrawal_order = order.to_vec();
}
pub fn order_overlay(base: &str, order: &[TreatmentClass]) -> Result<String, PlanError> {
#[derive(Serialize)]
struct Restated<'a> {
withdrawal_order: &'a [TreatmentClass],
}
#[derive(Serialize)]
struct OverlayDocument<'a> {
schema: u32,
base: &'a str,
plan: Restated<'a>,
}
Ok(toml::to_string_pretty(&OverlayDocument {
schema: SCHEMA_VERSION,
base,
plan: Restated {
withdrawal_order: order,
},
})?)
}