///|
/// Exact IR format release, independent of the library's own release version.
#derive(Eq, Compare, Hash, Debug)
pub struct FormatVersion {
  major : UInt32
  minor : UInt32
  patch : UInt32
}

///|
#derive(Eq, Debug)
pub(all) suberror VersionError {
  InvalidSyntax(String)
  OutOfRange(String)
  UnsupportedMajor(UInt32)
  UnsupportedMinor(UInt32, UInt32)
  FamilyMismatch(UInt32, UInt32)
  SpecsBefore3_1
}

///|
pub fn FormatVersion::new(
  major : UInt32,
  minor : UInt32,
  patch : UInt32,
) -> FormatVersion raise VersionError {
  if major == 0 || (major < 3 && (minor != 0 || patch != 0)) {
    raise InvalidSyntax("Invalid historical format version")
  }
  { major, minor, patch, }
}

///|
fn parse_component(text : String) -> UInt32 raise VersionError {
  if text.is_empty() || (text.length() > 1 && text.has_prefix("0")) {
    raise InvalidSyntax(text)
  }
  let mut value = 0U
  for character in text {
    if character < '0' || character > '9' {
      raise InvalidSyntax(text)
    }
    let digit = (character.to_int() - 48).reinterpret_as_uint()
    if value > (4294967295U - digit) / 10U {
      raise OutOfRange(text)
    }
    value = value * 10U + digit
  }
  value
}

///|
/// Parse a release string. Historical v1 and v2 only have integer wire forms.
pub fn FormatVersion::parse(text : String) -> FormatVersion raise VersionError {
  let components = text.split(".").collect()
  if components.length() != 3 {
    raise InvalidSyntax(text)
  }
  let major = parse_component(components[0].to_owned())
  let minor = parse_component(components[1].to_owned())
  let patch = parse_component(components[2].to_owned())
  if major < 3 {
    raise InvalidSyntax(text)
  }
  FormatVersion::new(major, minor, patch)
}

///|
/// Baseline release for an integer formatVersion.
pub fn FormatVersion::baseline(
  major : UInt32,
) -> FormatVersion raise VersionError {
  FormatVersion::new(major, 0U, 0U)
}

///|
pub fn FormatVersion::to_release_string(self : FormatVersion) -> String {
  "\{self.major}.\{self.minor}.\{self.patch}"
}

///|
/// Model coverage: historical 1 and 2, [3.0.0,3.2.0), [4.0.0,4.1.0).
/// Historical v1 and v2 are supported in addition to the current upstream table.
pub fn FormatVersion::check_supported(
  self : FormatVersion,
) -> Unit raise VersionError {
  match self.major {
    1U | 2U => ()
    3U if self.minor < 2U => ()
    4U if self.minor == 0U => ()
    3U | 4U => raise UnsupportedMinor(self.major, self.minor)
    _ => raise UnsupportedMajor(self.major)
  }
}

///|
pub fn FormatVersion::check_family(
  self : FormatVersion,
  major : UInt32,
) -> Unit raise VersionError {
  self.check_supported()
  if self.major != major {
    raise FamilyMismatch(major, self.major)
  }
}