///|
pub(all) struct SatelliteObservation {
  satellite_id_value : String
  elevation_degrees_value : Int?
  azimuth_degrees_value : Int?
  snr_value : Int?
} derive(Eq, Debug)

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

///|
fn gsv_error(message : String) -> NmeaError {
  NmeaError::from_diagnostic(
    Diagnostic::new(SentenceInvalid, Error, message, SourceRef::sentence(0)),
  )
}

///|
fn gsv_optional_integer(
  cursor : FieldCursor,
  index : Int,
  min : Int,
  max : Int,
) -> Result[Int?, NmeaError] {
  match cursor.optional(index) {
    Ok(None) => Ok(None)
    Ok(Some(_)) =>
      match cursor.integer(index, min~, max~) {
        Ok(value) => Ok(Some(value))
        Err(error) => Err(error)
      }
    Err(error) => Err(error)
  }
}

///|
pub fn parse_gsv(frame : NmeaFrame) -> Result[GsvRecord, NmeaError] {
  if frame.formatter() != "GSV" {
    return Err(gsv_error("frame formatter is not GSV"))
  }
  let cursor = FieldCursor::new(frame.fields())
  let total_messages = match cursor.integer(0, min=1, max=99) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let message_number = match cursor.integer(1, min=1, max=99) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  if message_number > total_messages {
    return Err(gsv_error("GSV message number exceeds total messages"))
  }
  let total_satellites = match cursor.integer(2, min=0, max=999) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let remainder = cursor.length() - 3
  if remainder < 0 || (remainder % 4 != 0 && remainder % 4 != 1) {
    return Err(gsv_error("GSV satellite fields are incomplete"))
  }
  let signal_id = if remainder % 4 == 1 {
    match cursor.optional(cursor.length() - 1) {
      Ok(value) => value
      Err(error) => return Err(error)
    }
  } else {
    None
  }
  let group_count = remainder / 4
  let observations : Array[SatelliteObservation] = []
  for group = 0; group < group_count; group = group + 1 {
    let base = 3 + group * 4
    let satellite_id = match cursor.required(base) {
      Ok(value) => value
      Err(error) => return Err(error)
    }
    let elevation = match gsv_optional_integer(cursor, base + 1, 0, 90) {
      Ok(value) => value
      Err(error) => return Err(error)
    }
    let azimuth = match gsv_optional_integer(cursor, base + 2, 0, 359) {
      Ok(value) => value
      Err(error) => return Err(error)
    }
    let snr = match gsv_optional_integer(cursor, base + 3, 0, 99) {
      Ok(value) => value
      Err(error) => return Err(error)
    }
    observations.push({
      satellite_id_value: satellite_id,
      elevation_degrees_value: elevation,
      azimuth_degrees_value: azimuth,
      snr_value: snr,
    })
  }
  Ok({
    talker_value: frame.talker(),
    total_messages_value: total_messages,
    message_number_value: message_number,
    total_satellites_value: total_satellites,
    observations_value: observations,
    signal_id_value: signal_id,
  })
}

///|
pub fn SatelliteObservation::satellite_id(
  self : SatelliteObservation,
) -> String {
  self.satellite_id_value
}

///|
pub fn SatelliteObservation::elevation_degrees(
  self : SatelliteObservation,
) -> Int? {
  self.elevation_degrees_value
}

///|
pub fn SatelliteObservation::azimuth_degrees(
  self : SatelliteObservation,
) -> Int? {
  self.azimuth_degrees_value
}

///|
pub fn SatelliteObservation::snr(self : SatelliteObservation) -> Int? {
  self.snr_value
}

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

///|
pub fn GsvRecord::message_number(self : GsvRecord) -> Int {
  self.message_number_value
}

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

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

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

///|
fn gsv_integer_text(value : Int?) -> String {
  match value {
    Some(integer) => integer.to_string()
    None => ""
  }
}

///|
pub fn GsvRecord::encode(self : GsvRecord) -> Result[String, NmeaError] {
  if self.observations_value.length() > 4 {
    return Err(gsv_error("GSV cannot encode more than four observations"))
  }
  let fields = [
    self.total_messages_value.to_string(),
    self.message_number_value.to_string(),
    self.total_satellites_value.to_string(),
  ]
  for observation in self.observations_value {
    fields.push(observation.satellite_id_value)
    fields.push(gsv_integer_text(observation.elevation_degrees_value))
    fields.push(gsv_integer_text(observation.azimuth_degrees_value))
    fields.push(gsv_integer_text(observation.snr_value))
  }
  match self.signal_id_value {
    Some(signal_id) => fields.push(signal_id)
    None => ()
  }
  Ok(NmeaFrame::new(36, self.talker_value, "GSV", fields).encode())
}