#![allow(dead_code, unused_variables)]
use hashbrown::{hash_map::Entry, HashMap};
use std::{fmt::Debug, hash::Hash, ops::Deref};
use super::{Cost, CostSpec, Interpolated, Number, PostingSpec, PriceSpec};
#[derive(PartialEq, Eq, Default, Debug)]
pub(crate) struct CurrencyPositions<D, N, C, L>(Vec<CurrencyPosition<D, N, C, L>>)
where
D: Copy,
N: Copy,
C: Clone,
L: Clone;
impl<D, N, C, L> Deref for CurrencyPositions<D, N, C, L>
where
D: Copy,
N: Copy,
C: Clone,
L: Clone,
{
type Target = Vec<CurrencyPosition<D, N, C, L>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub(crate) struct CurrencyPosition<D, N, C, L>
where
D: Copy,
N: Copy,
C: Clone,
L: Clone,
{
units: N,
cost: Option<Cost<D, N, C, L>>,
}
impl<D, N, C, L> CurrencyPosition<D, N, C, L>
where
D: Copy,
N: Copy,
C: Clone,
L: Clone,
{
pub(crate) fn is_below(&self, threshold: N) -> bool
where
N: Number + Ord,
{
self.units.abs() <= threshold && self.cost.is_none()
}
}
#[derive(Debug)]
pub(crate) struct HashMapOfVec<K, V>(HashMap<K, Vec<V>>);
impl<K, V> HashMapOfVec<K, V> {
pub(crate) fn push_or_insert(&mut self, k: K, v: V)
where
K: Eq + Hash,
{
use Entry::*;
match self.0.entry(k) {
Occupied(mut occupied) => {
occupied.get_mut().push(v);
}
Vacant(vacant) => {
vacant.insert(vec![v]);
}
}
}
}
impl<K, V> Default for HashMapOfVec<K, V> {
fn default() -> Self {
Self(Default::default())
}
}
impl<K, V> IntoIterator for HashMapOfVec<K, V> {
type Item = (K, Vec<V>);
type IntoIter = hashbrown::hash_map::IntoIter<K, Vec<V>>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<K, V> Deref for HashMapOfVec<K, V> {
type Target = HashMap<K, Vec<V>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Debug)]
pub(crate) struct AnnotatedPosting<P, C>
where
C: Clone,
{
pub(crate) posting: P,
pub(crate) idx: usize,
pub(crate) currency: Option<C>,
pub(crate) cost_currency: Option<C>,
pub(crate) price_currency: Option<C>,
}
impl<P, C> AnnotatedPosting<P, C>
where
P: PostingSpec,
C: Clone,
{
pub(crate) fn bucket(&self) -> Option<C>
where
C: Clone,
{
self.cost_currency
.as_ref()
.cloned()
.or(self.price_currency.as_ref().cloned())
.or_else(|| {
if self.posting.cost().is_none() && self.posting.price().is_none() {
self.currency.as_ref().cloned()
} else {
None
}
})
}
}
#[derive(Clone, Debug)]
pub(crate) enum BookedOrUnbookedPosting<P>
where
P: PostingSpec,
{
Booked(Interpolated<P, P::Date, P::Number, P::Currency, P::Label>),
Unbooked(AnnotatedPosting<P, P::Currency>),
}
impl<P> BookedOrUnbookedPosting<P>
where
P: PostingSpec,
{
pub(crate) fn weight(&self) -> Option<P::Number> {
use BookedOrUnbookedPosting::*;
match self {
Booked(booked) => Some(booked.units),
Unbooked(unbooked) => {
let p = &unbooked.posting;
if let Some(cost_spec) = p.cost() {
match (cost_spec.total(), cost_spec.per_unit(), p.units()) {
(Some(cost_total), _, _) => Some(cost_total),
(None, Some(cost_per_unit), Some(units)) => {
let weight = (cost_per_unit * units).rescaled(units.scale());
Some(weight)
}
_ => None,
}
} else if let Some(price_spec) = p.price() {
match (price_spec.total(), price_spec.per_unit(), p.units()) {
(Some(price_total), _, _) => Some(price_total),
(None, Some(price_per_unit), Some(units)) => {
let weight = (price_per_unit * units).rescaled(units.scale());
tracing::debug!("weight {weight} from price_per_unit {price_per_unit} units {units}");
Some(weight)
}
_ => None,
}
} else {
p.units()
}
}
}
}
}