///|
pub struct ZoneDocument {
  record_values : Array[ZoneRecord]
  origin_value : DomainName?
  default_ttl_value : Ttl?
  directive_count_value : Int
} derive(Eq, Debug)

///|
fn statement_is_directive(statement : ZoneStatement) -> Bool {
  let tokens = statement.tokens()
  tokens.length() > 0 &&
  tokens[0].text().length() > 0 &&
  tokens[0].text()[0].to_int() == 36
}

///|
/// Parse a complete caller-provided zone text into resolved records.
pub fn parse_zone(
  text : String,
  initial_origin? : DomainName? = None,
  initial_ttl? : Ttl? = None,
  max_tokens? : Int = 100000,
  max_token_bytes? : Int = 65535,
) -> Result[ZoneDocument, ZoneError] {
  match initial_origin {
    Some(value) if !value.is_absolute() =>
      return Err(
        ZoneError::new(
          RelativeNameWithoutOrigin,
          "initial zone origin must be absolute",
          SourceSpan::point(0, 0),
        ),
      )
    _ => ()
  }
  let statements = match lex_zone(text, max_tokens~, max_token_bytes~) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let records : Array[ZoneRecord] = []
  let mut origin = initial_origin
  let mut default_ttl = initial_ttl
  let mut previous_owner : DomainName? = None
  let mut previous_ttl = initial_ttl
  let mut directive_count = 0
  for statement in statements {
    if statement_is_directive(statement) {
      let directive = match
        parse_zone_directive(statement, current_origin=origin) {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      directive_count = directive_count + 1
      match directive {
        OriginDirective(value) => {
          origin = Some(value)
          previous_owner = None
        }
        DefaultTtlDirective(value) => {
          default_ttl = Some(value)
          previous_ttl = Some(value)
        }
      }
    } else {
      let active_origin = match origin {
        Some(value) => value
        None => {
          let token = statement.tokens()[0]
          return Err(
            ZoneError::new(
              RelativeNameWithoutOrigin,
              "zone record appears before an origin is established",
              token.span(),
            ),
          )
        }
      }
      let inherited_ttl = match previous_ttl {
        Some(value) => Some(value)
        None => default_ttl
      }
      let record = match
        parse_zone_record(
          statement,
          active_origin,
          inherited_owner=previous_owner,
          inherited_ttl~,
        ) {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      previous_owner = Some(record.owner())
      previous_ttl = Some(record.ttl())
      records.push(record)
    }
  }
  Ok({
    record_values: records,
    origin_value: origin,
    default_ttl_value: default_ttl,
    directive_count_value: directive_count,
  })
}

///|
pub fn ZoneDocument::records(self : ZoneDocument) -> Array[ZoneRecord] {
  self.record_values.copy()
}

///|
pub fn ZoneDocument::origin(self : ZoneDocument) -> DomainName? {
  self.origin_value
}

///|
pub fn ZoneDocument::default_ttl(self : ZoneDocument) -> Ttl? {
  self.default_ttl_value
}

///|
pub fn ZoneDocument::directive_count(self : ZoneDocument) -> Int {
  self.directive_count_value
}