use approx::AbsDiffEq;
use serde::{Deserialize, Serialize};
use struct_iterable::Iterable;
use crate::{
composition::{ArtificialSweeteners, Carbohydrates, Fats, ScaleComponents},
constants,
error::{Error, Result},
};
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Iterable, PartialEq, Serialize, Deserialize, Copy, Clone, Debug)]
#[serde(default, deny_unknown_fields)]
pub struct SolidsBreakdown {
pub fats: Fats,
pub carbohydrates: Carbohydrates,
pub proteins: f64,
pub artificial_sweeteners: ArtificialSweeteners,
pub others: f64,
}
impl SolidsBreakdown {
#[must_use]
pub const fn empty() -> Self {
Self {
fats: Fats::empty(),
carbohydrates: Carbohydrates::empty(),
proteins: 0.0,
artificial_sweeteners: ArtificialSweeteners::empty(),
others: 0.0,
}
}
#[must_use]
pub const fn new() -> Self {
Self::empty()
}
#[must_use]
pub const fn fats(self, fats: Fats) -> Self {
Self { fats, ..self }
}
#[must_use]
pub const fn carbohydrates(self, carbohydrates: Carbohydrates) -> Self {
Self { carbohydrates, ..self }
}
#[must_use]
pub const fn proteins(self, proteins: f64) -> Self {
Self { proteins, ..self }
}
#[must_use]
pub const fn artificial_sweeteners(self, artificial_sweeteners: ArtificialSweeteners) -> Self {
Self {
artificial_sweeteners,
..self
}
}
#[must_use]
pub const fn others(self, others: f64) -> Self {
Self { others, ..self }
}
pub fn others_from_total(&self, total: f64) -> Result<Self> {
if (self.total() - self.others) > total {
return Err(Error::InvalidComposition(format!(
"Cannot set others from total: total {} is less than sum of other components {}",
total,
self.total() - self.others
)));
}
Ok(Self {
others: total - (self.total() - self.others),
..*self
})
}
pub fn energy(&self) -> Result<f64> {
Ok(self.fats.energy()
+ self.carbohydrates.energy()?
+ (self.proteins * constants::energy::PROTEINS)
+ self.artificial_sweeteners.energy()?)
}
}
#[cfg_attr(feature = "wasm", wasm_bindgen)]
impl SolidsBreakdown {
#[allow(clippy::missing_const_for_fn)] #[cfg_attr(coverage, coverage(off))]
#[cfg(feature = "wasm")]
#[wasm_bindgen(constructor)]
#[must_use]
pub fn new_wasm() -> Self {
Self::new()
}
#[must_use]
pub fn total(&self) -> f64 {
self.fats.total + self.carbohydrates.total() + self.proteins + self.artificial_sweeteners.total() + self.others
}
#[must_use]
pub fn snf(&self) -> f64 {
self.total() - self.fats.total
}
#[must_use]
pub fn snfs(&self) -> f64 {
self.snf() - self.carbohydrates.sugars.total()
}
}
impl ScaleComponents for SolidsBreakdown {
fn scale(&self, factor: f64) -> Self {
Self {
fats: self.fats.scale(factor),
carbohydrates: self.carbohydrates.scale(factor),
proteins: self.proteins * factor,
artificial_sweeteners: self.artificial_sweeteners.scale(factor),
others: self.others * factor,
}
}
fn add(&self, other: &Self) -> Self {
Self {
fats: self.fats.add(&other.fats),
carbohydrates: self.carbohydrates.add(&other.carbohydrates),
proteins: self.proteins + other.proteins,
artificial_sweeteners: self.artificial_sweeteners.add(&other.artificial_sweeteners),
others: self.others + other.others,
}
}
}
impl AbsDiffEq for SolidsBreakdown {
type Epsilon = f64;
fn default_epsilon() -> Self::Epsilon {
f64::default_epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.fats.abs_diff_eq(&other.fats, epsilon)
&& self.carbohydrates.abs_diff_eq(&other.carbohydrates, epsilon)
&& self.proteins.abs_diff_eq(&other.proteins, epsilon)
&& self
.artificial_sweeteners
.abs_diff_eq(&other.artificial_sweeteners, epsilon)
&& self.others.abs_diff_eq(&other.others, epsilon)
}
}
impl Default for SolidsBreakdown {
fn default() -> Self {
Self::empty()
}
}