// DNS 抽象与离线 zone。
//
// 核心不做任何网络 I/O:调用方实现 `Resolver` 注入数据,
// 因此同一封邮件在任何机器、任何时间都会得到同一结论。

///|
/// 只读 DNS 解析器。实现者可以是离线 zone,也可以是真实的 DNS 客户端。
pub(open) trait Resolver {
  fn txt(Self, String) -> Array[String]
  fn a(Self, String) -> Array[String]
  fn aaaa(Self, String) -> Array[String]
  fn mx(Self, String) -> Array[String]
  fn ptr(Self, String) -> Array[String]
}

///|
/// 把域名转成小写并去掉末尾的点。
pub fn normalize_name(name : String) -> String {
  let out = StringBuilder()
  let trimmed = if name.has_suffix(".") {
    name[:name.length() - 1].to_owned()
  } else {
    name
  }
  for c in trimmed {
    if c >= 'A' && c <= 'Z' {
      out.write_char(Int::unsafe_to_char(c.to_int() + 32))
    } else {
      out.write_char(c)
    }
  }
  out.to_string()
}

///|
/// 离线 DNS zone。按「名称 + 记录类型」保存记录,供测试与 CI 使用。
pub struct Zone {
  records : Array[(String, String, Array[String])]
}

///|
pub fn Zone::new() -> Zone {
  { records: [], }
}

///|
/// 添加一条记录。名称大小写与末尾点会被归一化。
pub fn Zone::add(
  self : Zone,
  name : String,
  rtype : String,
  values : Array[String],
) -> Unit {
  self.records.push((normalize_name(name), rtype, values))
}

///|
/// 从文本加载 zone。每行格式:`name TYPE value`,value 可以包含空格。
pub fn Zone::from_text(text : StringView) -> Zone {
  let zone = Zone::new()
  for raw_line in text.split("\n") {
    let line = trim_wsp(raw_line)
    if line.length() == 0 {
      continue
    }
    let first = line.find(" ")
    match first {
      None => ()
      Some(i) => {
        let name = line[:i].to_owned()
        let rest = trim_wsp(line[i + 1:])
        let second = rest.find(" ")
        match second {
          None => zone.add(name, rest, [""])
          Some(j) => {
            let rtype = rest[:j].to_owned()
            let value = trim_wsp(rest[j + 1:])
            zone.add(name, rtype, [value])
          }
        }
      }
    }
  }
  zone
}

///|
fn Zone::lookup(self : Zone, name : String, rtype : String) -> Array[String] {
  let key = normalize_name(name)
  let out : Array[String] = []
  for record in self.records {
    if record.0 == key && record.1 == rtype {
      for value in record.2 {
        out.push(value)
      }
    }
  }
  out
}

///|
pub impl Resolver for Zone with fn txt(self, name) {
  self.lookup(name, "TXT")
}

///|
pub impl Resolver for Zone with fn a(self, name) {
  self.lookup(name, "A")
}

///|
pub impl Resolver for Zone with fn aaaa(self, name) {
  self.lookup(name, "AAAA")
}

///|
pub impl Resolver for Zone with fn mx(self, name) {
  self.lookup(name, "MX")
}

///|
pub impl Resolver for Zone with fn ptr(self, name) {
  self.lookup(name, "PTR")
}