///|
pub(all) enum Format {
  Binary
  Xml
} derive(Eq)

///|
pub extend Format with Eq::{equal, not_equal}

///|
/// Header selection; unsupported OpenStep text is never guessed as a plist.
pub fn detect_format(data : Bytes) -> Format {
  if data.has_prefix(b"bplist00"[:]) {
    Binary
  } else {
    Xml
  }
}

///|
fn xml_bytes(data : Bytes) -> String raise PlistError {
  let n = data.length()
  let be = n >= 2 && data[0] == b'\xfe' && data[1] == b'\xff'
  let le = n >= 2 && data[0] == b'\xff' && data[1] == b'\xfe'
  let guessed_be = n >= 4 &&
    data[0] == b'\x00' &&
    data[1] == b'<' &&
    data[2] == b'\x00' &&
    data[3] != b'\x00'
  let guessed_le = n >= 4 &&
    data[0] == b'<' &&
    data[1] == b'\x00' &&
    data[2] != b'\x00' &&
    data[3] == b'\x00'
  if be || le || guessed_be || guessed_le {
    let start = if be || le { 2 } else { 0 }
    if (n - start) % 2 != 0 {
      raise Invalid("odd UTF16 XML byte length")
    }
    if be || guessed_be {
      return binary_text(data, start, (n - start) / 2, true, n)
    }
    let out = @buffer.Buffer()
    for i = start; i < n; i = i + 2 {
      out.write_byte(data[i + 1])
      out.write_byte(data[i])
    }
    return binary_text(out.to_bytes(), 0, (n - start) / 2, true, n - start)
  }
  @utf8.decode(data[:]) catch {
    _ => raise Invalid("XML must be UTF8 or UTF16")
  }
}

///|
pub fn decode(
  data : Bytes,
  limits~ : Limits = Limits::default(),
) -> Value raise PlistError {
  limits.check()
  if data.length() > limits.max_bytes {
    raise Limit("input byte budget")
  }
  match detect_format(data) {
    Binary => decode_binary(data, limits~)
    Xml => decode_xml(xml_bytes(data), limits~)
  }
}

///|
pub fn encode(
  value : Value,
  format : Format,
  limits~ : Limits = Limits::default(),
) -> Bytes raise PlistError {
  match format {
    Binary => encode_binary(value, limits~)
    Xml => @utf8.encode(encode_xml(value, limits~)[:])
  }
}

///|
pub fn convert(
  data : Bytes,
  format : Format,
  limits~ : Limits = Limits::default(),
) -> Bytes raise PlistError {
  encode(decode(data, limits~), format, limits~)
}