///|
pub(all) struct StringCountEntry {
  key_value : String
  mut count_value : Int
} derive(Eq, Debug)

///|
pub(all) struct ChecksumCountEntry {
  status_value : ChecksumStatus
  mut count_value : Int
} derive(Eq, Debug)

///|
pub(all) struct BatchSummary {
  total_value : Int
  successes_value : Int
  failures_value : Int
  formatter_entries_value : Array[StringCountEntry]
  talker_entries_value : Array[StringCountEntry]
  checksum_entries_value : Array[ChecksumCountEntry]
  error_entries_value : Array[StringCountEntry]
} derive(Eq, Debug)

///|
pub(all) struct NmeaBatch {
  items_value : Array[SentenceResult[NmeaRecord]]
  summary_value : BatchSummary
} derive(Eq, Debug)

///|
fn increment_string(entries : Array[StringCountEntry], key : String) -> Unit {
  for entry in entries {
    if entry.key_value == key {
      entry.count_value += 1
      return
    }
  }
  entries.push({ key_value: key, count_value: 1 })
}

///|
fn increment_checksum(
  entries : Array[ChecksumCountEntry],
  status : ChecksumStatus,
) -> Unit {
  for entry in entries {
    if entry.status_value == status {
      entry.count_value += 1
      return
    }
  }
  entries.push({ status_value: status, count_value: 1 })
}

///|
fn checksum_status_for_error(error : NmeaError) -> ChecksumStatus {
  if error.code() == "checksum.mismatch" {
    PresentInvalid
  } else if error.code() == "checksum.missing" {
    MissingChecksum
  } else {
    NotChecked
  }
}

///|
fn batch_from_frames(frames : Array[NmeaFrame]) -> NmeaBatch {
  let items : Array[SentenceResult[NmeaRecord]] = []
  let formatter_entries : Array[StringCountEntry] = []
  let talker_entries : Array[StringCountEntry] = []
  let checksum_entries : Array[ChecksumCountEntry] = []
  let error_entries : Array[StringCountEntry] = []
  let mut successes = 0
  let mut failures = 0
  for index = 0; index < frames.length(); index = index + 1 {
    let frame = frames[index]
    let status = frame.checksum_status()
    increment_checksum(checksum_entries, status)
    match parse_record(frame) {
      Ok(record) => {
        successes += 1
        increment_string(formatter_entries, record.formatter())
        increment_string(talker_entries, record.talker())
        items.push(
          SentenceResult::ok(
            index,
            record.formatter(),
            status,
            value=Some(record),
          ),
        )
      }
      Err(error) => {
        failures += 1
        increment_string(error_entries, error.code())
        items.push(SentenceResult::err(index, status, error))
      }
    }
  }
  {
    items_value: items,
    summary_value: {
      total_value: frames.length(),
      successes_value: successes,
      failures_value: failures,
      formatter_entries_value: formatter_entries,
      talker_entries_value: talker_entries,
      checksum_entries_value: checksum_entries,
      error_entries_value: error_entries,
    },
  }
}

///|
pub fn analyze_frame_batch(frames : Array[NmeaFrame]) -> NmeaBatch {
  batch_from_frames(frames)
}

///|
pub fn parse_batch(
  inputs : Array[String],
  policy? : ParsePolicy = Strict,
  limits? : FrameLimits = FrameLimits::standard(),
) -> NmeaBatch {
  let items : Array[SentenceResult[NmeaRecord]] = []
  let formatter_entries : Array[StringCountEntry] = []
  let talker_entries : Array[StringCountEntry] = []
  let checksum_entries : Array[ChecksumCountEntry] = []
  let error_entries : Array[StringCountEntry] = []
  let mut successes = 0
  let mut failures = 0
  for index = 0; index < inputs.length(); index = index + 1 {
    match parse_frame(inputs[index], policy~, limits~) {
      Err(error) => {
        failures += 1
        let status = checksum_status_for_error(error)
        increment_checksum(checksum_entries, status)
        increment_string(error_entries, error.code())
        items.push(SentenceResult::err(index, status, error))
      }
      Ok(frame) => {
        let status = frame.checksum_status()
        increment_checksum(checksum_entries, status)
        match parse_record(frame) {
          Ok(record) => {
            successes += 1
            increment_string(formatter_entries, record.formatter())
            increment_string(talker_entries, record.talker())
            items.push(
              SentenceResult::ok(
                index,
                record.formatter(),
                status,
                value=Some(record),
              ),
            )
          }
          Err(error) => {
            failures += 1
            increment_string(error_entries, error.code())
            items.push(SentenceResult::err(index, status, error))
          }
        }
      }
    }
  }
  {
    items_value: items,
    summary_value: {
      total_value: inputs.length(),
      successes_value: successes,
      failures_value: failures,
      formatter_entries_value: formatter_entries,
      talker_entries_value: talker_entries,
      checksum_entries_value: checksum_entries,
      error_entries_value: error_entries,
    },
  }
}

///|
fn string_count(entries : Array[StringCountEntry], key : String) -> Int {
  for entry in entries {
    if entry.key_value == key {
      return entry.count_value
    }
  }
  0
}

///|
pub fn StringCountEntry::key(self : StringCountEntry) -> String {
  self.key_value
}

///|
pub fn StringCountEntry::count(self : StringCountEntry) -> Int {
  self.count_value
}

///|
pub fn ChecksumCountEntry::status(self : ChecksumCountEntry) -> ChecksumStatus {
  self.status_value
}

///|
pub fn ChecksumCountEntry::count(self : ChecksumCountEntry) -> Int {
  self.count_value
}

///|
pub fn BatchSummary::total(self : BatchSummary) -> Int {
  self.total_value
}

///|
pub fn BatchSummary::successes(self : BatchSummary) -> Int {
  self.successes_value
}

///|
pub fn BatchSummary::failures(self : BatchSummary) -> Int {
  self.failures_value
}

///|
pub fn BatchSummary::formatter_entries(
  self : BatchSummary,
) -> Array[StringCountEntry] {
  self.formatter_entries_value.copy()
}

///|
pub fn BatchSummary::talker_entries(
  self : BatchSummary,
) -> Array[StringCountEntry] {
  self.talker_entries_value.copy()
}

///|
pub fn BatchSummary::checksum_entries(
  self : BatchSummary,
) -> Array[ChecksumCountEntry] {
  self.checksum_entries_value.copy()
}

///|
pub fn BatchSummary::error_entries(
  self : BatchSummary,
) -> Array[StringCountEntry] {
  self.error_entries_value.copy()
}

///|
pub fn BatchSummary::formatter_count(self : BatchSummary, key : String) -> Int {
  string_count(self.formatter_entries_value, key)
}

///|
pub fn BatchSummary::talker_count(self : BatchSummary, key : String) -> Int {
  string_count(self.talker_entries_value, key)
}

///|
pub fn BatchSummary::error_count(self : BatchSummary, key : String) -> Int {
  string_count(self.error_entries_value, key)
}

///|
pub fn BatchSummary::checksum_count(
  self : BatchSummary,
  status : ChecksumStatus,
) -> Int {
  for entry in self.checksum_entries_value {
    if entry.status_value == status {
      return entry.count_value
    }
  }
  0
}

///|
pub fn NmeaBatch::items(self : NmeaBatch) -> Array[SentenceResult[NmeaRecord]] {
  self.items_value.copy()
}

///|
pub fn NmeaBatch::summary(self : NmeaBatch) -> BatchSummary {
  self.summary_value
}