use crate::error::PncError;
use crate::value::Value;
use num_bigint::BigInt;
use num_rational::BigRational;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum UnitId {
Celsius,
Fahrenheit,
Kelvin,
Unknown(String),
}
impl fmt::Display for UnitId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UnitId::Celsius => write!(f, "c"),
UnitId::Fahrenheit => write!(f, "f"),
UnitId::Kelvin => write!(f, "k"),
UnitId::Unknown(s) => write!(f, "{}", s),
}
}
}
fn to_rational(v: &Value) -> Result<BigRational, PncError> {
match v {
Value::Int(n) => Ok(BigRational::from_integer(n.clone())),
Value::Rational(r) => Ok(r.clone()),
_ => Err(PncError::InvalidUnitConversion),
}
}
fn kelvin_offset() -> BigRational {
BigRational::new(BigInt::from(27315), BigInt::from(100))
}
fn to_kelvin(value: &Value) -> Result<BigRational, PncError> {
match value {
Value::Unit { number, unit } => {
let n = to_rational(number)?;
match unit {
UnitId::Celsius => Ok(n + kelvin_offset()),
UnitId::Fahrenheit => Ok((n - BigInt::from(32))
* BigRational::new(BigInt::from(5), BigInt::from(9))
+ kelvin_offset()),
UnitId::Kelvin => Ok(n),
_ => Err(PncError::InvalidUnitConversion),
}
}
_ => Err(PncError::InvalidUnitConversion),
}
}
fn rational_to_value(r: BigRational) -> Value {
if r.denom() == &BigInt::from(1) {
Value::Int(r.numer().clone())
} else {
Value::Rational(r)
}
}
fn from_kelvin(k: BigRational, target: UnitId) -> Value {
match target {
UnitId::Celsius => Value::Unit {
number: Box::new(rational_to_value(k - kelvin_offset())),
unit: target,
},
UnitId::Fahrenheit => {
let f = (k - kelvin_offset()) * BigRational::new(BigInt::from(9), BigInt::from(5))
+ BigInt::from(32);
Value::Unit {
number: Box::new(rational_to_value(f)),
unit: target,
}
}
UnitId::Kelvin => Value::Unit {
number: Box::new(rational_to_value(k)),
unit: target,
},
UnitId::Unknown(_) => Value::Unit {
number: Box::new(rational_to_value(k)),
unit: target,
},
}
}
pub fn convert(value: &Value, target: UnitId) -> Result<Value, PncError> {
let k = to_kelvin(value)?;
Ok(from_kelvin(k, target))
}