///|
/// Standard FLAC channel order, RFC 9639 section 9.1.3.
fn default_channel_mask(channels : Int32) -> UInt32 raise FlacError {
  match channels {
    1 => 0x4U
    2 => 0x3U
    3 => 0x7U
    4 => 0x33U
    5 => 0x37U
    6 => 0x3fU
    7 => 0x70fU
    8 => 0x63fU
    _ => raise Invalid(0L, "channel count outside FLAC range")
  }
}

///|
/// Read the optional RFC 9639 channel-layout tag. None means the standard
/// FLAC ordering; Some(0) explicitly leaves every channel's speaker undefined.
/// Fewer named speakers than audio channels is legal and is preserved.
/// Repeated equivalent tags are accepted; conflicting tags are an error.
pub fn Metadata::channel_mask(self : Metadata) -> UInt32? raise FlacError {
  let comments = match self.comments {
    None => return None
    Some(comments) => comments
  }
  let mut position = 4L
  for block in self.blocks {
    if block.kind == 4 {
      break
    }
    position += 4L + block.data.length().to_int64()
  }
  let mut mask : UInt32? = None
  for text in comments.values("WAVEFORMATEXTENSIBLE_CHANNEL_MASK") {
    if text.length() < 3 || !(text.has_prefix("0x") || text.has_prefix("0X")) {
      raise Invalid(
        position, "channel mask must contain 0x followed by hexadecimal digits",
      )
    }
    let mut value = 0U
    for char in text[2:] {
      let code = char.to_int()
      let digit = if code >= 48 && code <= 57 {
        code - 48
      } else if code >= 65 && code <= 70 {
        code - 65 + 10
      } else if code >= 97 && code <= 102 {
        code - 97 + 10
      } else {
        raise Invalid(position, "invalid hexadecimal digit in channel mask")
      }
      if value > 0x0fffffffU {
        raise Invalid(position, "channel mask exceeds unsigned 32-bit range")
      }
      value = (value << 4) | digit.to_uint()
    }
    if mask is Some(previous) {
      if previous != value {
        raise Invalid(position, "conflicting channel mask tags")
      }
    }
    mask = Some(value)
  }
  mask
}

///|
/// Resolve absent layout metadata to the standard FLAC channel assignment.
/// Explicit zero and partial assignments remain unchanged.
pub fn Metadata::effective_channel_mask(
  self : Metadata,
) -> UInt32 raise FlacError {
  match self.channel_mask() {
    Some(mask) => mask
    None => default_channel_mask(self.info.channels)
  }
}