///|
/// Block type for AR block request.
pub let block_type_ar_req : Int = 257

///|
/// Block type for AR block response.
pub let block_type_ar_res : Int = 33025

///|
/// Block type for IOCR block request.
pub let block_type_iocr_req : Int = 258

///|
/// Block type for IOCR block response.
pub let block_type_iocr_res : Int = 33026

///|
/// Block type for AlarmCR block request.
pub let block_type_alarm_cr_req : Int = 259

///|
/// Block type for AlarmCR block response.
pub let block_type_alarm_cr_res : Int = 33027

///|
/// Block type for ExpectedSubmodule block request.
pub let block_type_expected_submodule_req : Int = 260

///|
/// Block type for ModuleDiffBlock in Connect.rsp.
pub let block_type_module_diff : Int = 33028

///|
/// Block type for IOX Control request (ControlBlockConnect/ApplicationReady).
pub let block_type_iox_control_req : Int = 274

///|
/// Block type for IOX Control response.
pub let block_type_iox_control_res : Int = 33042

///|
/// Wrap a block body in the standard IO block header:
/// BlockType(2) + BlockLength(2) + HighVersion(1) + LowVersion(1) + body.
/// BlockLength counts the two version bytes plus the body (mirrors the C#
/// reference `IoBlock.SerialiseList`).
fn encode_io_block(block_type : Int, body : Bytes) -> Bytes {
  let output : Array[Byte] = []
  push_u16_be(output, block_type)
  push_u16_be(output, body.length() + 2)
  output.push(b'\x01') // HighVersion
  output.push(b'\x00') // LowVersion
  append_bytes(output, body)
  Bytes::from_array(output)
}

///|
/// Wrap a block whose body contains trailing UserSpecifiedData bytes.
///
/// For IODWriteReqHeader, the bytes are transmitted after the fixed header but
/// are excluded from the block length field, matching `IoBlock.SerialiseList`.
fn encode_io_block_with_user_data(
  block_type : Int,
  body : Bytes,
  user_data_length : Int,
) -> Bytes {
  let output : Array[Byte] = []
  push_u16_be(output, block_type)
  push_u16_be(output, body.length() + 2 - user_data_length)
  output.push(b'\x01') // HighVersion
  output.push(b'\x00') // LowVersion
  append_bytes(output, body)
  Bytes::from_array(output)
}

///|
/// AR type: IO Controller.
pub let ar_type_io_controller : Int = 1

///|
/// IOCR type: Input.
pub let iocr_type_input : Int = 1

///|
/// IOCR type: Output.
pub let iocr_type_output : Int = 2

///|
pub fn iocr_type_label(iocr_type : Int) -> String {
  match iocr_type {
    1 => "Input"
    2 => "Output"
    _ => "Unknown(" + iocr_type.to_string() + ")"
  }
}

///|
fn push_u16_be(output : Array[Byte], value : Int) -> Unit {
  output.push(((value >> 8) & 0xFF).to_byte())
  output.push((value & 0xFF).to_byte())
}

///|
fn push_u32_be(output : Array[Byte], value : Int) -> Unit {
  output.push(((value >> 24) & 0xFF).to_byte())
  output.push(((value >> 16) & 0xFF).to_byte())
  output.push(((value >> 8) & 0xFF).to_byte())
  output.push((value & 0xFF).to_byte())
}

///|
fn read_u16_be(data : Bytes, offset : Int) -> Int {
  (data[offset].to_int() << 8) + data[offset + 1].to_int()
}

///|
fn append_bytes(output : Array[Byte], data : Bytes) -> Unit {
  for byte in data {
    output.push(byte)
  }
}