use super::types::{Unit, UnitType};
use crate::{FLOAT_EPSILON, MAX_INTEGER_FOR_FORMATTING};
#[derive(Debug, Clone)]
pub struct UnitValue {
pub value: f64,
pub unit: Option<Unit>,
}
impl UnitValue {
pub fn new(value: f64, unit: Option<Unit>) -> Self {
UnitValue { value, unit }
}
pub fn to_unit(&self, target_unit: &Unit) -> Option<UnitValue> {
match &self.unit {
Some(current_unit) => {
if let (
Unit::RateUnit(curr_num, curr_denom),
Unit::RateUnit(targ_num, targ_denom),
) = (current_unit, target_unit)
{
if curr_num.unit_type() == targ_num.unit_type() && curr_num == targ_num {
let curr_denom_base = curr_denom.to_base_value(1.0);
let targ_denom_base = targ_denom.to_base_value(1.0);
let conversion_factor = targ_denom_base / curr_denom_base;
let converted_value = self.value * conversion_factor;
return Some(UnitValue::new(converted_value, Some(target_unit.clone())));
} else if curr_num.unit_type() == UnitType::Currency
&& targ_num.unit_type() == UnitType::Currency
{
return None;
}
}
if current_unit.unit_type() == target_unit.unit_type()
|| self.can_convert_between_data_rates(current_unit, target_unit)
{
let base_value = current_unit.to_base_value(self.value);
let converted_value = target_unit.clone().from_base_value(base_value);
Some(UnitValue::new(converted_value, Some(target_unit.clone())))
}
else if self.can_convert_between_bits_bytes(current_unit, target_unit) {
self.convert_bits_bytes(current_unit, target_unit)
} else {
None }
}
None => {
use super::types::UnitType;
if target_unit.unit_type() == UnitType::Percentage {
let converted_value = target_unit.clone().from_base_value(self.value);
Some(UnitValue::new(converted_value, Some(target_unit.clone())))
} else {
None }
}
}
}
fn can_convert_between_data_rates(&self, current: &Unit, target: &Unit) -> bool {
use super::types::UnitType;
matches!(
(current.unit_type(), target.unit_type()),
(UnitType::DataRate { .. }, UnitType::DataRate { .. })
)
}
fn can_convert_between_bits_bytes(&self, current: &Unit, target: &Unit) -> bool {
use super::types::UnitType;
matches!(
(current.unit_type(), target.unit_type()),
(UnitType::Bit, UnitType::Data)
| (UnitType::Data, UnitType::Bit)
| (UnitType::BitRate, UnitType::DataRate { .. })
| (UnitType::DataRate { .. }, UnitType::BitRate)
)
}
fn convert_bits_bytes(&self, current: &Unit, target: &Unit) -> Option<UnitValue> {
use super::types::UnitType;
match (current.unit_type(), target.unit_type()) {
(UnitType::Bit, UnitType::Data) => {
let bits = current.to_base_value(self.value); let bytes = bits / 8.0; let converted_value = target.clone().from_base_value(bytes);
Some(UnitValue::new(converted_value, Some(target.clone())))
}
(UnitType::Data, UnitType::Bit) => {
let bytes = current.to_base_value(self.value); let bits = bytes * 8.0; let converted_value = target.clone().from_base_value(bits);
Some(UnitValue::new(converted_value, Some(target.clone())))
}
(UnitType::BitRate, UnitType::DataRate { .. }) => {
let bits_per_sec = current.to_base_value(self.value); let bytes_per_sec = bits_per_sec / 8.0; let converted_value = target.clone().from_base_value(bytes_per_sec);
Some(UnitValue::new(converted_value, Some(target.clone())))
}
(UnitType::DataRate { .. }, UnitType::BitRate) => {
let bytes_per_sec = current.to_base_value(self.value); let bits_per_sec = bytes_per_sec * 8.0; let converted_value = target.clone().from_base_value(bits_per_sec);
Some(UnitValue::new(converted_value, Some(target.clone())))
}
_ => None,
}
}
pub fn format(&self) -> String {
let formatted_value =
if self.value.fract() == 0.0 && self.value.abs() < MAX_INTEGER_FOR_FORMATTING {
format_number_with_commas(self.value as i64)
} else {
format_decimal_with_commas(self.value)
};
match &self.unit {
Some(unit) => format!("{} {}", formatted_value, unit.display_name()),
None => formatted_value,
}
}
}
fn format_number_with_commas(num: i64) -> String {
let num_str = num.to_string();
let mut result = String::new();
let chars: Vec<char> = num_str.chars().collect();
let is_negative = chars.first() == Some(&'-');
let start_idx = if is_negative { 1 } else { 0 };
if is_negative {
result.push('-');
}
for (i, ch) in chars[start_idx..].iter().enumerate() {
if i > 0 && (chars.len() - start_idx - i) % 3 == 0 {
result.push(',');
}
result.push(*ch);
}
result
}
fn format_decimal_with_commas(num: f64) -> String {
if num.abs() < FLOAT_EPSILON {
return "0".to_string();
}
let is_negative = num < 0.0;
let abs_num = num.abs();
let formatted = format!("{:.3}", abs_num);
let parts: Vec<&str> = formatted.split('.').collect();
if parts.len() != 2 {
return if is_negative {
format!("-{}", formatted)
} else {
formatted
};
}
let whole_part = parts[0];
let decimal_part = parts[1];
let whole_with_commas = if whole_part == "0" {
"0".to_string()
} else {
let whole_chars: Vec<char> = whole_part.chars().collect();
let mut result = String::new();
for (i, ch) in whole_chars.iter().enumerate() {
if i > 0 && (whole_chars.len() - i) % 3 == 0 {
result.push(',');
}
result.push(*ch);
}
result
};
let decimal_trimmed = decimal_part.trim_end_matches('0');
let formatted_result = if decimal_trimmed.is_empty() {
whole_with_commas
} else {
format!("{}.{}", whole_with_commas, decimal_trimmed)
};
if is_negative {
format!("-{}", formatted_result)
} else {
formatted_result
}
}