///|
pub struct ZoneBatchInput {
  label_value : String
  text_value : String
  initial_origin_value : DomainName?
  initial_ttl_value : Ttl?
} derive(Eq, Debug)

///|
pub struct ZoneBatchItem {
  label_value : String
  document_value : ZoneDocument?
  validation_value : ZoneValidation?
  statistics_value : ZoneStatistics?
  parse_error_value : ZoneError?
} derive(Eq, Debug)

///|
pub struct ZoneBatch {
  item_values : Array[ZoneBatchItem]
  accepted_count_value : Int
  rejected_count_value : Int
  parse_failed_count_value : Int
  total_record_count_value : Int
} derive(Eq, Debug)

///|
fn batch_configuration_error(message : String) -> ZoneError {
  ZoneError::new(IntegrityViolation, message, SourceSpan::point(0, 0))
}

///|
fn batch_label_is_valid(label : String) -> Bool {
  if label.length() == 0 || label.length() > 128 {
    return false
  }
  for index = 0; index < label.length(); index = index + 1 {
    let code = label[index].to_int()
    if code < 33 || code > 126 {
      return false
    }
  }
  true
}

///|
pub fn ZoneBatchInput::new(
  label : String,
  text : String,
  initial_origin? : DomainName? = None,
  initial_ttl? : Ttl? = None,
) -> Result[ZoneBatchInput, ZoneError] {
  if !batch_label_is_valid(label) {
    return Err(
      batch_configuration_error(
        "batch labels must contain 1 to 128 visible ASCII characters",
      ),
    )
  }
  match initial_origin {
    Some(value) if !value.is_absolute() =>
      return Err(
        batch_configuration_error(
          "batch input initial origin must be an absolute domain name",
        ),
      )
    _ => ()
  }
  Ok({
    label_value: label,
    text_value: text,
    initial_origin_value: initial_origin,
    initial_ttl_value: initial_ttl,
  })
}

///|
pub fn ZoneBatchInput::label(self : ZoneBatchInput) -> String {
  self.label_value
}

///|
pub fn ZoneBatchInput::text(self : ZoneBatchInput) -> String {
  self.text_value
}

///|
pub fn ZoneBatchInput::initial_origin(self : ZoneBatchInput) -> DomainName? {
  self.initial_origin_value
}

///|
pub fn ZoneBatchInput::initial_ttl(self : ZoneBatchInput) -> Ttl? {
  self.initial_ttl_value
}

///|
fn batch_contains_label(items : Array[ZoneBatchInput], label : String) -> Bool {
  for item in items {
    if item.label() == label {
      return true
    }
  }
  false
}

///|
fn validate_batch_inputs(
  inputs : Array[ZoneBatchInput],
  max_items : Int,
  max_total_text_units : Int,
) -> Result[Unit, ZoneError] {
  if max_items < 0 || max_total_text_units < 0 {
    return Err(batch_configuration_error("batch limits cannot be negative"))
  }
  if inputs.length() > max_items {
    return Err(
      batch_configuration_error("batch exceeds the configured item limit"),
    )
  }
  let seen : Array[ZoneBatchInput] = []
  let mut text_units = 0
  for input in inputs {
    if batch_contains_label(seen, input.label()) {
      return Err(batch_configuration_error("batch input labels must be unique"))
    }
    seen.push(input)
    text_units = text_units + input.text().length()
    if text_units > max_total_text_units {
      return Err(
        batch_configuration_error(
          "batch exceeds the configured total text size limit",
        ),
      )
    }
  }
  Ok(())
}

///|
/// Parse and validate independent in-memory zone texts without fail-fast loss.
pub fn process_zone_batch(
  inputs : Array[ZoneBatchInput],
  policy : ZonePolicy,
  max_items? : Int = 100,
  max_total_text_units? : Int = 1000000,
  max_tokens_per_item? : Int = 100000,
  max_token_bytes? : Int = 65535,
) -> Result[ZoneBatch, ZoneError] {
  match validate_batch_inputs(inputs, max_items, max_total_text_units) {
    Ok(_) => ()
    Err(error) => return Err(error)
  }
  if max_tokens_per_item < 0 || max_token_bytes < 0 {
    return Err(
      batch_configuration_error("batch parser limits cannot be negative"),
    )
  }
  let items : Array[ZoneBatchItem] = []
  let mut accepted = 0
  let mut rejected = 0
  let mut parse_failed = 0
  let mut total_records = 0
  for input in inputs {
    match
      parse_zone(
        input.text(),
        initial_origin=input.initial_origin(),
        initial_ttl=input.initial_ttl(),
        max_tokens=max_tokens_per_item,
        max_token_bytes~,
      ) {
      Err(error) => {
        parse_failed = parse_failed + 1
        items.push({
          label_value: input.label(),
          document_value: None,
          validation_value: None,
          statistics_value: None,
          parse_error_value: Some(error),
        })
      }
      Ok(document) => {
        let validation = match validate_zone(document, policy) {
          Ok(value) => value
          Err(error) => return Err(error)
        }
        let statistics = calculate_zone_statistics(document)
        total_records = total_records + statistics.record_count()
        if validation.accepted() {
          accepted = accepted + 1
        } else {
          rejected = rejected + 1
        }
        items.push({
          label_value: input.label(),
          document_value: Some(document),
          validation_value: Some(validation),
          statistics_value: Some(statistics),
          parse_error_value: None,
        })
      }
    }
  }
  Ok({
    item_values: items,
    accepted_count_value: accepted,
    rejected_count_value: rejected,
    parse_failed_count_value: parse_failed,
    total_record_count_value: total_records,
  })
}

///|
pub fn ZoneBatchItem::label(self : ZoneBatchItem) -> String {
  self.label_value
}

///|
pub fn ZoneBatchItem::document(self : ZoneBatchItem) -> ZoneDocument? {
  self.document_value
}

///|
pub fn ZoneBatchItem::validation(self : ZoneBatchItem) -> ZoneValidation? {
  self.validation_value
}

///|
pub fn ZoneBatchItem::statistics(self : ZoneBatchItem) -> ZoneStatistics? {
  self.statistics_value
}

///|
pub fn ZoneBatchItem::parse_error(self : ZoneBatchItem) -> ZoneError? {
  self.parse_error_value
}

///|
pub fn ZoneBatchItem::parsed(self : ZoneBatchItem) -> Bool {
  self.document_value is Some(_)
}

///|
pub fn ZoneBatchItem::accepted(self : ZoneBatchItem) -> Bool {
  match self.validation_value {
    Some(value) => value.accepted()
    None => false
  }
}

///|
pub fn ZoneBatch::items(self : ZoneBatch) -> Array[ZoneBatchItem] {
  self.item_values.copy()
}

///|
pub fn ZoneBatch::accepted_count(self : ZoneBatch) -> Int {
  self.accepted_count_value
}

///|
pub fn ZoneBatch::rejected_count(self : ZoneBatch) -> Int {
  self.rejected_count_value
}

///|
pub fn ZoneBatch::parse_failed_count(self : ZoneBatch) -> Int {
  self.parse_failed_count_value
}

///|
pub fn ZoneBatch::total_record_count(self : ZoneBatch) -> Int {
  self.total_record_count_value
}