///|
pub(all) struct GsvAssembly {
  talker_value : String
  signal_id_value : String?
  total_messages_value : Int
  total_satellites_value : Int
  observations_value : Array[SatelliteObservation]
  source_record_indexes_value : Array[Int]
} derive(Eq, Debug)

///|
struct PendingGsvAssembly {
  talker_value : String
  signal_id_value : String?
  total_messages_value : Int
  mut next_message_value : Int
  total_satellites_value : Int
  observations_value : Array[SatelliteObservation]
  source_record_indexes_value : Array[Int]
}

///|
pub struct GsvAssembler {
  pending_value : Array[PendingGsvAssembly]
}

///|
fn gsv_assembly_error(record_index : Int, message : String) -> NmeaError {
  NmeaError::from_diagnostic(
    Diagnostic::new(
      GsvAssemblyInvalid,
      Error,
      message,
      SourceRef::sentence(record_index),
    ),
  )
}

///|
pub fn GsvAssembler::new() -> GsvAssembler {
  { pending_value: [] }
}

///|
pub fn GsvAssembler::pending_count(self : GsvAssembler) -> Int {
  self.pending_value.length()
}

///|
fn GsvAssembler::find_pending(
  self : GsvAssembler,
  talker : String,
  signal_id : String?,
) -> Int? {
  for index = 0; index < self.pending_value.length(); index = index + 1 {
    let pending = self.pending_value[index]
    if pending.talker_value == talker && pending.signal_id_value == signal_id {
      return Some(index)
    }
  }
  None
}

///|
fn pending_from_first(
  record : GsvRecord,
  record_index : Int,
) -> PendingGsvAssembly {
  {
    talker_value: record.talker_value,
    signal_id_value: record.signal_id_value,
    total_messages_value: record.total_messages_value,
    next_message_value: 2,
    total_satellites_value: record.total_satellites_value,
    observations_value: record.observations_value.copy(),
    source_record_indexes_value: [record_index],
  }
}

///|
fn complete_gsv(
  pending : PendingGsvAssembly,
  record_index : Int,
) -> Result[GsvAssembly, NmeaError] {
  if pending.observations_value.length() != pending.total_satellites_value {
    return Err(
      gsv_assembly_error(
        record_index, "assembled observations do not match announced satellite total",
      ),
    )
  }
  Ok({
    talker_value: pending.talker_value,
    signal_id_value: pending.signal_id_value,
    total_messages_value: pending.total_messages_value,
    total_satellites_value: pending.total_satellites_value,
    observations_value: pending.observations_value,
    source_record_indexes_value: pending.source_record_indexes_value,
  })
}

///|
pub fn GsvAssembler::push(
  self : GsvAssembler,
  record : GsvRecord,
  record_index~ : Int,
) -> Result[GsvAssembly?, NmeaError] {
  let existing = self.find_pending(record.talker_value, record.signal_id_value)
  if record.message_number_value == 1 {
    let interrupted = match existing {
      Some(index) => {
        self.pending_value.remove(index) |> ignore
        true
      }
      None => false
    }
    let pending = pending_from_first(record, record_index)
    if pending.observations_value.length() > pending.total_satellites_value {
      return Err(
        gsv_assembly_error(
          record_index, "fragment observations exceed announced satellite total",
        ),
      )
    }
    if pending.total_messages_value == 1 {
      return match complete_gsv(pending, record_index) {
        Ok(assembly) => Ok(Some(assembly))
        Err(error) => Err(error)
      }
    }
    self.pending_value.push(pending)
    if interrupted {
      Err(
        gsv_assembly_error(
          record_index, "a new first fragment interrupted an incomplete sequence",
        ),
      )
    } else {
      Ok(None)
    }
  } else {
    let index = match existing {
      Some(value) => value
      None =>
        return Err(
          gsv_assembly_error(
            record_index, "GSV sequence does not begin with the first fragment",
          ),
        )
    }
    let pending = self.pending_value.remove(index)
    if record.total_messages_value != pending.total_messages_value ||
      record.total_satellites_value != pending.total_satellites_value {
      return Err(
        gsv_assembly_error(
          record_index, "GSV sequence metadata changed between fragments",
        ),
      )
    }
    if record.message_number_value != pending.next_message_value {
      return Err(
        gsv_assembly_error(
          record_index, "GSV fragment is duplicate, missing, or out of order",
        ),
      )
    }
    for observation in record.observations_value {
      pending.observations_value.push(observation)
    }
    pending.source_record_indexes_value.push(record_index)
    if pending.observations_value.length() > pending.total_satellites_value {
      return Err(
        gsv_assembly_error(
          record_index, "fragment observations exceed announced satellite total",
        ),
      )
    }
    if record.message_number_value == pending.total_messages_value {
      match complete_gsv(pending, record_index) {
        Ok(assembly) => Ok(Some(assembly))
        Err(error) => Err(error)
      }
    } else {
      pending.next_message_value += 1
      self.pending_value.push(pending)
      Ok(None)
    }
  }
}

///|
pub fn GsvAssembly::talker(self : GsvAssembly) -> String {
  self.talker_value
}

///|
pub fn GsvAssembly::signal_id(self : GsvAssembly) -> String? {
  self.signal_id_value
}

///|
pub fn GsvAssembly::total_satellites(self : GsvAssembly) -> Int {
  self.total_satellites_value
}

///|
pub fn GsvAssembly::total_messages(self : GsvAssembly) -> Int {
  self.total_messages_value
}

///|
pub fn GsvAssembly::observations(
  self : GsvAssembly,
) -> Array[SatelliteObservation] {
  self.observations_value.copy()
}

///|
pub fn GsvAssembly::source_record_indexes(self : GsvAssembly) -> Array[Int] {
  self.source_record_indexes_value.copy()
}