use hashbrown::DefaultHashBuilder;
use indexmap::set::{IndexSet, IntoIter, Iter};
use serde::Serialize;
use crate::energy::EnergyClass;
use crate::macros::set;
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize)]
#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
pub enum CostTimespan {
Week,
Month,
Year,
}
impl CostTimespan {
const fn name(self) -> &'static str {
match self {
Self::Week => "week",
Self::Month => "month",
Self::Year => "year",
}
}
}
impl core::fmt::Display for CostTimespan {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
self.name().fmt(f)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize)]
#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
pub struct Cost {
#[serde(rename = "usd")]
pub usd_currency: i32,
pub timespan: CostTimespan,
}
impl core::fmt::Display for Cost {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(
f,
"The device {} {} USD in a {} timespan",
if self.usd_currency < 0 {
"saves"
} else {
"spends"
},
self.usd_currency.abs(),
self.timespan
)
}
}
impl Cost {
#[must_use]
pub const fn new(usd_currency: i32, timespan: CostTimespan) -> Self {
Self {
usd_currency,
timespan,
}
}
}
set! {
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
pub struct Costs(IndexSet<Cost, DefaultHashBuilder>);
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize)]
#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
pub struct Roi {
pub years: u8,
#[serde(rename = "energy-class")]
pub energy_class: EnergyClass,
}
impl core::fmt::Display for Roi {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(
f,
"The device has a Return on Investments (Roi) for the `{}` \
energy efficiency class over a timespan of {} {}",
self.energy_class,
self.years,
if self.years > 1 { "years" } else { "year" },
)
}
}
impl Roi {
#[must_use]
pub const fn new(years: u8, energy_class: EnergyClass) -> Self {
let years = match years {
0 => 1,
30.. => 30,
_ => years,
};
Self {
years,
energy_class,
}
}
}
set! {
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
pub struct Rois(IndexSet<Roi, DefaultHashBuilder>);
}
#[derive(Debug, PartialEq, Clone, Serialize)]
#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
pub struct Economy {
#[serde(skip_serializing_if = "Option::is_none")]
pub costs: Option<Costs>,
#[serde(skip_serializing_if = "Option::is_none")]
pub roi: Option<Rois>,
}
impl Economy {
#[must_use]
pub const fn empty() -> Self {
Self {
costs: None,
roi: None,
}
}
#[must_use]
pub const fn init_with_costs(costs: Costs) -> Self {
Self {
costs: Some(costs),
roi: None,
}
}
#[must_use]
pub const fn init_with_roi(roi: Rois) -> Self {
Self {
costs: None,
roi: Some(roi),
}
}
#[must_use]
#[inline]
pub fn costs(mut self, costs: Costs) -> Self {
self.costs = Some(costs);
self
}
#[must_use]
#[inline]
pub fn roi(mut self, roi: Rois) -> Self {
self.roi = Some(roi);
self
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.costs.is_none() && self.roi.is_none()
}
}
#[cfg(test)]
#[cfg(feature = "deserialize")]
mod tests {
use super::Economy;
use crate::energy::EnergyClass;
use crate::{deserialize, serialize};
use super::{Cost, CostTimespan, Costs, Roi, Rois};
#[test]
fn test_cost_timespan() {
for cost_timespan in &[CostTimespan::Week, CostTimespan::Month, CostTimespan::Year] {
assert_eq!(
deserialize::<CostTimespan>(serialize(cost_timespan)),
*cost_timespan
);
}
}
#[test]
fn test_cost() {
let cost = Cost::new(100, CostTimespan::Week);
assert_eq!(deserialize::<Cost>(serialize(cost)), cost);
}
#[test]
fn test_roi_serde() {
let roi = Roi::new(10, EnergyClass::A);
assert_eq!(deserialize::<Roi>(serialize(roi)), roi);
}
#[test]
fn test_roi_clamping() {
assert_eq!(Roi::new(0, EnergyClass::A).years, 1);
assert_eq!(Roi::new(31, EnergyClass::A).years, 30);
assert_eq!(Roi::new(20, EnergyClass::A).years, 20);
}
#[test]
fn test_economy() {
let mut economy = Economy::empty();
let costs = Costs::init(Cost::new(100, CostTimespan::Week))
.insert(Cost::new(1000, CostTimespan::Month));
let roi = Rois::init(Roi::new(10, EnergyClass::A)).insert(Roi::new(20, EnergyClass::B));
assert!(economy.is_empty());
economy = economy.costs(costs).roi(roi);
assert_eq!(deserialize::<Economy>(serialize(&economy)), economy);
}
}