///|
pub suberror MailboxError {
  Invalid(String)
} derive(Debug)

///|
pub(all) struct Message {
  envelope : String
  content : String
} derive(Debug, Eq)

///|
fn from_line(s : String) -> Bool {
  let mut i = 0
  while i < s.length() && s[i].to_int() == 62 {
    i += 1
  }
  s[i:].has_prefix("From ")
}

///|
fn normalize_newlines(s : String) -> String raise MailboxError {
  let out = s.replace_all(old="\r\n", new="\n")
  if out.contains("\r") || out.contains("\u0000") {
    raise Invalid("bare CR or NUL unsupported in text mailbox")
  }
  out
}

///|
/// mboxrd text codec. Content is normalized to LF and one final LF.
/// One blank separator line is removed between messages; MIME bytes are not decoded.
pub fn parse_mboxrd(source : String) -> Array[Message] raise MailboxError {
  if source.length() > 16777216 {
    raise Invalid("mailbox exceeds 16 Mi UTF-16 units")
  }
  let text = normalize_newlines(source)
  if text == "" {
    return []
  }
  if !text.has_prefix("From ") {
    raise Invalid("mbox must start with envelope line")
  }
  let out = []
  let mut envelope = ""
  let mut content = ""
  let lines = text.split("\n").to_array()
  for i in 0..") && from_line(line) {
        line[1:].to_owned()
      } else {
        line
      }
      content += unquoted + "\n"
    }
  }
  if content.has_suffix("\n\n") {
    content = content[:content.length() - 1].to_owned()
  }
  out.push({ envelope, content, })
  out
}

///|
pub fn encode_mboxrd(messages : Array[Message]) -> String raise MailboxError {
  let mut out = ""
  for message in messages {
    if message.envelope.trim() == "" ||
      message.envelope.contains("\n") ||
      message.envelope.contains("\r") ||
      message.envelope.contains("\u0000") {
      raise Invalid("invalid envelope")
    }
    out += "From " + message.envelope + "\n"
    let body = normalize_newlines(message.content)
    let lines = body.split("\n").to_array()
    for i in 0.." } else { "" }) + line + "\n"
    }
    out += "\n"
  }
  out
}