///|
pub(all) enum GsaSelection {
  AutomaticSelection
  ManualSelection
} derive(Eq, Debug)

///|
pub(all) enum GsaFixMode {
  NoDimensionalFix
  TwoDimensional
  ThreeDimensional
} derive(Eq, Debug)

///|
pub(all) struct GsaRecord {
  talker_value : String
  selection_value : GsaSelection
  fix_mode_value : GsaFixMode
  satellite_ids_value : Array[String]
  pdop_value : ExactDecimal?
  hdop_value : ExactDecimal?
  vdop_value : ExactDecimal?
} derive(Eq, Debug)

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

///|
fn gsa_optional_decimal(
  cursor : FieldCursor,
  index : Int,
) -> Result[ExactDecimal?, NmeaError] {
  match cursor.optional(index) {
    Ok(None) => Ok(None)
    Ok(Some(_)) =>
      match cursor.decimal(index) {
        Ok(value) => Ok(Some(value))
        Err(error) => Err(error)
      }
    Err(error) => Err(error)
  }
}

///|
pub fn parse_gsa(frame : NmeaFrame) -> Result[GsaRecord, NmeaError] {
  if frame.formatter() != "GSA" {
    return Err(gsa_error("frame formatter is not GSA"))
  }
  let cursor = FieldCursor::new(frame.fields())
  let selection_text = match cursor.required(0) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let selection = if selection_text == "A" {
    AutomaticSelection
  } else if selection_text == "M" {
    ManualSelection
  } else {
    return Err(gsa_error("GSA selection must be A or M"))
  }
  let mode = match cursor.integer(1, min=1, max=3) {
    Ok(1) => NoDimensionalFix
    Ok(2) => TwoDimensional
    Ok(3) => ThreeDimensional
    Ok(_) => return Err(gsa_error("GSA fix mode is invalid"))
    Err(error) => return Err(error)
  }
  let satellites : Array[String] = []
  for index = 2; index < 14; index = index + 1 {
    match cursor.optional(index) {
      Ok(Some(value)) => satellites.push(value)
      Ok(None) => ()
      Err(error) => return Err(error)
    }
  }
  let pdop = match gsa_optional_decimal(cursor, 14) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let hdop = match gsa_optional_decimal(cursor, 15) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let vdop = match gsa_optional_decimal(cursor, 16) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  Ok({
    talker_value: frame.talker(),
    selection_value: selection,
    fix_mode_value: mode,
    satellite_ids_value: satellites,
    pdop_value: pdop,
    hdop_value: hdop,
    vdop_value: vdop,
  })
}

///|
pub fn GsaRecord::selection(self : GsaRecord) -> GsaSelection {
  self.selection_value
}

///|
pub fn GsaRecord::fix_mode(self : GsaRecord) -> GsaFixMode {
  self.fix_mode_value
}

///|
pub fn GsaRecord::satellite_ids(self : GsaRecord) -> Array[String] {
  self.satellite_ids_value.copy()
}

///|
pub fn GsaRecord::pdop(self : GsaRecord) -> ExactDecimal? {
  self.pdop_value
}

///|
pub fn GsaRecord::hdop(self : GsaRecord) -> ExactDecimal? {
  self.hdop_value
}

///|
pub fn GsaRecord::vdop(self : GsaRecord) -> ExactDecimal? {
  self.vdop_value
}

///|
fn gsa_decimal_text(value : ExactDecimal?) -> String {
  match value {
    Some(decimal) => decimal.to_string()
    None => ""
  }
}

///|
pub fn GsaRecord::encode(self : GsaRecord) -> Result[String, NmeaError] {
  if self.satellite_ids_value.length() > 12 {
    return Err(gsa_error("GSA cannot encode more than 12 satellite slots"))
  }
  let fields = [
    match self.selection_value {
      AutomaticSelection => "A"
      ManualSelection => "M"
    },
    match self.fix_mode_value {
      NoDimensionalFix => "1"
      TwoDimensional => "2"
      ThreeDimensional => "3"
    },
  ]
  for satellite in self.satellite_ids_value {
    fields.push(satellite)
  }
  while fields.length() < 14 {
    fields.push("")
  }
  fields.push(gsa_decimal_text(self.pdop_value))
  fields.push(gsa_decimal_text(self.hdop_value))
  fields.push(gsa_decimal_text(self.vdop_value))
  Ok(NmeaFrame::new(36, self.talker_value, "GSA", fields).encode())
}