///|
/// The route-origin subset of one RFC 8416 SLURM document.
pub(all) struct SlurmPolicy {
  filters : Array[SlurmPrefixFilter]
  assertions : Array[Vrp]
  ipv6_assertions : Array[Ipv6Vrp]
  assertion_is_ipv6 : Array[Bool]
} derive(Eq, Debug)

///|
pub fn SlurmPolicy::apply(
  self : SlurmPolicy,
  validated : Array[Vrp],
) -> SlurmApplyResult {
  apply_slurm(validated, self.filters, self.assertions)
}

///|
/// Apply this policy's ASN-only and IPv6 prefix rules to IPv6 VRPs.
pub fn SlurmPolicy::apply_ipv6(
  self : SlurmPolicy,
  validated : Array[Ipv6Vrp],
) -> Ipv6SlurmApplyResult {
  apply_ipv6_slurm(validated, self.filters, self.ipv6_assertions)
}

///|
priv enum SlurmParsedPrefix {
  SlurmIpv4(Ipv4Prefix)
  SlurmIpv6(Ipv6Prefix)
}

///|
priv enum SlurmParsedAssertion {
  SlurmIpv4Assertion(Vrp)
  SlurmIpv6Assertion(Ipv6Vrp)
}

///|
fn json_object(
  value : Json,
  path : String,
) -> Result[Map[String, Json], String] {
  match value {
    Object(members) => Ok(members)
    _ => Err("\{path} must be an object")
  }
}

///|
fn json_array(value : Json, path : String) -> Result[Array[Json], String] {
  match value {
    Array(items) => Ok(items)
    _ => Err("\{path} must be an array")
  }
}

///|
fn required_member(
  members : Map[String, Json],
  key : String,
  path : String,
) -> Result[Json, String] {
  match members.get(key) {
    Some(value) => Ok(value)
    None => Err("\{path}.\{key} is required")
  }
}

///|
fn reject_unknown_members(
  members : Map[String, Json],
  allowed : Array[String],
  path : String,
) -> Result[Unit, String] {
  for key, _ in members {
    if !allowed.contains(key) {
      return Err("unknown member \{path}.\{key}")
    }
  }
  Ok(())
}

///|
fn json_uint(value : Json, path : String) -> Result[UInt, String] {
  match value {
    Number(number, ..) => {
      if number < 0.0 || number > 4294967295.0 || number.trunc() != number {
        return Err("\{path} must be an unsigned 32-bit integer")
      }
      Ok(number.to_uint())
    }
    _ => Err("\{path} must be an unsigned 32-bit integer")
  }
}

///|
fn optional_comment(
  members : Map[String, Json],
  path : String,
) -> Result[String, String] {
  match members.get("comment") {
    None => Ok("")
    Some(String(comment)) => Ok(comment)
    Some(_) => Err("\{path}.comment must be a string")
  }
}

///|
fn optional_prefix(
  members : Map[String, Json],
  path : String,
) -> Result[SlurmParsedPrefix?, String] {
  match members.get("prefix") {
    None => Ok(None)
    Some(String(text)) => {
      if contains_colon(text) {
        return match Ipv6Prefix::parse(text) {
          Ok(prefix) => Ok(Some(SlurmIpv6(prefix)))
          Err(message) => Err("\{path}.prefix: \{message}")
        }
      }
      match Ipv4Prefix::parse(text) {
        Ok(prefix) => Ok(Some(SlurmIpv4(prefix)))
        Err(message) => Err("\{path}.prefix: \{message}")
      }
    }
    Some(_) => Err("\{path}.prefix must be a string")
  }
}

///|
fn optional_asn(
  members : Map[String, Json],
  path : String,
) -> Result[UInt?, String] {
  match members.get("asn") {
    None => Ok(None)
    Some(value) =>
      match json_uint(value, "\{path}.asn") {
        Ok(asn) => Ok(Some(asn))
        Err(message) => Err(message)
      }
  }
}

///|
fn parse_prefix_filter(
  value : Json,
  index : Int,
) -> Result[SlurmPrefixFilter, String] {
  let path = "validationOutputFilters.prefixFilters[\{index}]"
  let members = match json_object(value, path) {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  match reject_unknown_members(members, ["prefix", "asn", "comment"], path) {
    Err(message) => return Err(message)
    Ok(_) => ()
  }
  let prefix = match optional_prefix(members, path) {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  let asn = match optional_asn(members, path) {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  let comment = match optional_comment(members, path) {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  match (prefix, asn) {
    (Some(SlurmIpv4(prefix)), Some(asn)) =>
      Ok(SlurmPrefixFilter::by_prefix_and_asn(prefix, asn, comment~))
    (Some(SlurmIpv4(prefix)), None) =>
      Ok(SlurmPrefixFilter::by_prefix(prefix, comment~))
    (Some(SlurmIpv6(prefix)), Some(asn)) =>
      Ok(SlurmPrefixFilter::by_ipv6_prefix_and_asn(prefix, asn, comment~))
    (Some(SlurmIpv6(prefix)), None) =>
      Ok(SlurmPrefixFilter::by_ipv6_prefix(prefix, comment~))
    (None, Some(asn)) => Ok(SlurmPrefixFilter::by_asn(asn, comment~))
    (None, None) => Err("\{path} must contain prefix, asn, or both")
  }
}

///|
fn parse_prefix_assertion(
  value : Json,
  index : Int,
) -> Result[SlurmParsedAssertion, String] {
  let path = "locallyAddedAssertions.prefixAssertions[\{index}]"
  let members = match json_object(value, path) {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  match
    reject_unknown_members(
      members,
      ["prefix", "asn", "maxPrefixLength", "comment"],
      path,
    ) {
    Err(message) => return Err(message)
    Ok(_) => ()
  }
  let prefix = match optional_prefix(members, path) {
    Ok(Some(value)) => value
    Ok(None) => return Err("\{path}.prefix is required")
    Err(message) => return Err(message)
  }
  let asn = match optional_asn(members, path) {
    Ok(Some(value)) => value
    Ok(None) => return Err("\{path}.asn is required")
    Err(message) => return Err(message)
  }
  match optional_comment(members, path) {
    Err(message) => return Err(message)
    Ok(_) => ()
  }
  let max_length = match members.get("maxPrefixLength") {
    None => None
    Some(value) =>
      match json_uint(value, "\{path}.maxPrefixLength") {
        Ok(length) => Some(length.reinterpret_as_int())
        Err(message) => return Err(message)
      }
  }
  match prefix {
    SlurmIpv4(prefix) =>
      match slurm_assertion(prefix, asn, max_length?) {
        Ok(value) => Ok(SlurmIpv4Assertion(value))
        Err(message) => Err("\{path}: \{message}")
      }
    SlurmIpv6(prefix) =>
      match ipv6_slurm_assertion(prefix, asn, max_length?) {
        Ok(value) => Ok(SlurmIpv6Assertion(value))
        Err(message) => Err("\{path}: \{message}")
      }
  }
}

///|
fn require_empty_bgpsec(value : Json, path : String) -> Result[Unit, String] {
  match json_array(value, path) {
    Ok([]) => Ok(())
    Ok(_) => Err("\{path} is not supported yet")
    Err(message) => Err(message)
  }
}

///|
/// Parse the route-origin subset of an RFC 8416 SLURM JSON file.
///
/// The document is rejected atomically on malformed JSON, unknown members,
/// unsupported versions, invalid prefixes, or non-empty BGPsec sections.
pub fn parse_slurm_json(source : StringView) -> Result[SlurmPolicy, String] {
  let document = @json.parse(source) catch { _ => return Err("invalid JSON") }
  let root = match json_object(document, "SLURM document") {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  match
    reject_unknown_members(
      root,
      ["slurmVersion", "validationOutputFilters", "locallyAddedAssertions"],
      "SLURM document",
    ) {
    Err(message) => return Err(message)
    Ok(_) => ()
  }
  let version_value = match
    required_member(root, "slurmVersion", "SLURM document") {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  match json_uint(version_value, "slurmVersion") {
    Ok(1U) => ()
    Ok(_) => return Err("slurmVersion must be 1")
    Err(message) => return Err(message)
  }
  let filters_value = match
    required_member(root, "validationOutputFilters", "SLURM document") {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  let filter_object = match
    json_object(filters_value, "validationOutputFilters") {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  match
    reject_unknown_members(
      filter_object,
      ["prefixFilters", "bgpsecFilters"],
      "validationOutputFilters",
    ) {
    Err(message) => return Err(message)
    Ok(_) => ()
  }
  let prefix_filters_value = match
    required_member(filter_object, "prefixFilters", "validationOutputFilters") {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  let prefix_filter_items = match
    json_array(prefix_filters_value, "validationOutputFilters.prefixFilters") {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  let filters : Array[SlurmPrefixFilter] = []
  for index, item in prefix_filter_items {
    match parse_prefix_filter(item, index) {
      Ok(filter) => filters.push(filter)
      Err(message) => return Err(message)
    }
  }
  let bgpsec_filters = match
    required_member(filter_object, "bgpsecFilters", "validationOutputFilters") {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  match
    require_empty_bgpsec(
      bgpsec_filters, "validationOutputFilters.bgpsecFilters",
    ) {
    Err(message) => return Err(message)
    Ok(_) => ()
  }
  let assertions_value = match
    required_member(root, "locallyAddedAssertions", "SLURM document") {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  let assertion_object = match
    json_object(assertions_value, "locallyAddedAssertions") {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  match
    reject_unknown_members(
      assertion_object,
      ["prefixAssertions", "bgpsecAssertions"],
      "locallyAddedAssertions",
    ) {
    Err(message) => return Err(message)
    Ok(_) => ()
  }
  let prefix_assertions_value = match
    required_member(
      assertion_object, "prefixAssertions", "locallyAddedAssertions",
    ) {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  let prefix_assertion_items = match
    json_array(
      prefix_assertions_value, "locallyAddedAssertions.prefixAssertions",
    ) {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  let assertions : Array[Vrp] = []
  let ipv6_assertions : Array[Ipv6Vrp] = []
  let assertion_is_ipv6 : Array[Bool] = []
  for index, item in prefix_assertion_items {
    match parse_prefix_assertion(item, index) {
      Ok(SlurmIpv4Assertion(assertion)) => {
        assertions.push(assertion)
        assertion_is_ipv6.push(false)
      }
      Ok(SlurmIpv6Assertion(assertion)) => {
        ipv6_assertions.push(assertion)
        assertion_is_ipv6.push(true)
      }
      Err(message) => return Err(message)
    }
  }
  let bgpsec_assertions = match
    required_member(
      assertion_object, "bgpsecAssertions", "locallyAddedAssertions",
    ) {
    Ok(value) => value
    Err(message) => return Err(message)
  }
  match
    require_empty_bgpsec(
      bgpsec_assertions, "locallyAddedAssertions.bgpsecAssertions",
    ) {
    Err(message) => return Err(message)
    Ok(_) => ()
  }
  Ok({ filters, assertions, ipv6_assertions, assertion_is_ipv6, })
}