///|
/// Modbus polynomial CRC16, low byte first on the wire.
pub fn crc16(bytes : Array[Byte]) -> UInt16 {
  let mut crc : UInt16 = 0xFFFF
  for byte in bytes {
    crc = crc ^ byte.to_uint16()
    for _ in 0..<8 {
      if (crc & 1) == 1 {
        crc = (crc >> 1) ^ 0xA001
      } else {
        crc = crc >> 1
      }
    }
  }
  crc
}

///|
/// Return CRC bytes in Modbus RTU order.
pub fn crc_bytes(bytes : Array[Byte]) -> Array[Byte] {
  let crc = crc16(bytes)
  [crc.to_byte(), crc.shr(8).to_byte()]
}

///|
/// LRC used by Modbus ASCII.
pub fn lrc(bytes : Array[Byte]) -> Byte {
  let mut sum : Byte = 0
  for byte in bytes {
    sum = sum + byte
  }
  (0 - sum.to_int()).to_byte()
}

///|
/// Convert one ASCII hex byte into a binary byte.
pub fn parse_hex_byte(high : Byte, low : Byte) -> Result[Byte, ModbusError] {
  fn nibble(x : Byte) -> Byte {
    if x >= 48 && x <= 57 {
      x - 48
    } else if x >= 65 && x <= 70 {
      x - 55
    } else if x >= 97 && x <= 102 {
      x - 87
    } else {
      255
    }
  }
  let h = nibble(high)
  let l = nibble(low)
  if h == 255 || l == 255 {
    Err(InvalidAscii)
  } else {
    Ok(h.shl(4) + l)
  }
}

///|
fn hex_nibble(value : Byte) -> Byte {
  if value < 10 {
    value + 48
  } else {
    value + 55
  }
}

///|
/// Update a CRC16 accumulator with one byte.
pub fn crc16_update(crc : UInt16, byte : Byte) -> UInt16 {
  let mut current = crc ^ byte.to_uint16()
  for _ in 0..<8 {
    if (current & 1) == 1 {
      current = (current >> 1) ^ 0xA001
    } else {
      current = current >> 1
    }
  }
  current
}

///|
/// Compute CRC16 for a half-open range without allocating a slice.
pub fn crc16_range(
  bytes : Array[Byte],
  start : Int,
  end : Int,
) -> Result[UInt16, ModbusError] {
  if start < 0 || end < start || end > bytes.length() {
    return Err(InvalidLength)
  }
  let mut crc : UInt16 = 0xFFFF
  for i in start.. Crc16State {
  { value: 0xFFFF }
}

///|
pub fn Crc16State::update(self : Crc16State, bytes : Array[Byte]) -> Unit {
  for byte in bytes {
    self.value = crc16_update(self.value, byte)
  }
}

///|
pub fn Crc16State::update_byte(self : Crc16State, byte : Byte) -> Unit {
  self.value = crc16_update(self.value, byte)
}

///|
pub fn Crc16State::finalize(self : Crc16State) -> UInt16 {
  self.value
}

///|
pub fn Crc16State::finalize_bytes(self : Crc16State) -> Array[Byte] {
  [self.value.to_byte(), (self.value >> 8).to_byte()]
}

///|
/// Compute the LRC from a running sum, using the Modbus two's-complement rule.
pub fn lrc_from_sum(sum : Byte) -> Byte {
  (0 - sum.to_int()).to_byte()
}

///|
/// Return both the LRC and the modulo-256 sum for diagnostics.
pub fn lrc_with_sum(bytes : Array[Byte]) -> (Byte, Byte) {
  let mut sum : Byte = 0
  for byte in bytes {
    sum = sum + byte
  }
  (lrc_from_sum(sum), sum)
}

///|
/// Encode an owned byte array as uppercase hexadecimal ASCII.
pub fn encode_hex(bytes : Array[Byte]) -> Array[Byte] {
  let out : Array[Byte] = []
  for byte in bytes {
    out.push(hex_nibble(byte >> 4))
    out.push(hex_nibble(byte & 15))
  }
  out
}

///|
fn hex_value(value : Byte) -> Byte? {
  if value >= 48 && value <= 57 {
    Some(value - 48)
  } else if value >= 65 && value <= 70 {
    Some(value - 55)
  } else if value >= 97 && value <= 102 {
    Some(value - 87)
  } else {
    None
  }
}

///|
/// Decode an even-length hexadecimal ASCII array.
pub fn decode_hex(bytes : Array[Byte]) -> Result[Array[Byte], ModbusError] {
  if bytes.length() % 2 != 0 {
    return Err(InvalidAscii)
  }
  let out : Array[Byte] = []
  for i in 0..<(bytes.length() / 2) {
    match (hex_value(bytes[i * 2]), hex_value(bytes[i * 2 + 1])) {
      (Some(high), Some(low)) => out.push((high << 4) | low)
      _ => return Err(InvalidAscii)
    }
  }
  Ok(out)
}

///|
/// Verify a CRC16 trailer in low-byte-first wire order.
pub fn verify_crc16(bytes : Array[Byte]) -> Result[Unit, ModbusError] {
  if bytes.length() < 2 {
    return Err(Incomplete)
  }
  let payload = match crc16_range(bytes, 0, bytes.length() - 2) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let received = bytes[bytes.length() - 2].to_uint16() |
    (bytes[bytes.length() - 1].to_uint16() << 8)
  if payload == received {
    Ok(())
  } else {
    Err(InvalidChecksum)
  }
}

///|
/// Verify an LRC trailer at the end of a byte array.
pub fn verify_lrc(bytes : Array[Byte]) -> Result[Unit, ModbusError] {
  if bytes.length() < 2 {
    return Err(Incomplete)
  }
  let payload = match copy_range(bytes, 0, bytes.length() - 1) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  if lrc(payload) == bytes[bytes.length() - 1] {
    Ok(())
  } else {
    Err(InvalidChecksum)
  }
}