///|
fn number(text : String) -> @smithy.NodeNumber {
  @smithy.NodeNumber::parse(text).unwrap()
}

///|
fn integer_bounds(kind : @smithy.SimpleType) -> (String, String)? {
  match kind {
    Byte => Some(("-128", "127"))
    Short => Some(("-32768", "32767"))
    Integer => Some(("-2147483648", "2147483647"))
    Long => Some(("-9223372036854775808", "9223372036854775807"))
    _ => None
  }
}

///|
fn numeric_bounds(
  kind : @smithy.SimpleType,
  value : @smithy.NodeNumber,
) -> Bool {
  if integer_bounds(kind) is Some((min, max)) {
    return value.is_integer() &&
      value.numeric_compare(number(min)) >= 0 &&
      value.numeric_compare(number(max)) <= 0
  }
  if kind == BigInteger {
    return value.is_integer()
  }
  let bound = match kind {
    Float => Some("340282346638528859811704183484516925440")
    Double =>
      Some(
        "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368",
      )
    _ => None
  }
  match bound {
    None => true
    Some(max) =>
      value.numeric_compare(number("-" + max)) >= 0 &&
      value.numeric_compare(number(max)) <= 0
  }
}

///|
fn strict_blob_length(text : String) -> Int32? {
  let bytes = @base64.decode(text, ignore_whitespace=false) catch {
    _ => return None
  }
  if @base64.encode(bytes, padding=true) != text {
    return None
  }
  Some(bytes.length())
}

///|
fn date_time(text : String) -> Bool {
  let mut i = 0
  let digits = fn(start : Int32, count : Int32) -> Int32? {
    if start + count > text.length() {
      return None
    }
    let mut value = 0
    for offset in 0.. 57 {
        return None
      }
      value = value * 10 + c - 48
    }
    Some(value)
  }
  guard text.length() >= 20 &&
    text.code_unit_at(4) == 45 &&
    text.code_unit_at(7) == 45 &&
    (text.code_unit_at(10) == 84 || text.code_unit_at(10) == 116) &&
    text.code_unit_at(13) == 58 &&
    text.code_unit_at(16) == 58 else {
    return false
  }
  guard digits(0, 4) is Some(year) &&
    digits(5, 2) is Some(month) &&
    digits(8, 2) is Some(day) &&
    calendar_day(year, month, day) &&
    digits(11, 2) is Some(hour) &&
    hour < 24 &&
    digits(14, 2) is Some(minute) &&
    minute < 60 &&
    digits(17, 2) is Some(second) &&
    second < 60 else {
    return false
  }
  i = 19
  if text.code_unit_at(i) == 46 {
    i += 1
    let start = i
    while i < text.length() &&
          text.code_unit_at(i) >= 48 &&
          text.code_unit_at(i) <= 57 {
      i += 1
    }
    if i == start {
      return false
    }
  }
  guard i < text.length() else { return false }
  let zone = text.code_unit_at(i)
  if zone == 90 || zone == 122 {
    return i + 1 == text.length()
  }
  guard (zone == 43 || zone == 45) &&
    i + 6 == text.length() &&
    text.code_unit_at(i + 3) == 58 &&
    digits(i + 1, 2) is Some(hours) &&
    hours < 24 &&
    digits(i + 4, 2) is Some(minutes) &&
    minutes < 60 else {
    return false
  }
  true
}