use std::num::ParseFloatError;

// a real number described by a specification string. since the structure is
// read-only, we can guarantee that `spec` always specifies `value` in the
// following format
//   ┌──────────────────────────────────────────────────────┬───────────┐
//   │ `spec`                                               │ `value`   │
//   ┝━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━┥
//   │ a string that parses to the floating-point value `x` │ `Some(x)` │
//   ├──────────────────────────────────────────────────────┼───────────┤
//   │ the empty string                                     │ `None`    │
//   └──────────────────────────────────────────────────────┴───────────┘
#[readonly::make]
pub struct SpecifiedValue {
    pub spec: String,
    pub value: Option<f64>
}

impl SpecifiedValue {
    pub fn from_empty_spec() -> SpecifiedValue {
        SpecifiedValue { spec: String::new(), value: None }
    }
    
    pub fn is_present(&self) -> bool {
        matches!(self.value, Some(_))
    }
}

// a `SpecifiedValue` can be constructed from a specification string, formatted
// as described in the comment on the structure definition. the result is `Ok`
// if the specification is properly formatted, and `Error` if not
impl TryFrom<String> for SpecifiedValue {
    type Error = ParseFloatError;
    
    fn try_from(spec: String) -> Result<Self, Self::Error> {
        if spec.is_empty() {
            Ok(SpecifiedValue::from_empty_spec())
        } else {
            spec.parse::<f64>().map(
                |value| SpecifiedValue { spec: spec, value: Some(value) }
            )
        }
    }
}