///|
/// Register byte order used when mapping multi-register values.
pub(all) enum RegisterByteOrder {
  BigEndian
  LittleEndian
} derive(Debug, Eq)

///|
/// Word order used for values wider than one register.
pub(all) enum RegisterWordOrder {
  HighWordFirst
  LowWordFirst
} derive(Debug, Eq)

///|
pub fn register_byte_order_name(order : RegisterByteOrder) -> String {
  match order {
    BigEndian => "big-endian"
    LittleEndian => "little-endian"
  }
}

///|
pub fn register_word_order_name(order : RegisterWordOrder) -> String {
  match order {
    HighWordFirst => "high-word-first"
    LowWordFirst => "low-word-first"
  }
}

///|
/// Convert one register to two bytes in the requested byte order.
pub fn register_to_bytes(
  value : UInt16,
  order : RegisterByteOrder,
) -> Array[Byte] {
  match order {
    BigEndian => [value >> 8, value].map(x => x.to_byte())
    LittleEndian => [value, value >> 8].map(x => x.to_byte())
  }
}

///|
/// Decode two bytes as one register.
pub fn bytes_to_register(
  bytes : Array[Byte],
  offset : Int,
  order : RegisterByteOrder,
) -> Result[UInt16, ModbusError] {
  if offset < 0 || offset + 2 > bytes.length() {
    return Err(Incomplete)
  }
  match order {
    BigEndian =>
      Ok((bytes[offset].to_uint16() << 8) | bytes[offset + 1].to_uint16())
    LittleEndian =>
      Ok((bytes[offset + 1].to_uint16() << 8) | bytes[offset].to_uint16())
  }
}

///|
/// Encode an unsigned 32-bit value into two registers.
pub fn u32_to_registers(
  value : UInt,
  word_order : RegisterWordOrder,
) -> Array[UInt16] {
  let high = (value >> 16).to_uint16()
  let low = value.to_uint16()
  match word_order {
    HighWordFirst => [high, low]
    LowWordFirst => [low, high]
  }
}

///|
/// Decode two registers as an unsigned 32-bit value.
pub fn registers_to_u32(
  values : Array[UInt16],
  word_order : RegisterWordOrder,
) -> Result[UInt, ModbusError] {
  if values.length() < 2 {
    return Err(Incomplete)
  }
  match word_order {
    HighWordFirst => Ok((values[0].to_uint() << 16) | values[1].to_uint())
    LowWordFirst => Ok((values[1].to_uint() << 16) | values[0].to_uint())
  }
}

///|
/// Encode a signed 32-bit value using two's-complement bits.
pub fn i32_to_registers(
  value : Int,
  word_order : RegisterWordOrder,
) -> Array[UInt16] {
  u32_to_registers(value.reinterpret_as_uint(), word_order)
}

///|
/// Decode a signed 32-bit value from two registers.
pub fn registers_to_i32(
  values : Array[UInt16],
  word_order : RegisterWordOrder,
) -> Result[Int, ModbusError] {
  match registers_to_u32(values, word_order) {
    Ok(value) => Ok(value.reinterpret_as_int())
    Err(error) => Err(error)
  }
}

///|
/// Encode a 64-bit value into four registers.
pub fn u64_to_registers(
  value : UInt64,
  word_order : RegisterWordOrder,
) -> Array[UInt16] {
  let words : Array[UInt16] = [
    (value >> 48).to_uint16(),
    (value >> 32).to_uint16(),
    (value >> 16).to_uint16(),
    value.to_uint16(),
  ]
  match word_order {
    HighWordFirst => words
    LowWordFirst => [words[3], words[2], words[1], words[0]]
  }
}

///|
/// Decode four registers as a 64-bit value.
pub fn registers_to_u64(
  values : Array[UInt16],
  word_order : RegisterWordOrder,
) -> Result[UInt64, ModbusError] {
  if values.length() < 4 {
    return Err(Incomplete)
  }
  let words = match word_order {
    HighWordFirst => values
    LowWordFirst => [values[3], values[2], values[1], values[0]]
  }
  Ok(
    (words[0].to_uint64() << 48) |
    (words[1].to_uint64() << 32) |
    (words[2].to_uint64() << 16) |
    words[3].to_uint64(),
  )
}

///|
/// Encode a Float in IEEE-754 binary32 form as two registers.
pub fn f32_to_registers(
  value : Float,
  word_order : RegisterWordOrder,
) -> Array[UInt16] {
  u32_to_registers(value.reinterpret_as_uint(), word_order)
}

///|
/// Decode two registers as an IEEE-754 binary32 Float.
pub fn registers_to_f32(
  values : Array[UInt16],
  word_order : RegisterWordOrder,
) -> Result[Float, ModbusError] {
  match registers_to_u32(values, word_order) {
    Ok(value) => Ok(Float::reinterpret_from_uint(value))
    Err(error) => Err(error)
  }
}

///|
/// Convert an array of registers to bytes with a selected byte order.
pub fn registers_to_bytes(
  values : Array[UInt16],
  order : RegisterByteOrder,
) -> Array[Byte] {
  let out : Array[Byte] = []
  for value in values {
    let encoded = register_to_bytes(value, order)
    out.push(encoded[0])
    out.push(encoded[1])
  }
  out
}

///|
/// Convert an even-length byte array to registers.
pub fn bytes_to_registers(
  bytes : Array[Byte],
  order : RegisterByteOrder,
) -> Result[Array[UInt16], ModbusError] {
  if bytes.length() % 2 != 0 {
    return Err(InvalidLength)
  }
  let out : Array[UInt16] = []
  for offset in 0..<(bytes.length() / 2) {
    match bytes_to_register(bytes, offset * 2, order) {
      Ok(value) => out.push(value)
      Err(error) => return Err(error)
    }
  }
  Ok(out)
}

///|
/// Rotate bytes inside every register, useful for devices with byte-swapped words.
pub fn swap_register_bytes(values : Array[UInt16]) -> Array[UInt16] {
  values.map(value => (value << 8) | (value >> 8))
}

///|
/// Reverse register order in a copied array.
pub fn reverse_registers(values : Array[UInt16]) -> Array[UInt16] {
  let out : Array[UInt16] = []
  let mut index = values.length() - 1
  while index >= 0 {
    out.push(values[index])
    index -= 1
  }
  out
}

///|
/// A bounds-checked cursor over an owned register array.
pub(all) struct RegisterCursor {
  values : Array[UInt16]
  mut position : Int
}

///|
pub fn RegisterCursor::new(values : Array[UInt16]) -> RegisterCursor {
  { values, position: 0 }
}

///|
pub fn RegisterCursor::position(self : RegisterCursor) -> Int {
  self.position
}

///|
pub fn RegisterCursor::remaining(self : RegisterCursor) -> Int {
  self.values.length() - self.position
}

///|
pub fn RegisterCursor::at_end(self : RegisterCursor) -> Bool {
  self.position >= self.values.length()
}

///|
pub fn RegisterCursor::read_u16(
  self : RegisterCursor,
) -> Result[UInt16, ModbusError] {
  if self.position >= self.values.length() {
    Err(Incomplete)
  } else {
    let value = self.values[self.position]
    self.position += 1
    Ok(value)
  }
}

///|
pub fn RegisterCursor::read_i16(
  self : RegisterCursor,
) -> Result[Int, ModbusError] {
  match self.read_u16() {
    Ok(value) => Ok(signed_register(value))
    Err(error) => Err(error)
  }
}

///|
pub fn RegisterCursor::read_u32(
  self : RegisterCursor,
  order : RegisterWordOrder,
) -> Result[UInt, ModbusError] {
  match (self.read_u16(), self.read_u16()) {
    (Ok(first), Ok(second)) => registers_to_u32([first, second], order)
    (Err(error), _) => Err(error)
    (_, Err(error)) => Err(error)
  }
}

///|
pub fn RegisterCursor::read_i32(
  self : RegisterCursor,
  order : RegisterWordOrder,
) -> Result[Int, ModbusError] {
  match self.read_u32(order) {
    Ok(value) => Ok(value.reinterpret_as_int())
    Err(error) => Err(error)
  }
}

///|
pub fn RegisterCursor::read_u64(
  self : RegisterCursor,
  order : RegisterWordOrder,
) -> Result[UInt64, ModbusError] {
  let values : Array[UInt16] = []
  for _ in 0..<4 {
    match self.read_u16() {
      Ok(value) => values.push(value)
      Err(error) => return Err(error)
    }
  }
  registers_to_u64(values, order)
}

///|
pub fn RegisterCursor::read_f32(
  self : RegisterCursor,
  order : RegisterWordOrder,
) -> Result[Float, ModbusError] {
  match self.read_u32(order) {
    Ok(value) => Ok(Float::reinterpret_from_uint(value))
    Err(error) => Err(error)
  }
}

///|
pub fn RegisterCursor::skip(
  self : RegisterCursor,
  count : Int,
) -> Result[Unit, ModbusError] {
  if count < 0 || self.position + count > self.values.length() {
    Err(Incomplete)
  } else {
    self.position += count
    Ok(())
  }
}

///|
pub fn RegisterCursor::seek(
  self : RegisterCursor,
  position : Int,
) -> Result[Unit, ModbusError] {
  if position < 0 || position > self.values.length() {
    Err(InvalidAddress)
  } else {
    self.position = position
    Ok(())
  }
}

///|
/// A register writer that grows only up to a configured capacity.
pub(all) struct RegisterWriter {
  values : Array[UInt16]
  capacity : Int
}

///|
pub fn RegisterWriter::new(
  capacity : Int,
) -> Result[RegisterWriter, ModbusError] {
  if capacity < 0 || capacity > 125 {
    Err(CapacityExceeded)
  } else {
    Ok({ values: [], capacity })
  }
}

///|
pub fn RegisterWriter::length(self : RegisterWriter) -> Int {
  self.values.length()
}

///|
pub fn RegisterWriter::remaining(self : RegisterWriter) -> Int {
  self.capacity - self.values.length()
}

///|
pub fn RegisterWriter::push(
  self : RegisterWriter,
  value : UInt16,
) -> Result[Unit, ModbusError] {
  if self.values.length() >= self.capacity {
    Err(CapacityExceeded)
  } else {
    self.values.push(value)
    Ok(())
  }
}

///|
pub fn RegisterWriter::push_i16(
  self : RegisterWriter,
  value : Int,
) -> Result[Unit, ModbusError] {
  self.push(value.to_uint16())
}

///|
pub fn RegisterWriter::push_u32(
  self : RegisterWriter,
  value : UInt,
  order : RegisterWordOrder,
) -> Result[Unit, ModbusError] {
  for word in u32_to_registers(value, order) {
    match self.push(word) {
      Ok(_) => ()
      Err(error) => return Err(error)
    }
  }
  Ok(())
}

///|
pub fn RegisterWriter::push_u64(
  self : RegisterWriter,
  value : UInt64,
  order : RegisterWordOrder,
) -> Result[Unit, ModbusError] {
  for word in u64_to_registers(value, order) {
    match self.push(word) {
      Ok(_) => ()
      Err(error) => return Err(error)
    }
  }
  Ok(())
}

///|
pub fn RegisterWriter::push_f32(
  self : RegisterWriter,
  value : Float,
  order : RegisterWordOrder,
) -> Result[Unit, ModbusError] {
  for word in f32_to_registers(value, order) {
    match self.push(word) {
      Ok(_) => ()
      Err(error) => return Err(error)
    }
  }
  Ok(())
}

///|
pub fn RegisterWriter::to_array(self : RegisterWriter) -> Array[UInt16] {
  copy_registers(self.values)
}

///|
fn copy_registers(values : Array[UInt16]) -> Array[UInt16] {
  let out : Array[UInt16] = []
  for value in values {
    out.push(value)
  }
  out
}