///|
pub struct ZoneRecordSet {
  owner_value : DomainName
  type_value : String
  record_values : Array[ZoneRecord]
} derive(Eq, Debug)

///|
pub struct ZoneRecordSets {
  set_values : Array[ZoneRecordSet]
  record_count_value : Int
  duplicate_count_value : Int
} derive(Eq, Debug)

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

///|
fn record_set_matches(set : ZoneRecordSet, record : ZoneRecord) -> Bool {
  set.owner() == record.owner() && set.record_type() == record.record_type()
}

///|
fn record_set_index(sets : Array[ZoneRecordSet], record : ZoneRecord) -> Int {
  for index, set in sets {
    if record_set_matches(set, record) {
      return index
    }
  }
  -1
}

///|
fn record_set_has_exact(
  records : Array[ZoneRecord],
  candidate : ZoneRecord,
) -> Bool {
  for record in records {
    if records_exactly_equal(record, candidate) {
      return true
    }
  }
  false
}

///|
fn record_set_sort_after(left : ZoneRecordSet, right : ZoneRecordSet) -> Bool {
  let left_owner = left.owner().to_text()
  let right_owner = right.owner().to_text()
  if left_owner == right_owner {
    return statistics_text_after(left.record_type(), right.record_type())
  }
  statistics_text_after(left_owner, right_owner)
}

///|
fn sort_record_sets(sets : Array[ZoneRecordSet]) -> Unit {
  for index = 1; index < sets.length(); index = index + 1 {
    let current = sets[index]
    let mut position = index
    while position > 0 && record_set_sort_after(sets[position - 1], current) {
      sets[position] = sets[position - 1]
      position = position - 1
    }
    sets[position] = current
  }
}

///|
/// Group records by owner and type and count exact duplicate evidence.
pub fn build_zone_record_sets(
  zone : ZoneDocument,
  max_sets? : Int = 100000,
) -> Result[ZoneRecordSets, ZoneError] {
  if max_sets < 0 {
    return Err(record_set_error("maximum record set count cannot be negative"))
  }
  let sets : Array[ZoneRecordSet] = []
  let mut duplicates = 0
  let mut record_count = 0
  for record in zone.records() {
    record_count = record_count + 1
    let index = record_set_index(sets, record)
    if index < 0 {
      if sets.length() >= max_sets {
        return Err(
          record_set_error("zone exceeds the configured record set limit"),
        )
      }
      sets.push({
        owner_value: record.owner(),
        type_value: record.record_type(),
        record_values: [record],
      })
    } else {
      let current = sets[index]
      if record_set_has_exact(current.records(), record) {
        duplicates = duplicates + 1
      }
      let records = current.records()
      records.push(record)
      sets[index] = {
        owner_value: current.owner(),
        type_value: current.record_type(),
        record_values: records,
      }
    }
  }
  sort_record_sets(sets)
  Ok({
    set_values: sets,
    record_count_value: record_count,
    duplicate_count_value: duplicates,
  })
}

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

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

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

///|
pub fn ZoneRecordSet::count(self : ZoneRecordSet) -> Int {
  self.record_values.length()
}

///|
pub fn ZoneRecordSet::duplicate_count(self : ZoneRecordSet) -> Int {
  let unique : Array[ZoneRecord] = []
  let mut duplicates = 0
  for record in self.record_values {
    if record_set_has_exact(unique, record) {
      duplicates = duplicates + 1
    } else {
      unique.push(record)
    }
  }
  duplicates
}

///|
pub fn ZoneRecordSet::unique_record_count(self : ZoneRecordSet) -> Int {
  self.count() - self.duplicate_count()
}

///|
pub fn ZoneRecordSet::unique_records(self : ZoneRecordSet) -> Array[ZoneRecord] {
  let unique : Array[ZoneRecord] = []
  for record in self.record_values {
    if !record_set_has_exact(unique, record) {
      unique.push(record)
    }
  }
  unique
}

///|
pub fn ZoneRecordSet::duplicate_records(
  self : ZoneRecordSet,
) -> Array[ZoneRecord] {
  let unique : Array[ZoneRecord] = []
  let duplicates : Array[ZoneRecord] = []
  for record in self.record_values {
    if record_set_has_exact(unique, record) {
      duplicates.push(record)
    } else {
      unique.push(record)
    }
  }
  duplicates
}

///|
pub fn ZoneRecordSets::sets(self : ZoneRecordSets) -> Array[ZoneRecordSet] {
  self.set_values.copy()
}

///|
pub fn ZoneRecordSets::record_count(self : ZoneRecordSets) -> Int {
  self.record_count_value
}

///|
pub fn ZoneRecordSets::set_count(self : ZoneRecordSets) -> Int {
  self.set_values.length()
}

///|
pub fn ZoneRecordSets::duplicate_count(self : ZoneRecordSets) -> Int {
  self.duplicate_count_value
}

///|
pub fn ZoneRecordSets::unique_record_count(self : ZoneRecordSets) -> Int {
  self.record_count_value - self.duplicate_count_value
}

///|
pub fn ZoneRecordSets::has_duplicates(self : ZoneRecordSets) -> Bool {
  self.duplicate_count_value > 0
}

///|
pub fn ZoneRecordSets::singleton_set_count(self : ZoneRecordSets) -> Int {
  let mut count = 0
  for set in self.set_values {
    if set.count() == 1 {
      count = count + 1
    }
  }
  count
}

///|
pub fn ZoneRecordSets::multi_record_set_count(self : ZoneRecordSets) -> Int {
  self.set_count() - self.singleton_set_count()
}

///|
pub fn ZoneRecordSets::largest_set_size(self : ZoneRecordSets) -> Int {
  let mut largest = 0
  for set in self.set_values {
    if set.count() > largest {
      largest = set.count()
    }
  }
  largest
}

///|
pub fn ZoneRecordSets::lookup(
  self : ZoneRecordSets,
  owner : DomainName,
  record_type : String,
) -> ZoneRecordSet? {
  let expected = uppercase_ascii(record_type)
  for set in self.set_values {
    if set.owner() == owner && set.record_type() == expected {
      return Some(set)
    }
  }
  None
}

///|
pub fn ZoneRecordSets::for_owner(
  self : ZoneRecordSets,
  owner : DomainName,
) -> Array[ZoneRecordSet] {
  let result : Array[ZoneRecordSet] = []
  for set in self.set_values {
    if set.owner() == owner {
      result.push(set)
    }
  }
  result
}

///|
pub fn ZoneRecordSets::for_type(
  self : ZoneRecordSets,
  record_type : String,
) -> Array[ZoneRecordSet] {
  let expected = uppercase_ascii(record_type)
  let result : Array[ZoneRecordSet] = []
  for set in self.set_values {
    if set.record_type() == expected {
      result.push(set)
    }
  }
  result
}

///|
pub fn render_record_sets_text(sets : ZoneRecordSets) -> String {
  let mut result = "DNS zone record sets" +
    "\nrecords: " +
    sets.record_count().to_string() +
    "\nsets: " +
    sets.set_count().to_string() +
    "\nunique records: " +
    sets.unique_record_count().to_string() +
    "\nduplicates: " +
    sets.duplicate_count().to_string() +
    "\nsingleton sets: " +
    sets.singleton_set_count().to_string() +
    "\nmulti-record sets: " +
    sets.multi_record_set_count().to_string() +
    "\nlargest set: " +
    sets.largest_set_size().to_string()
  for set in sets.sets() {
    result = result +
      "\n" +
      set.owner().to_text() +
      " " +
      set.record_type() +
      " count=" +
      set.count().to_string() +
      " unique=" +
      set.unique_record_count().to_string()
  }
  result
}

///|
pub fn render_record_sets_json(sets : ZoneRecordSets) -> String {
  let values : Array[Json] = []
  for set in sets.sets() {
    let records : Array[Json] = []
    for record in set.records() {
      records.push(Json::string(record.to_text()))
    }
    let duplicates : Array[Json] = []
    for record in set.duplicate_records() {
      duplicates.push(Json::string(record.to_text()))
    }
    values.push(
      Json::object({
        "owner": Json::string(set.owner().to_text()),
        "record_type": Json::string(set.record_type()),
        "count": Json::number(set.count().to_double()),
        "unique_count": Json::number(set.unique_record_count().to_double()),
        "duplicate_count": Json::number(set.duplicate_count().to_double()),
        "records": Json::array(records),
        "duplicates": Json::array(duplicates),
      }),
    )
  }
  Json::object({
    "type": Json::string("dns_zone_record_sets"),
    "record_count": Json::number(sets.record_count().to_double()),
    "set_count": Json::number(sets.set_count().to_double()),
    "unique_record_count": Json::number(sets.unique_record_count().to_double()),
    "duplicate_count": Json::number(sets.duplicate_count().to_double()),
    "has_duplicates": Json::boolean(sets.has_duplicates()),
    "singleton_set_count": Json::number(sets.singleton_set_count().to_double()),
    "multi_record_set_count": Json::number(
      sets.multi_record_set_count().to_double(),
    ),
    "largest_set_size": Json::number(sets.largest_set_size().to_double()),
    "record_sets": Json::array(values),
  }).stringify()
}