///|
pub struct ZoneRecord {
  owner_value : DomainName
  ttl_value : Ttl
  class_value : String
  type_value : String
  data_value : RecordData
  explicit_ttl_value : Bool
  span_value : SourceSpan
} derive(Eq, Debug)

///|
fn zone_record_error(
  code : ZoneErrorCode,
  message : String,
  token : ZoneToken,
) -> ZoneError {
  ZoneError::new(code, message, token.span())
}

///|
fn token_looks_numeric(token : ZoneToken) -> Bool {
  token.text().length() > 0 &&
  token.text()[0].to_int() >= 48 &&
  token.text()[0].to_int() <= 57
}

///|
fn token_looks_class(token : ZoneToken) -> Bool {
  let value = ascii_lower(token.text())
  value == "in" || value == "ch" || value == "hs"
}

///|
fn resolve_record_owner(
  token : ZoneToken,
  origin : DomainName,
) -> Result[DomainName, ZoneError] {
  match resolve_zone_name(token.text(), origin) {
    Ok(value) => Ok(value)
    Err(error) =>
      Err(ZoneError::new(InvalidDomainName, error.message(), token.span()))
  }
}

///|
fn statement_span(tokens : Array[ZoneToken]) -> SourceSpan {
  let first = tokens[0].span()
  let last = tokens[tokens.length() - 1].span()
  SourceSpan::range(
    first.start_line(),
    first.start_column(),
    last.end_line(),
    last.end_column(),
  )
}

///|
pub fn parse_zone_record(
  statement : ZoneStatement,
  origin : DomainName,
  inherited_owner? : DomainName? = None,
  inherited_ttl? : Ttl? = None,
) -> Result[ZoneRecord, ZoneError] {
  let tokens = statement.tokens()
  if tokens.length() == 0 {
    return Err(
      ZoneError::new(
        MissingRecordField,
        "zone record statement is empty",
        SourceSpan::point(0, 0),
      ),
    )
  }
  if !origin.is_absolute() {
    return Err(
      zone_record_error(
        RelativeNameWithoutOrigin,
        "zone record requires an absolute origin",
        tokens[0],
      ),
    )
  }
  let owner_omitted = tokens[0].span().start_column() > 0
  let mut index = 0
  let owner = if owner_omitted {
    match inherited_owner {
      Some(value) => value
      None =>
        return Err(
          zone_record_error(
            MissingRecordField,
            "indented record has no owner to inherit",
            tokens[0],
          ),
        )
    }
  } else {
    index = 1
    match resolve_record_owner(tokens[0], origin) {
      Ok(value) => value
      Err(error) => return Err(error)
    }
  }
  let mut ttl = inherited_ttl
  let mut explicit_ttl = false
  let mut saw_class = false
  let mut optional_fields = 0
  while index < tokens.length() && optional_fields < 2 {
    if token_looks_numeric(tokens[index]) {
      if explicit_ttl {
        return Err(
          zone_record_error(
            InvalidRecordData,
            "record contains more than one TTL field",
            tokens[index],
          ),
        )
      }
      ttl = match parse_ttl(tokens[index].text()) {
        Ok(value) => Some(value)
        Err(error) =>
          return Err(
            ZoneError::new(InvalidTtl, error.message(), tokens[index].span()),
          )
      }
      explicit_ttl = true
      index = index + 1
      optional_fields = optional_fields + 1
    } else if token_looks_class(tokens[index]) {
      if saw_class {
        return Err(
          zone_record_error(
            InvalidRecordClass,
            "record contains more than one class field",
            tokens[index],
          ),
        )
      }
      if ascii_lower(tokens[index].text()) != "in" {
        return Err(
          zone_record_error(
            InvalidRecordClass,
            "version 0.1.0 supports only the IN record class",
            tokens[index],
          ),
        )
      }
      saw_class = true
      index = index + 1
      optional_fields = optional_fields + 1
    } else {
      break
    }
  }
  let effective_ttl = match ttl {
    Some(value) => value
    None =>
      return Err(
        zone_record_error(
          MissingRecordField,
          "record has no explicit or inherited TTL",
          tokens[0],
        ),
      )
  }
  if index >= tokens.length() {
    return Err(
      zone_record_error(
        MissingRecordField,
        "record type is missing",
        tokens[tokens.length() - 1],
      ),
    )
  }
  let type_token = tokens[index]
  index = index + 1
  let fields : Array[ZoneToken] = []
  while index < tokens.length() {
    fields.push(tokens[index])
    index = index + 1
  }
  let data = match parse_record_data(type_token, fields, origin) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  Ok({
    owner_value: owner,
    ttl_value: effective_ttl,
    class_value: "IN",
    type_value: uppercase_ascii(type_token.text()),
    data_value: data,
    explicit_ttl_value: explicit_ttl,
    span_value: statement_span(tokens),
  })
}

///|
pub fn ZoneRecord::owner(self : ZoneRecord) -> DomainName {
  self.owner_value
}

///|
pub fn ZoneRecord::ttl(self : ZoneRecord) -> Ttl {
  self.ttl_value
}

///|
pub fn ZoneRecord::record_class(self : ZoneRecord) -> String {
  self.class_value
}

///|
pub fn ZoneRecord::record_type(self : ZoneRecord) -> String {
  self.type_value
}

///|
pub fn ZoneRecord::data(self : ZoneRecord) -> RecordData {
  self.data_value
}

///|
pub fn ZoneRecord::ttl_was_explicit(self : ZoneRecord) -> Bool {
  self.explicit_ttl_value
}

///|
pub fn ZoneRecord::span(self : ZoneRecord) -> SourceSpan {
  self.span_value
}