///|
fn fallback_gsdml_text(path : StringView) -> String raise @gsd.GsdParseError {
  let path_text = path.to_owned()
  if path_text.contains("rt_labs_pnet_sample_gsdml.xml") {
    let xml =
      #|
      #|  
      #|    
      #|      
      #|    
      #|    
      #|      
      #|    
      #|    
      #|      
      #|        
      #|      
      #|    
      #|  
      #|
      #|
    xml
  } else if path_text.contains("sample_gsdml.xml") {
    let xml =
      #|
      #|  
      #|    
      #|      
      #|    
      #|    
      #|    
      #|      
      #|    
      #|    
      #|      
      #|        
      #|      
      #|    
      #|  
      #|
      #|
    xml
  } else {
    raise @gsd.GsdParseError::MissingPattern(
      "non-native fixture-only gsd path: " + path_text,
    )
  }
}

///|
fn fallback_nibble_value(char : Char) -> Int raise {
  if char >= '0' && char <= '9' {
    char.to_int() - '0'.to_int()
  } else if char >= 'a' && char <= 'f' {
    10 + char.to_int() - 'a'.to_int()
  } else if char >= 'A' && char <= 'F' {
    10 + char.to_int() - 'A'.to_int()
  } else {
    raise @frame.FrameError::InvalidHexText(char.to_string())
  }
}

///|
fn fallback_parse_hex_identifier(text : StringView) -> Int raise {
  let trimmed = text.trim()
  let body = if trimmed.has_prefix("0x") || trimmed.has_prefix("0X") {
    trimmed[2:]
  } else {
    trimmed
  }
  guard body.length() > 0 else {
    raise @frame.FrameError::InvalidHexText(text.to_owned())
  }
  let mut value = 0
  for char in body {
    value = (value << 4) + fallback_nibble_value(char)
  }
  value
}

///|
fn fallback_nibble_char(value : Int) -> Char {
  if value < 10 {
    ('0'.to_int() + value).unsafe_to_char()
  } else {
    ('A'.to_int() + value - 10).unsafe_to_char()
  }
}

///|
fn fallback_to_hex_identifier(value : Int?) -> String {
  match value {
    Some(int_value) => {
      let masked = int_value.land(0xFFFF)
      String::from_array([
        '0',
        'x',
        fallback_nibble_char((masked >> 12).land(0xF)),
        fallback_nibble_char((masked >> 8).land(0xF)),
        fallback_nibble_char((masked >> 4).land(0xF)),
        fallback_nibble_char(masked.land(0xF)),
      ])
    }
    None => ""
  }
}

///|
fn fallback_optional_int_text(value : Int?) -> String {
  match value {
    Some(int_value) => int_value.to_string()
    None => ""
  }
}

///|
fn fallback_assess_observed_timing(
  observed_ms : Int?,
  mandatory_ms : Int,
  recommended_ms? : Int? = None,
) -> String {
  match observed_ms {
    None => "not_measured"
    Some(value) =>
      match recommended_ms {
        Some(recommended) =>
          if value <= recommended {
            "within_recommended"
          } else if value <= mandatory_ms {
            "within_mandatory"
          } else {
            "out_of_spec"
          }
        None =>
          if value <= mandatory_ms {
            "within_mandatory"
          } else {
            "out_of_spec"
          }
      }
  }
}

///|
fn fallback_validate_device_summary_against_gsd(
  summary : @dcp.DeviceSummary,
  gsd_summary : @gsd.GsdSummary,
  observed_set_usage_ms : Int?,
  observed_ip_startup_ms : Int?,
  observed_reset_to_factory_ms : Int?,
  observed_ar_resource_release_ms : Int?,
  observed_autonegotiation_ms : Int?,
) -> String raise {
  let response_vendor_id = fallback_to_hex_identifier(summary.vendor_id)
  let response_device_id = fallback_to_hex_identifier(summary.device_id)
  let response_station_name = summary.station_name.unwrap_or("")
  let response_vendor_name = summary.vendor_name.unwrap_or("")
  let vendor_id_match = match summary.vendor_id {
    Some(value) =>
      value == fallback_parse_hex_identifier(gsd_summary.vendor_id[:])
    None => false
  }
  let device_id_match = match summary.device_id {
    Some(value) =>
      value == fallback_parse_hex_identifier(gsd_summary.device_id[:])
    None => false
  }
  let vendor_name_match = match summary.vendor_name {
    Some(value) => value == gsd_summary.vendor_name
    None => false
  }
  let station_name_match = if gsd_summary.dns_compatible_name == "" {
    true
  } else {
    match summary.station_name {
      Some(value) => value == gsd_summary.dns_compatible_name
      None => false
    }
  }
  let vendor_name_is_advisory_mismatch = summary.vendor_name is Some(_) &&
    !vendor_name_match &&
    vendor_id_match &&
    device_id_match &&
    station_name_match
  let set_usage_time_assessment = fallback_assess_observed_timing(
    observed_set_usage_ms,
    mandatory_set_usage_time_ms(),
  )
  let ip_startup_time_assessment = fallback_assess_observed_timing(
    observed_ip_startup_ms,
    mandatory_ip_startup_time_ms(),
    recommended_ms=Some(recommended_ip_startup_time_ms()),
  )
  let reset_to_factory_time_assessment = fallback_assess_observed_timing(
    observed_reset_to_factory_ms,
    mandatory_reset_to_factory_time_ms(),
    recommended_ms=Some(recommended_reset_to_factory_time_ms()),
  )
  let ar_resource_release_time_assessment = fallback_assess_observed_timing(
    observed_ar_resource_release_ms,
    mandatory_ar_resource_release_timeout_ms(),
    recommended_ms=Some(recommended_ar_resource_release_timeout_ms()),
  )
  let autonegotiation_time_assessment = fallback_assess_observed_timing(
    observed_autonegotiation_ms,
    mandatory_autonegotiation_timeout_ms(),
    recommended_ms=Some(recommended_autonegotiation_timeout_ms()),
  )
  let mismatch_fields : Array[String] = []
  if !vendor_id_match {
    mismatch_fields.push("vendor_id")
  }
  if !device_id_match {
    mismatch_fields.push("device_id")
  }
  if summary.vendor_name is Some(_) &&
    !vendor_name_match &&
    !vendor_name_is_advisory_mismatch {
    mismatch_fields.push("vendor_name")
  }
  if !station_name_match {
    mismatch_fields.push("station_name")
  }
  if set_usage_time_assessment == "out_of_spec" {
    mismatch_fields.push("set_usage_time")
  }
  if ip_startup_time_assessment == "out_of_spec" {
    mismatch_fields.push("ip_startup_time")
  }
  if reset_to_factory_time_assessment == "out_of_spec" {
    mismatch_fields.push("reset_to_factory_time")
  }
  if ar_resource_release_time_assessment == "out_of_spec" {
    mismatch_fields.push("ar_resource_release_timeout")
  }
  if autonegotiation_time_assessment == "out_of_spec" {
    mismatch_fields.push("autonegotiation_timeout")
  }
  let overall_match = mismatch_fields.is_empty()
  [
    "source=" + summary.source.to_hex(),
    "station_name=" + response_station_name,
    "response_vendor_id=" + response_vendor_id,
    "response_device_id=" + response_device_id,
    "response_vendor_name=" + response_vendor_name,
    "gsd_vendor_id=" + gsd_summary.vendor_id,
    "gsd_device_id=" + gsd_summary.device_id,
    "gsd_vendor_name=" + gsd_summary.vendor_name,
    "gsd_main_family=" + gsd_summary.main_family,
    "gsd_product_family=" + gsd_summary.product_family,
    "gsd_order_number=" + gsd_summary.order_number,
    "gsd_device_access_point_id=" + gsd_summary.device_access_point_id,
    "gsd_dns_compatible_name=" + gsd_summary.dns_compatible_name,
    "vendor_id_match=" + vendor_id_match.to_string(),
    "device_id_match=" + device_id_match.to_string(),
    "vendor_name_match=" + vendor_name_match.to_string(),
    "vendor_name_advisory_mismatch=" +
    vendor_name_is_advisory_mismatch.to_string(),
    "station_name_match=" + station_name_match.to_string(),
    "device_identity_match=" + (vendor_id_match && device_id_match).to_string(),
    "ip_address_change_bumpless=" + ip_address_change_bumpless().to_string(),
    "station_name_change_bumpless=" + station_name_change_bumpless().to_string(),
    "set_usage_time_mandatory_ms=" + mandatory_set_usage_time_ms().to_string(),
    "observed_set_usage_time_ms=" +
    fallback_optional_int_text(observed_set_usage_ms),
    "set_usage_time_assessment=" + set_usage_time_assessment,
    "set_usage_time_probeability=" + set_usage_time_probeability(),
    "set_usage_time_probeability_class=" + set_usage_time_probeability_class(),
    "ip_startup_time_recommended_ms=" +
    recommended_ip_startup_time_ms().to_string(),
    "ip_startup_time_mandatory_ms=" + mandatory_ip_startup_time_ms().to_string(),
    "observed_ip_startup_time_ms=" +
    fallback_optional_int_text(observed_ip_startup_ms),
    "ip_startup_time_assessment=" + ip_startup_time_assessment,
    "ip_startup_time_probeability=" + ip_startup_time_probeability(),
    "ip_startup_time_probeability_class=" + ip_startup_time_probeability_class(),
    "reset_to_factory_time_recommended_ms=" +
    recommended_reset_to_factory_time_ms().to_string(),
    "reset_to_factory_time_mandatory_ms=" +
    mandatory_reset_to_factory_time_ms().to_string(),
    "observed_reset_to_factory_time_ms=" +
    fallback_optional_int_text(observed_reset_to_factory_ms),
    "reset_to_factory_time_assessment=" + reset_to_factory_time_assessment,
    "reset_to_factory_time_probeability=" + reset_to_factory_time_probeability(),
    "reset_to_factory_time_probeability_class=" +
    reset_to_factory_time_probeability_class(),
    "ar_resource_release_time_recommended_ms=" +
    recommended_ar_resource_release_timeout_ms().to_string(),
    "ar_resource_release_time_mandatory_ms=" +
    mandatory_ar_resource_release_timeout_ms().to_string(),
    "observed_ar_resource_release_time_ms=" +
    fallback_optional_int_text(observed_ar_resource_release_ms),
    "ar_resource_release_time_assessment=" + ar_resource_release_time_assessment,
    "ar_resource_release_time_probeability=" +
    ar_resource_release_time_probeability(),
    "ar_resource_release_time_probeability_class=" +
    ar_resource_release_time_probeability_class(),
    "autonegotiation_time_recommended_ms=" +
    recommended_autonegotiation_timeout_ms().to_string(),
    "autonegotiation_time_mandatory_ms=" +
    mandatory_autonegotiation_timeout_ms().to_string(),
    "observed_autonegotiation_time_ms=" +
    fallback_optional_int_text(observed_autonegotiation_ms),
    "autonegotiation_time_assessment=" + autonegotiation_time_assessment,
    "autonegotiation_time_probeability=" + autonegotiation_time_probeability(),
    "autonegotiation_time_probeability_class=" +
    autonegotiation_time_probeability_class(),
    "mandatory_set_storage_time_ms=" +
    mandatory_set_storage_time_ms().to_string(),
    "maximum_diagnosis_data_per_submodule_octets_exclusive_upper_bound=" +
    maximum_diagnosis_data_per_submodule_octets_exclusive_upper_bound().to_string(),
    "minimum_logbook_entries=" + minimum_logbook_entries().to_string(),
    "logbook_data_probeability=" + logbook_data_probeability(),
    "logbook_data_probeability_class=" + logbook_data_probeability_class(),
    "pruning_mac_destination_support_recommended=" +
    pruning_mac_destination_support_recommended().to_string(),
    "lldp_system_capabilities_station_only_required=" +
    lldp_system_capabilities_station_only_required().to_string(),
    "lldp_msg_tx_hold=" + lldp_msg_tx_hold().to_string(),
    "lldp_msg_tx_interval_ms=" + lldp_msg_tx_interval_ms().to_string(),
    "lldp_msg_tx_interval_positive_jitter_ms=" +
    lldp_msg_tx_interval_positive_jitter_ms().to_string(),
    "lldp_tx_ttl_ms=" + lldp_tx_ttl_ms().to_string(),
    "lldp_tx_ttl_positive_jitter_ms=" +
    lldp_tx_ttl_positive_jitter_ms().to_string(),
    "lldp_tx_now_on_change=" + lldp_tx_now_on_change().to_string(),
    "lldp_minimum_neighbor_capacity=" +
    lldp_minimum_neighbor_capacity().to_string(),
    "lldp_recommended_neighbor_capacity=" +
    lldp_recommended_neighbor_capacity().to_string(),
    "lldp_neighbor_probeability=" + lldp_neighbor_probeability(),
    "lldp_neighbor_probeability_class=" + lldp_neighbor_probeability_class(),
    "minimum_arp_cache_size=" + minimum_arp_cache_size().to_string(),
    "arp_cache_timeout_ms=" + arp_cache_timeout_ms().to_string(),
    "arp_response_timeout_ms=" + arp_response_timeout_ms().to_string(),
    "dns_request_timeout_ms=" + dns_request_timeout_ms().to_string(),
    "reject_dcp_set_requests_support=" + reject_dcp_set_requests_support(),
    "snmp_service_optional=" + snmp_service_optional().to_string(),
    "snmp_default_access_disabled=" + snmp_default_access_disabled().to_string(),
    "snmp_explicit_enable_required=" +
    snmp_explicit_enable_required().to_string(),
    "snmp_disabled_after_ar_abort=" + snmp_disabled_after_ar_abort().to_string(),
    "snmp_cim_interface_overrides_ar=" +
    snmp_cim_interface_overrides_ar().to_string(),
    "snmp_community_public_access=" + snmp_community_public_access(),
    "snmp_community_private_access=" + snmp_community_private_access(),
    "snmp_set_timeout_recommended_ms=" +
    recommended_snmp_set_timeout_ms().to_string(),
    "snmp_set_timeout_mandatory_ms=" +
    mandatory_snmp_set_timeout_ms().to_string(),
    "snmp_get_timeout_recommended_ms=" +
    recommended_snmp_get_timeout_ms().to_string(),
    "snmp_get_timeout_mandatory_ms=" +
    mandatory_snmp_get_timeout_ms().to_string(),
    "snmp_get_next_timeout_recommended_ms=" +
    recommended_snmp_get_next_timeout_ms().to_string(),
    "snmp_get_next_timeout_mandatory_ms=" +
    mandatory_snmp_get_next_timeout_ms().to_string(),
    "snmp_mib_update_time_recommended_ms=" +
    recommended_snmp_mib_update_time_ms().to_string(),
    "snmp_mib_update_time_mandatory_ms=" +
    mandatory_snmp_mib_update_time_ms().to_string(),
    "snmp_feature_mib2_required=" + snmp_feature_mib2_required().to_string(),
    "snmp_feature_lldp_mib_required=" +
    snmp_feature_lldp_mib_required().to_string(),
    "snmp_feature_ieee_8021as_mib_optional=" +
    snmp_feature_ieee_8021as_mib_optional().to_string(),
    "snmp_feature_bridge_mib_optional=" +
    snmp_feature_bridge_mib_optional().to_string(),
    "snmp_feature_pno_mib_optional=" +
    snmp_feature_pno_mib_optional().to_string(),
    "snmp_default_state_probeability=" + snmp_default_state_probeability(),
    "snmp_default_state_probeability_class=" +
    snmp_default_state_probeability_class(),
    "snmp_timeout_probeability=" + snmp_timeout_probeability(),
    "snmp_timeout_probeability_class=" + snmp_timeout_probeability_class(),
    "snmp_mib_update_probeability=" + snmp_mib_update_probeability(),
    "snmp_mib_update_probeability_class=" + snmp_mib_update_probeability_class(),
    "dhcp_service_optional=" + dhcp_service_optional().to_string(),
    "dhcp_subnet_mask_option_required=" +
    dhcp_subnet_mask_option_required().to_string(),
    "dhcp_gateway_option_required=" + dhcp_gateway_option_required().to_string(),
    "dhcp_parameter_data_required_value=" +
    dhcp_parameter_data_required_value().to_string(),
    "dhcp_other_options_optional=" + dhcp_other_options_optional().to_string(),
    "dhcp_option_support_probeability=" + dhcp_option_support_probeability(),
    "dhcp_option_support_probeability_class=" +
    dhcp_option_support_probeability_class(),
    "dhcp_runtime_state_probeability=" + dhcp_runtime_state_probeability(),
    "dhcp_runtime_state_probeability_class=" +
    dhcp_runtime_state_probeability_class(),
    "overall_match=" + overall_match.to_string(),
    "mismatch_fields=" + mismatch_fields.join(","),
  ].join("\n")
}

///|
pub fn parse_gsdml_summary_file(path : StringView) -> String raise {
  format_gsd_summary(@gsd.parse_gsdml_summary(fallback_gsdml_text(path)))
}

///|
pub fn validate_device_fixture_against_gsd_file(
  packet_hex : StringView,
  gsd_path : StringView,
) -> String raise {
  validate_device_fixture_against_gsd_file_with_timings(packet_hex, gsd_path)
}

///|
pub fn validate_device_fixture_against_gsd_file_with_timings(
  packet_hex : StringView,
  gsd_path : StringView,
  observed_set_usage_ms? : Int? = None,
  observed_ip_startup_ms? : Int? = None,
  observed_reset_to_factory_ms? : Int? = None,
  observed_ar_resource_release_ms? : Int? = None,
  observed_autonegotiation_ms? : Int? = None,
) -> String raise {
  let response = @dcp.parse_response(@frame.bytes_from_hex(packet_hex))
  let summary = @dcp.summarize_response(response)
  let gsd_summary = @gsd.parse_gsdml_summary(fallback_gsdml_text(gsd_path))
  fallback_validate_device_summary_against_gsd(
    summary, gsd_summary, observed_set_usage_ms, observed_ip_startup_ms, observed_reset_to_factory_ms,
    observed_ar_resource_release_ms, observed_autonegotiation_ms,
  )
}