///|
/// Parsed STIX identifier: `--`.
pub(all) struct StixId {
  type_name : String
  uuid : String
  text : String
} derive(Eq, Debug)

///|
pub fn parse_stix_id(text : String) -> Result[StixId, String] {
  if text.length() == 0 {
    return Err("empty STIX id")
  }
  match text.find("--") {
    None => Err("STIX id must contain '--'")
    Some(idx) => {
      if idx == 0 {
        return Err("STIX id is missing a type prefix")
      }
      let type_name = slice_to(text, 0, idx)
      let uuid = slice_from(text, idx + 2)
      if !valid_stix_type_name(type_name) {
        return Err("invalid STIX type name '\{type_name}'")
      }
      if !valid_stix_uuid(uuid) {
        return Err("invalid UUID in STIX id '\{text}'")
      }
      Ok({ type_name, uuid, text, })
    }
  }
}

///|
pub fn valid_stix_type_name(name : String) -> Bool {
  if name.length() == 0 {
    return false
  }
  if name.has_prefix("x-") {
    return valid_custom_type_name(name)
  }
  match char_at(name, 0) {
    Some(ch) => if !is_ascii_id_start(ch) { return false }
    None => return false
  }
  for i = 1; i < name.length(); i = i + 1 {
    match char_at(name, i) {
      Some(ch) => if !is_ascii_id_continue(ch) { return false }
      None => return false
    }
  }
  match char_at(name, name.length() - 1) {
    Some('-') => false
    Some(_) => true
    None => false
  }
}

///|
fn valid_custom_type_name(name : String) -> Bool {
  if name.length() < 3 {
    return false
  }
  for i = 2; i < name.length(); i = i + 1 {
    match char_at(name, i) {
      Some(ch) => if !is_ascii_id_continue(ch) { return false }
      None => return false
    }
  }
  match char_at(name, name.length() - 1) {
    Some('-') => false
    Some(_) => true
    None => false
  }
}

///|
pub fn valid_stix_uuid(text : String) -> Bool {
  if text.length() != 36 {
    return false
  }
  let groups = [8, 4, 4, 4, 12]
  let mut pos = 0
  for g = 0; g < groups.length(); g = g + 1 {
    if g > 0 {
      if !char_eq(text, pos, '-') {
        return false
      }
      pos += 1
    }
    let width = groups[g]
    for _i = 0; _i < width; _i = _i + 1 {
      match char_at(text, pos) {
        Some(ch) => if !ch.is_ascii_hexdigit() { return false }
        None => return false
      }
      pos += 1
    }
  }
  pos == text.length()
}

///|
pub fn id_type_matches(id : StixId, expected : String) -> Bool {
  id.type_name == expected
}