///|
/// The capabilities advertised by the server in the EHLO reply.
pub struct SmtpCaps {
  auth : Array[String]
  mut size : Int?
  mut has_8bitmime : Bool
  mut has_smtputf8 : Bool
  mut has_pipelining : Bool
  mut has_starttls : Bool
  raw : Array[String]
} derive(Eq, Debug)

///|
pub fn SmtpCaps::empty() -> SmtpCaps {
  {
    auth: [],
    size: None,
    has_8bitmime: false,
    has_smtputf8: false,
    has_pipelining: false,
    has_starttls: false,
    raw: [],
  }
}

///|
pub fn SmtpCaps::auth(self : SmtpCaps) -> Array[String] {
  self.auth
}

///|
pub fn SmtpCaps::size(self : SmtpCaps) -> Int? {
  self.size
}

///|
pub fn SmtpCaps::has_8bitmime(self : SmtpCaps) -> Bool {
  self.has_8bitmime
}

///|
pub fn SmtpCaps::has_smtputf8(self : SmtpCaps) -> Bool {
  self.has_smtputf8
}

///|
pub fn SmtpCaps::has_pipelining(self : SmtpCaps) -> Bool {
  self.has_pipelining
}

///|
pub fn SmtpCaps::has_starttls(self : SmtpCaps) -> Bool {
  self.has_starttls
}

///|
/// True when the server advertises `mech` in its AUTH capability
/// (case-insensitive).
pub fn SmtpCaps::supports_auth(self : SmtpCaps, mech : String) -> Bool {
  for m in self.auth {
    if m.to_upper() == mech.to_upper() {
      return true
    }
  }
  false
}

///|
/// Parse the EHLO capability lines (the reply lines after the greeting line,
/// without their numeric prefixes). `MM_SMTP_004`.
pub fn parse_caps(lines : Array[String]) -> SmtpCaps {
  let caps = SmtpCaps::empty()
  for raw in lines {
    let line = raw.trim().to_owned()
    let upper = line.to_upper()
    caps.raw.push(line)
    if upper == "8BITMIME" {
      caps.has_8bitmime = true
    } else if upper == "SMTPUTF8" {
      caps.has_smtputf8 = true
    } else if upper == "PIPELINING" {
      caps.has_pipelining = true
    } else if upper == "STARTTLS" {
      caps.has_starttls = true
    } else if upper == "SIZE" {
      caps.size = None
    } else if upper.has_prefix("SIZE ") {
      let rest = line[5:line.length()].to_owned()
      match parse_int(rest) {
        Some(n) => caps.size = Some(n)
        None => caps.size = None
      }
    } else if upper.has_prefix("AUTH") {
      // AUTH LOGIN PLAIN CRAM-MD5  or  AUTH=LOGIN PLAIN
      let rest = if upper.has_prefix("AUTH=") {
        line[5:line.length()].to_owned()
      } else {
        line[4:line.length()].trim().to_owned()
      }
      for mech in rest.split(" ") {
        let m = mech.trim().to_owned()
        if !m.is_empty() {
          caps.auth.push(m.to_upper())
        }
      }
    }
  }
  caps
}

///|
fn parse_int(s : String) -> Int? {
  if s.is_empty() {
    return None
  }
  let mut n = 0
  for b in string_to_bytes(s) {
    let c = b.to_int()
    if c < 0x30 || c > 0x39 {
      return None
    }
    n = n * 10 + (c - 0x30)
  }
  Some(n)
}