use crate::Points;
use arrayvec::ArrayString;
use serde::de::{self, Deserialize, Visitor};
use serde::ser::Serialize;
use std::convert::TryFrom;
use std::f32;
use std::fmt::{self, Write};
use std::num;
use std::ops;
use std::str::FromStr;
use crate::WeightUnits;
#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, Ord, Eq)]
pub struct WeightKg(i32);
#[derive(Copy, Clone, Debug)]
pub struct WeightAny(i32);
impl Serialize for WeightKg {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if self.0 == 0 {
return serializer.serialize_str("");
}
let mut buf = ArrayString::<13>::new();
let integer = self.0 / 100;
let fraction = self.0.abs() % 100;
write!(buf, "{}", integer).expect("ArrayString overflow");
if fraction != 0 {
if fraction % 10 == 0 {
write!(buf, ".{}", fraction / 10).expect("ArrayString overflow");
} else {
write!(buf, ".{:0>2}", fraction).expect("ArrayString overflow");
}
}
serializer.serialize_str(&buf)
}
}
impl Serialize for WeightAny {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if self.0 == 0 {
return serializer.serialize_str("");
}
let mut buf = ArrayString::<13>::new();
write!(buf, "{}", self).expect("ArrayString overflow");
serializer.serialize_str(&buf)
}
}
impl From<WeightKg> for f32 {
fn from(w: WeightKg) -> f32 {
(w.0 as f32) / 100.0
}
}
impl From<WeightKg> for f64 {
fn from(w: WeightKg) -> f64 {
f64::from(w.0) / 100.0
}
}
impl From<WeightKg> for Option<f64> {
fn from(w: WeightKg) -> Option<f64> {
if w.is_zero() {
None
} else {
Some(w.into())
}
}
}
impl From<WeightAny> for f32 {
fn from(w: WeightAny) -> f32 {
(w.0 as f32) / 100.0
}
}
impl From<WeightAny> for f64 {
fn from(w: WeightAny) -> f64 {
f64::from(w.0) / 100.0
}
}
impl From<WeightAny> for Option<f64> {
fn from(w: WeightAny) -> Option<f64> {
if w.is_zero() {
None
} else {
Some(w.into())
}
}
}
impl WeightKg {
#[inline]
pub fn max_value() -> WeightKg {
WeightKg(<i32>::max_value())
}
#[inline]
pub const fn from_i32(i: i32) -> WeightKg {
WeightKg(i.saturating_mul(100))
}
#[inline]
pub const fn from_raw(i: i32) -> WeightKg {
WeightKg(i)
}
#[inline]
pub fn from_f32(f: f32) -> WeightKg {
if f.is_finite() {
WeightKg((f * 100.0).round() as i32)
} else {
WeightKg(0)
}
}
#[inline]
pub fn from_f64(f: f64) -> WeightKg {
if f.is_finite() {
WeightKg((f * 100.0).round() as i32)
} else {
WeightKg(0)
}
}
#[inline]
pub fn is_failed(self) -> bool {
self < WeightKg::from_i32(0)
}
#[inline]
pub fn is_zero(self) -> bool {
self == WeightKg::from_i32(0)
}
#[inline]
pub fn is_non_zero(self) -> bool {
self != WeightKg::from_i32(0)
}
pub fn as_kg(self) -> WeightAny {
WeightAny(self.0)
}
pub fn as_lbs(self) -> WeightAny {
let f = (self.0.abs() as f32) * 2.2046225;
let mut rounded = f.round() as i32;
if (rounded % 10) == 9 {
rounded += 1;
}
if self.0.is_positive() {
WeightAny(rounded)
} else {
WeightAny(-rounded)
}
}
pub fn as_lbs_class(self) -> WeightAny {
let lbs = self.as_lbs();
let truncated: i32 = (lbs.0 / 100) * 100;
match truncated {
182_00 => WeightAny(183_00),
_ => WeightAny(truncated),
}
}
pub fn as_type(self, unit: WeightUnits) -> WeightAny {
match unit {
WeightUnits::Kg => self.as_kg(),
WeightUnits::Lbs => self.as_lbs(),
}
}
}
impl fmt::Display for WeightKg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
WeightAny(self.0).fmt(f)
}
}
impl ops::Add<WeightKg> for WeightKg {
type Output = WeightKg;
fn add(self, _rhs: WeightKg) -> WeightKg {
WeightKg(self.0 + _rhs.0)
}
}
impl ops::AddAssign for WeightKg {
fn add_assign(&mut self, other: WeightKg) {
*self = *self + other
}
}
impl ops::Sub<WeightKg> for WeightKg {
type Output = WeightKg;
fn sub(self, _rhs: WeightKg) -> WeightKg {
WeightKg(self.0 - _rhs.0)
}
}
impl ops::SubAssign for WeightKg {
fn sub_assign(&mut self, other: WeightKg) {
*self = *self - other
}
}
impl WeightKg {
pub fn abs(self) -> WeightKg {
WeightKg(self.0.abs())
}
}
impl WeightAny {
#[inline]
pub fn is_zero(self) -> bool {
self.0 == 0
}
#[inline]
pub fn is_non_zero(self) -> bool {
!self.is_zero()
}
pub fn format_comma(self) -> String {
if self.0 == 0 {
String::new()
} else {
let integer = self.0 / 100;
let decimal = (self.0.abs() % 100) / 10;
if decimal != 0 {
format!("{},{}", integer, decimal)
} else {
format!("{}", integer)
}
}
}
pub fn as_points(self) -> Points {
Points::from_i32(self.0)
}
}
impl fmt::Display for WeightAny {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.0 == 0 {
Ok(())
} else {
let integer = self.0 / 100;
let decimal = (self.0.abs() % 100) / 10;
if decimal != 0 {
write!(f, "{}.{}", integer, decimal)
} else {
write!(f, "{}", integer)
}
}
}
}
impl FromStr for WeightKg {
type Err = num::ParseFloatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
Ok(WeightKg(0))
} else {
Ok(WeightKg::from_f32(s.parse::<f32>()?))
}
}
}
struct WeightKgVisitor;
impl<'de> Visitor<'de> for WeightKgVisitor {
type Value = WeightKg;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a number or numeric string")
}
fn visit_i64<E: de::Error>(self, i: i64) -> Result<WeightKg, E> {
let v = i32::try_from(i).map_err(E::custom)?;
Ok(WeightKg::from_i32(v))
}
fn visit_u64<E: de::Error>(self, u: u64) -> Result<WeightKg, E> {
let v = i32::try_from(u).map_err(E::custom)?;
Ok(WeightKg::from_i32(v))
}
fn visit_f64<E: de::Error>(self, v: f64) -> Result<WeightKg, E> {
Ok(WeightKg::from_f64(v))
}
fn visit_borrowed_str<E: de::Error>(self, v: &str) -> Result<WeightKg, E> {
WeightKg::from_str(v).map_err(E::custom)
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<WeightKg, E> {
self.visit_borrowed_str(v)
}
}
impl<'de> Deserialize<'de> for WeightKg {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<WeightKg, D::Error> {
deserializer.deserialize_any(WeightKgVisitor)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_weightkg_basic() {
let w = "".parse::<WeightKg>().unwrap();
assert!(w.0 == 0);
let w = "789".parse::<WeightKg>().unwrap();
assert!(w.0 == 78900);
let w = "123.45".parse::<WeightKg>().unwrap();
assert!(w.0 == 12345);
let w = "-123.45".parse::<WeightKg>().unwrap();
assert!(w.0 == -12345);
}
#[test]
fn test_weightkg_f32_edgecases() {
let w = "-0".parse::<WeightKg>().unwrap();
assert!(w.0 == 0);
let w = "NaN".parse::<WeightKg>().unwrap();
assert!(w.0 == 0);
let w = format!("{}", f32::INFINITY).parse::<WeightKg>().unwrap();
assert!(w.0 == 0);
let w = format!("{}", f32::NEG_INFINITY)
.parse::<WeightKg>()
.unwrap();
assert!(w.0 == 0);
}
#[test]
fn test_weightkg_rounding() {
let w = "123.456".parse::<WeightKg>().unwrap();
assert!(w.0 == 12346);
let w = "-123.456".parse::<WeightKg>().unwrap();
assert!(w.0 == -12346);
}
#[test]
fn test_weightkg_as_lbs_rounding() {
let w = "775.64".parse::<WeightKg>().unwrap();
assert_eq!(w.as_lbs().0, 1710_00);
let w = "775.65".parse::<WeightKg>().unwrap();
assert_eq!(w.as_lbs().0, 1710_02);
let w = "197.31".parse::<WeightKg>().unwrap();
assert_eq!(w.as_lbs().0, 435_00);
let w = "109.04".parse::<WeightKg>().unwrap();
assert_eq!(w.as_lbs().0, 240_40);
let w = "317.5".parse::<WeightKg>().unwrap();
assert_eq!(w.as_lbs().0, 699_97);
let w = "340.19".parse::<WeightKg>().unwrap();
assert_eq!(w.as_lbs().0, 750_00);
let w = "-340.19".parse::<WeightKg>().unwrap();
assert_eq!(w.as_lbs().0, -750_00);
}
#[test]
fn test_weightkg_errors() {
assert!("..".parse::<WeightKg>().is_err());
assert!("123.45.6".parse::<WeightKg>().is_err());
assert!("notafloat".parse::<WeightKg>().is_err());
assert!("--123".parse::<WeightKg>().is_err());
}
#[test]
fn test_weightkg_display() {
let w = "123.456".parse::<WeightKg>().unwrap();
assert_eq!(format!("{}", w), "123.4");
let w = "100.456".parse::<WeightKg>().unwrap();
assert_eq!(format!("{}", w), "100.4");
let w = "100.056".parse::<WeightKg>().unwrap();
assert_eq!(format!("{}", w), "100");
let w = "-123.456".parse::<WeightKg>().unwrap();
assert_eq!(format!("{}", w), "-123.4");
let w = "-123.000".parse::<WeightKg>().unwrap();
assert_eq!(format!("{}", w), "-123");
let w = "-0.000".parse::<WeightKg>().unwrap();
assert_eq!(format!("{}", w), "");
}
#[test]
fn test_weightkg_serialize() {
let w = "0.00".parse::<WeightKg>().unwrap();
assert_eq!(w.0, 0_00);
assert_eq!(json!(w), "");
let w = "109.04".parse::<WeightKg>().unwrap(); assert_eq!(w.0, 109_04);
assert_eq!(json!(w), "109.04");
let w = "109.40".parse::<WeightKg>().unwrap();
assert_eq!(w.0, 109_40);
assert_eq!(json!(w), "109.4");
let w = "200.00".parse::<WeightKg>().unwrap();
assert_eq!(w.0, 200_00);
assert_eq!(json!(w), "200");
}
#[test]
fn test_weightkg_ordering() {
let w1 = "100".parse::<WeightKg>().unwrap();
let w2 = "200".parse::<WeightKg>().unwrap();
assert!(w1 < w2);
}
}