use std::{mem::{MaybeUninit, self}, fmt, ops::{Div, Mul}};
use crate::CurrencyCode;
pub struct Rates<RATE, const N: usize = { crate::currency::ARRAY.len() + 10 }> {
currency: [MaybeUninit<CurrencyCode>; N],
rate: [MaybeUninit<RATE>; N],
len: u8,
}
impl<const N: usize, RATE> Rates<RATE, N> {
pub const fn new() -> Self { Self {
currency: [MaybeUninit::uninit(); N],
rate: unsafe {
MaybeUninit::<[MaybeUninit<RATE>; N]>::uninit().assume_init()
},
len: 0,
} }
#[inline] pub const fn len(&self) -> usize { self.len as usize }
#[inline] pub const fn is_empty(&self) -> bool { self.len == 0 }
#[inline] pub fn clear(&mut self) { self.len = 0; }
pub fn currencies(&self) -> &[CurrencyCode] {
unsafe {
let currencies = self.currency.get_unchecked(..self.len as usize);
mem::transmute::<
&[MaybeUninit<CurrencyCode>],
&[CurrencyCode],
>(currencies)
}
}
pub fn rates(&self) -> &[RATE] {
unsafe {
let rates = self.rate.get_unchecked(..self.len as usize);
mem::transmute::<
&[MaybeUninit<RATE>],
&[RATE],
>(rates)
}
}
pub fn iter(&self) -> impl Iterator<Item = (CurrencyCode, &RATE)> {
self.currencies().iter().copied().zip(self.rates().iter()).rev()
}
pub unsafe fn push_unchecked(&mut self, currency: CurrencyCode, rate: RATE) {
let i = self.len as usize;
*self.currency.get_unchecked_mut(i) = MaybeUninit::new(currency);
*self.rate.get_unchecked_mut(i) = MaybeUninit::new(rate);
self.len += 1;
}
pub fn push(&mut self, currency: CurrencyCode, rate: RATE) -> bool {
if (self.len as usize) < N {
unsafe {
self.push_unchecked(currency, rate);
}
true
} else { false }
}
pub fn extend_capped(&mut self, iter: impl IntoIterator<Item = (CurrencyCode, RATE)>) -> bool {
for (currency, rate) in iter {
if !self.push(currency, rate) { return false }
}
true
}
pub fn get(&self, currency: CurrencyCode) -> Option<&RATE> {
self.iter()
.find(|&(c,_)| c == currency)
.map(|(_,r)| r)
}
pub fn convert(&self, amount: &RATE, from: CurrencyCode, to: CurrencyCode) -> Option<RATE>
where for<'x> &'x RATE: Div<&'x RATE, Output = RATE>, for<'x> &'x RATE: Mul<RATE, Output = RATE> {
let from_value = self.get(from)?;
let to_value = self.get(to)?;
Some(amount * (to_value / from_value))
}
}
impl<const N: usize, RATE> Default for Rates<RATE, N> { #[inline] fn default() -> Self { Self::new() } }
impl<const N: usize, RATE: fmt::Debug> fmt::Debug for Rates<RATE, N> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut m = f.debug_map();
for (currency, rate) in self.iter() {
m.entry(¤cy, rate);
}
m.finish()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_convert() {
use crate::currency::*;
let mut rates = Rates::<f64, 3>::new();
rates.push(USD, 1.0);
rates.push(EUR, 0.9);
rates.push(ILS, 3.1);
assert_eq!(rates.convert(&1234.0, USD, USD), Some(1234.));
assert_eq!(rates.convert(&1234.0, EUR, EUR), Some(1234.));
assert_eq!(rates.convert(&1234.0, ILS, ILS), Some(1234.));
assert_eq!(rates.convert(&1.0, ILS, EUR), Some(1. / 3.1 * 0.9));
assert_eq!(rates.convert(&1.0, EUR, ILS), Some(1. / 0.9 * 3.1));
}
#[test]
fn test_duplicates() {
use crate::currency::*;
let mut rates = Rates::<f64, 10>::new();
rates.push(USD, 1.0);
rates.push(EUR, 2.0);
assert_eq!(rates.get(USD).unwrap(), &1.0);
rates.push(USD, 3.0);
assert_eq!(rates.get(USD).unwrap(), &3.0);
}
}