///|
fn token_requires_quotes(text : String) -> Bool {
  if text.length() == 0 {
    return true
  }
  for index = 0; index < text.length(); index = index + 1 {
    let code = text[index].to_int()
    if code <= 32 ||
      code == 34 ||
      code == 40 ||
      code == 41 ||
      code == 59 ||
      code == 92 {
      return true
    }
  }
  false
}

///|
fn quote_zone_text(text : String) -> String {
  let mut result = "\""
  for index = 0; index < text.length(); index = index + 1 {
    let code = text[index].to_int()
    if code == 34 || code == 92 || code == 59 {
      result = result + "\\" + ascii_character(code)
    } else if code < 32 || code > 126 {
      let hundreds = code / 100
      let tens = code / 10 % 10
      let ones = code % 10
      result = result +
        "\\" +
        hundreds.to_string() +
        tens.to_string() +
        ones.to_string()
    } else {
      result = result + ascii_character(code)
    }
  }
  result + "\""
}

///|
fn render_zone_token(text : String) -> String {
  if token_requires_quotes(text) {
    quote_zone_text(text)
  } else {
    text
  }
}

///|
fn join_rendered_fields(fields : Array[String]) -> String {
  let mut result = ""
  for index, field in fields {
    if index > 0 {
      result = result + " "
    }
    result = result + field
  }
  result
}

///|
pub fn render_record_data(data : RecordData) -> String {
  match data {
    AData(value) => value.to_text()
    AaaaData(value) => value.to_text()
    NsData(value) | CnameData(value) | PtrData(value) => value.to_text()
    MxData(value) =>
      value.preference().to_string() + " " + value.exchange().to_text()
    TxtData(values) => {
      let fields : Array[String] = []
      for value in values {
        fields.push(quote_zone_text(value))
      }
      join_rendered_fields(fields)
    }
    SrvData(value) =>
      value.priority().to_string() +
      " " +
      value.weight().to_string() +
      " " +
      value.port().to_string() +
      " " +
      value.target().to_text()
    CaaData(value) =>
      value.flags().to_string() +
      " " +
      value.tag() +
      " " +
      quote_zone_text(value.value())
    SoaData(value) =>
      value.primary_name().to_text() +
      " " +
      value.responsible_name().to_text() +
      " " +
      value.serial().to_string() +
      " " +
      value.refresh().to_text() +
      " " +
      value.retry().to_text() +
      " " +
      value.expire().to_text() +
      " " +
      value.minimum().to_text()
    GenericData(value) => {
      let fields : Array[String] = []
      for field in value.fields() {
        fields.push(render_zone_token(field))
      }
      join_rendered_fields(fields)
    }
  }
}

///|
pub fn ZoneRecord::to_text(self : ZoneRecord) -> String {
  self.owner().to_text() +
  " " +
  self.ttl().to_text() +
  " IN " +
  self.record_type() +
  " " +
  render_record_data(self.data())
}

///|
pub fn render_zone(zone : ZoneDocument) -> String {
  let lines : Array[String] = []
  match zone.origin() {
    Some(value) => lines.push("$ORIGIN " + value.to_text())
    None => ()
  }
  match zone.default_ttl() {
    Some(value) => lines.push("$TTL " + value.to_text())
    None => ()
  }
  for record in zone.records() {
    lines.push(record.to_text())
  }
  let mut result = ""
  for index, line in lines {
    if index > 0 {
      result = result + "\n"
    }
    result = result + line
  }
  result
}