///|
pub struct DomainName {
  label_values : Array[String]
  absolute_value : Bool
} derive(Eq, Debug)

///|
fn name_error(
  code : ZoneErrorCode,
  message : String,
  column? : Int = 0,
) -> ZoneError {
  ZoneError::new(code, message, SourceSpan::point(0, column))
}

///|
fn ascii_lower(text : String) -> String {
  let mut result = ""
  for index = 0; index < text.length(); index = index + 1 {
    let code = text[index].to_int()
    let lowered = if code >= 65 && code <= 90 { code + 32 } else { code }
    let character = match lowered.to_char() {
      Some(value) => value.to_string()
      None => ""
    }
    result = result + character
  }
  result
}

///|
fn valid_label_character(code : Int) -> Bool {
  (code >= 97 && code <= 122) ||
  (code >= 65 && code <= 90) ||
  (code >= 48 && code <= 57) ||
  code == 45 ||
  code == 95
}

///|
fn validate_domain_label(
  label : String,
  index : Int,
) -> Result[Unit, ZoneError] {
  if label.length() == 0 {
    return Err(
      name_error(
        InvalidDomainLabel,
        "domain name contains an empty interior label",
        column=index,
      ),
    )
  }
  if label.length() > 63 {
    return Err(
      name_error(
        InvalidDomainLabel,
        "domain label exceeds 63 ASCII octets",
        column=index,
      ),
    )
  }
  if label == "*" {
    return Ok(())
  }
  for offset = 0; offset < label.length(); offset = offset + 1 {
    if !valid_label_character(label[offset].to_int()) {
      return Err(
        name_error(
          InvalidDomainLabel,
          "domain label contains an unsupported character",
          column=index + offset,
        ),
      )
    }
  }
  Ok(())
}

///|
fn domain_wire_length(labels : Array[String], absolute : Bool) -> Int {
  let mut length = if absolute { 1 } else { 0 }
  for label in labels {
    length = length + 1 + label.length()
  }
  length
}

///|
/// Parse an ASCII DNS presentation name without resolving a relative origin.
pub fn parse_domain_name(text : String) -> Result[DomainName, ZoneError] {
  if text.length() == 0 {
    return Err(name_error(InvalidDomainName, "domain name is empty"))
  }
  if text == "." {
    return Ok({ label_values: [], absolute_value: true })
  }
  let absolute = text[text.length() - 1].to_int() == 46
  let limit = if absolute { text.length() - 1 } else { text.length() }
  let labels : Array[String] = []
  let mut current = ""
  let mut label_start = 0
  for index = 0; index < limit; index = index + 1 {
    let code = text[index].to_int()
    if code > 127 {
      return Err(
        name_error(
          InvalidDomainLabel,
          "domain name must use ASCII presentation characters",
          column=index,
        ),
      )
    }
    if code == 46 {
      match validate_domain_label(current, label_start) {
        Ok(_) => labels.push(ascii_lower(current))
        Err(error) => return Err(error)
      }
      current = ""
      label_start = index + 1
    } else {
      let character = match code.to_char() {
        Some(value) => value.to_string()
        None => ""
      }
      current = current + character
    }
  }
  match validate_domain_label(current, label_start) {
    Ok(_) => labels.push(ascii_lower(current))
    Err(error) => return Err(error)
  }
  if domain_wire_length(labels, absolute) > 255 {
    return Err(
      name_error(DomainNameTooLong, "domain name exceeds 255 wire octets"),
    )
  }
  Ok({ label_values: labels, absolute_value: absolute })
}

///|
pub fn DomainName::labels(self : DomainName) -> Array[String] {
  self.label_values.copy()
}

///|
pub fn DomainName::is_absolute(self : DomainName) -> Bool {
  self.absolute_value
}

///|
pub fn DomainName::wire_length(self : DomainName) -> Int {
  domain_wire_length(self.label_values, self.absolute_value)
}

///|
pub fn DomainName::to_text(self : DomainName) -> String {
  if self.label_values.length() == 0 {
    if self.absolute_value {
      "."
    } else {
      ""
    }
  } else {
    let mut result = ""
    for index, label in self.label_values {
      if index > 0 {
        result = result + "."
      }
      result = result + label
    }
    if self.absolute_value {
      result + "."
    } else {
      result
    }
  }
}

///|
pub fn DomainName::resolve(
  self : DomainName,
  origin : DomainName,
) -> Result[DomainName, ZoneError] {
  if self.absolute_value {
    return Ok(self)
  }
  if !origin.absolute_value {
    return Err(
      name_error(
        RelativeNameWithoutOrigin,
        "relative domain name requires an absolute origin",
      ),
    )
  }
  let labels = self.label_values.copy()
  for label in origin.label_values {
    labels.push(label)
  }
  if domain_wire_length(labels, true) > 255 {
    return Err(
      name_error(
        DomainNameTooLong,
        "resolved domain name exceeds 255 wire octets",
      ),
    )
  }
  Ok({ label_values: labels, absolute_value: true })
}

///|
pub fn resolve_zone_name(
  text : String,
  origin : DomainName,
) -> Result[DomainName, ZoneError] {
  if text == "@" {
    if origin.is_absolute() {
      Ok(origin)
    } else {
      Err(
        name_error(
          RelativeNameWithoutOrigin,
          "at-sign owner requires an absolute origin",
        ),
      )
    }
  } else {
    match parse_domain_name(text) {
      Ok(value) => value.resolve(origin)
      Err(error) => Err(error)
    }
  }
}