///|
pub(all) enum RmcStatus {
  Active
  Void
} derive(Eq, Debug)

///|
pub(all) struct RmcRecord {
  talker_value : String
  time_value : UtcTime
  status_value : RmcStatus
  latitude_value : Coordinate?
  longitude_value : Coordinate?
  speed_knots_value : ExactDecimal?
  course_value : ExactDecimal?
  date_value : UtcDate
  magnetic_variation_value : ExactDecimal?
  magnetic_direction_value : String?
  mode_value : String?
} derive(Eq, Debug)

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

///|
fn optional_rmc_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_rmc(frame : NmeaFrame) -> Result[RmcRecord, NmeaError] {
  if frame.formatter() != "RMC" {
    return Err(rmc_error("frame formatter is not RMC"))
  }
  let cursor = FieldCursor::new(frame.fields())
  let time_text = match cursor.required(0) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let time = match UtcTime::parse(time_text, SourceRef::field(0, 0)) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let status_text = match cursor.required(1) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let status = if status_text == "A" {
    Active
  } else if status_text == "V" {
    Void
  } else {
    return Err(rmc_error("RMC status must be A or V"))
  }
  let date_text = match cursor.required(8) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let date = match UtcDate::parse_rmc(date_text, SourceRef::field(0, 8)) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let latitude_text = match cursor.optional(2) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let latitude_hemi = match cursor.optional(3) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let longitude_text = match cursor.optional(4) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let longitude_hemi = match cursor.optional(5) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let (latitude, longitude) = if status is Void {
    (None, None)
  } else {
    let lat_text = match latitude_text {
      Some(value) => value
      None => return Err(rmc_error("active RMC is missing latitude"))
    }
    let lat_hemi = match latitude_hemi {
      Some(value) => value
      None => return Err(rmc_error("active RMC is missing latitude hemisphere"))
    }
    let lon_text = match longitude_text {
      Some(value) => value
      None => return Err(rmc_error("active RMC is missing longitude"))
    }
    let lon_hemi = match longitude_hemi {
      Some(value) => value
      None =>
        return Err(rmc_error("active RMC is missing longitude hemisphere"))
    }
    let lat = match parse_latitude(lat_text, lat_hemi, SourceRef::field(0, 2)) {
      Ok(value) => value
      Err(error) => return Err(error)
    }
    let lon = match
      parse_longitude(lon_text, lon_hemi, SourceRef::field(0, 4)) {
      Ok(value) => value
      Err(error) => return Err(error)
    }
    (Some(lat), Some(lon))
  }
  let speed = match optional_rmc_decimal(cursor, 6) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let course = match optional_rmc_decimal(cursor, 7) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let magnetic = match optional_rmc_decimal(cursor, 9) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let magnetic_direction = match cursor.optional(10) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  if magnetic is Some(_) &&
    magnetic_direction != Some("E") &&
    magnetic_direction != Some("W") {
    return Err(rmc_error("RMC magnetic variation requires E or W direction"))
  }
  let mode = match cursor.optional(11) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  Ok({
    talker_value: frame.talker(),
    time_value: time,
    status_value: status,
    latitude_value: latitude,
    longitude_value: longitude,
    speed_knots_value: speed,
    course_value: course,
    date_value: date,
    magnetic_variation_value: magnetic,
    magnetic_direction_value: magnetic_direction,
    mode_value: mode,
  })
}

///|
pub fn RmcRecord::status(self : RmcRecord) -> RmcStatus {
  self.status_value
}

///|
pub fn RmcRecord::time(self : RmcRecord) -> UtcTime {
  self.time_value
}

///|
pub fn RmcRecord::date(self : RmcRecord) -> UtcDate {
  self.date_value
}

///|
pub fn RmcRecord::latitude(self : RmcRecord) -> Coordinate? {
  self.latitude_value
}

///|
pub fn RmcRecord::longitude(self : RmcRecord) -> Coordinate? {
  self.longitude_value
}

///|
pub fn RmcRecord::speed_knots(self : RmcRecord) -> ExactDecimal? {
  self.speed_knots_value
}

///|
pub fn RmcRecord::course(self : RmcRecord) -> ExactDecimal? {
  self.course_value
}

///|
pub fn RmcRecord::magnetic_variation(self : RmcRecord) -> ExactDecimal? {
  self.magnetic_variation_value
}

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

///|
fn rmc_optional_text(value : String?) -> String {
  match value {
    Some(text) => text
    None => ""
  }
}

///|
pub fn RmcRecord::encode(self : RmcRecord) -> Result[String, NmeaError] {
  let latitude = match self.latitude_value {
    Some(value) => coordinate_wire_text(value)
    None => ""
  }
  let latitude_hemi = match self.latitude_value {
    Some(value) => if value.hemisphere() == 78 { "N" } else { "S" }
    None => ""
  }
  let longitude = match self.longitude_value {
    Some(value) => coordinate_wire_text(value)
    None => ""
  }
  let longitude_hemi = match self.longitude_value {
    Some(value) => if value.hemisphere() == 69 { "E" } else { "W" }
    None => ""
  }
  let status = if self.status_value is Active { "A" } else { "V" }
  let frame = NmeaFrame::new(36, self.talker_value, "RMC", [
    utc_time_wire(self.time_value),
    status,
    latitude,
    latitude_hemi,
    longitude,
    longitude_hemi,
    rmc_optional_decimal_text(self.speed_knots_value),
    rmc_optional_decimal_text(self.course_value),
    utc_date_rmc_wire(self.date_value),
    rmc_optional_decimal_text(self.magnetic_variation_value),
    rmc_optional_text(self.magnetic_direction_value),
    rmc_optional_text(self.mode_value),
  ])
  Ok(frame.encode())
}