///|
pub(all) struct NmeaChecksum {
  value : Int
}

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

///|
/// Returns the XOR of all ASCII code units in a sentence payload.
pub fn NmeaChecksum::calculate(payload : String) -> Int {
  let mut value = 0
  for index = 0; index < payload.length(); index = index + 1 {
    value = value ^ payload[index].to_int()
  }
  value
}

///|
pub fn NmeaChecksum::hex(value : Int) -> String {
  let digits = "0123456789ABCDEF"
  let high = match digits[(value >> 4) & 0xF].to_char() {
    Some(character) => character.to_string()
    None => "?"
  }
  let low = match digits[value & 0xF].to_char() {
    Some(character) => character.to_string()
    None => "?"
  }
  high + low
}

///|
pub fn NmeaChecksum::verify(payload : String, expected : String) -> Bool {
  if expected.length() != 2 {
    return false
  }
  let mut normalized = ""
  for index = 0; index < expected.length(); index = index + 1 {
    let code = expected[index].to_int()
    let upper = if code >= 97 && code <= 102 { code - 32 } else { code }
    let character = match upper.to_uint16().to_char() {
      Some(value) => value
      None => ' '
    }
    normalized = normalized + character.to_string()
  }
  NmeaChecksum::hex(NmeaChecksum::calculate(payload)) == normalized
}

///|
fn parse_checksum_hex(value : String) -> Result[Int, NmeaError] {
  if value.length() != 2 {
    return Err(
      checksum_error(
        ChecksumMalformed,
        "checksum must contain two hexadecimal digits",
      ),
    )
  }
  let mut result = 0
  for index = 0; index < 2; index = index + 1 {
    let code = value[index].to_int()
    let digit = if code >= 48 && code <= 57 {
      code - 48
    } else if code >= 65 && code <= 70 {
      code - 55
    } else if code >= 97 && code <= 102 {
      code - 87
    } else {
      return Err(
        checksum_error(
          ChecksumMalformed,
          "checksum contains a non-hexadecimal digit",
        ),
      )
    }
    result = result * 16 + digit
  }
  Ok(result)
}

///|
fn checksum_value(value : String) -> Result[Int, NmeaError] {
  parse_checksum_hex(value)
}