///|
/// A decoded IO block header plus raw body bytes.
pub(all) struct IoBlockView {
  block_type : Int
  block_length : Int
  high_version : Byte
  low_version : Byte
  body : Bytes
} derive(Eq, Debug)

///|
pub(all) struct ConnectResponseSummary {
  status : Int
  args_length : Int
  block_count : Int
  ar_block : ARBlockRes?
  iocr_blocks : Array[IOCRBlockRes]
  module_diff_blocks : Array[IoBlockView]
  blocks : Array[IoBlockView]
} derive(Eq)

///|
pub(all) struct IoResponseSummary {
  status : Int
  args_length : Int
  actual_count : Int
  block_count : Int
  blocks : Array[IoBlockView]
} derive(Eq)

///|
fn read_u32_be_local(data : Bytes, offset : Int) -> Int {
  (data[offset].to_int() << 24) +
  (data[offset + 1].to_int() << 16) +
  (data[offset + 2].to_int() << 8) +
  data[offset + 3].to_int()
}

///|
pub fn parse_io_blocks(
  data : Bytes,
  offset : Int,
) -> Array[IoBlockView] raise @frame.FrameError {
  let blocks : Array[IoBlockView] = []
  let mut cursor = offset
  while cursor < data.length() {
    guard cursor + 6 <= data.length() else {
      raise @frame.FrameError::InvalidMacLength(data.length())
    }
    let block_type = read_u16_be(data, cursor)
    let block_length = read_u16_be(data, cursor + 2)
    guard block_length >= 2 && cursor + 4 + block_length <= data.length() else {
      raise @frame.FrameError::InvalidMacLength(data.length())
    }
    let body_start = cursor + 6
    let body_end = cursor + 4 + block_length
    let body_bytes : Array[Byte] = []
    for i in body_start..= 38 {
      user_data_length = read_u32_be_local(Bytes::from_array(body_bytes), 30)
      guard body_end + user_data_length <= data.length() else {
        raise @frame.FrameError::InvalidMacLength(data.length())
      }
      for i = body_end; i < body_end + user_data_length; i = i + 1 {
        body_bytes.push(data[i])
      }
    }
    blocks.push(IoBlockView::{
      block_type,
      block_length,
      high_version: data[cursor + 4],
      low_version: data[cursor + 5],
      body: Bytes::from_array(body_bytes),
    })
    cursor = cursor + 4 + block_length + user_data_length
  }
  blocks
}

///|
pub fn summarize_connect_response(
  response : @rpc.IoResponse,
) -> ConnectResponseSummary raise @frame.FrameError {
  let blocks = parse_io_blocks(response.payload, 0)
  let iocrs : Array[IOCRBlockRes] = []
  let module_diffs : Array[IoBlockView] = []
  let mut ar_block : ARBlockRes? = None
  for block in blocks {
    match block.block_type {
      t if t == block_type_ar_res =>
        ar_block = Some(parse_ar_block_res(block.body, 0))
      t if t == block_type_iocr_res =>
        iocrs.push(parse_iocr_block_res(block.body, 0))
      t if t == block_type_module_diff => module_diffs.push(block)
      _ => ()
    }
  }
  ConnectResponseSummary::{
    status: response.status,
    args_length: response.args_length,
    block_count: blocks.length(),
    ar_block,
    iocr_blocks: iocrs,
    module_diff_blocks: module_diffs,
    blocks,
  }
}

///|
fn block_type_name(block_type : Int) -> String {
  match block_type {
    t if t == block_type_ar_res => "ARBlockRes"
    t if t == block_type_iocr_res => "IOCRBlockRes"
    t if t == block_type_alarm_cr_res => "AlarmCRBlockRes"
    t if t == block_type_module_diff => "ModuleDiffBlock"
    t if t == @record.block_type_iod_write_res_header => "IODWriteResHeader"
    t if t == @record.block_type_iod_read_res_header => "IODReadResHeader"
    t if t == @record.block_type_iod_control_res => "IODControlRes"
    t if t == @record.block_type_release_res => "ReleaseBlockRes"
    t if t == block_type_iox_control_res => "IOXControlRes0"
    _ => "Unknown"
  }
}

///|
pub fn summarize_io_response(
  response : @rpc.IoResponse,
) -> IoResponseSummary raise @frame.FrameError {
  let blocks = parse_io_blocks(response.payload, 0)
  IoResponseSummary::{
    status: response.status,
    args_length: response.args_length,
    actual_count: response.actual_count,
    block_count: blocks.length(),
    blocks,
  }
}

///|
pub fn format_io_response_summary(summary : IoResponseSummary) -> String {
  let lines : Array[String] = []
  lines.push("io_status=0x" + @frame.uint32_to_hex(summary.status))
  lines.push("args_length=" + summary.args_length.to_string())
  lines.push("actual_count=" + summary.actual_count.to_string())
  lines.push("block_count=" + summary.block_count.to_string())
  for index, block in summary.blocks {
    lines.push(
      "block_" +
      (index + 1).to_string() +
      "=type:" +
      block.block_type.to_string() +
      ",name:" +
      block_type_name(block.block_type) +
      ",length:" +
      block.block_length.to_string() +
      ",version:" +
      block.high_version.to_int().to_string() +
      "." +
      block.low_version.to_int().to_string() +
      ",body_hex:" +
      @frame.bytes_to_hex(block.body, separator=""),
    )
  }
  lines.join("\n")
}

///|
pub fn format_connect_response_summary(
  summary : ConnectResponseSummary,
) -> String {
  let lines : Array[String] = []
  lines.push("io_status=0x" + @frame.uint32_to_hex(summary.status))
  lines.push("args_length=" + summary.args_length.to_string())
  lines.push("block_count=" + summary.block_count.to_string())
  match summary.ar_block {
    Some(ar) => {
      lines.push("ar_type=" + ar.ar_type.to_string())
      lines.push("ar_uuid=" + ar.ar_uuid.to_hex())
      lines.push("session_key=" + ar.session_key.to_string())
      lines.push("responder_mac=" + ar.cm_responder_mac.to_hex())
      lines.push(
        "responder_udp_rt_port=" + ar.cm_responder_udp_rt_port.to_string(),
      )
    }
    None => ()
  }
  for index, iocr in summary.iocr_blocks {
    lines.push(
      "iocr_" +
      (index + 1).to_string() +
      "=type:" +
      iocr_type_label(iocr.iocr_type) +
      ",ref:" +
      iocr.iocr_reference.to_string() +
      ",frame_id:0x" +
      @frame.uint16_to_hex(iocr.frame_id),
    )
  }
  for index, block in summary.blocks {
    lines.push(
      "block_" +
      (index + 1).to_string() +
      "=type:" +
      block.block_type.to_string() +
      ",name:" +
      block_type_name(block.block_type) +
      ",length:" +
      block.block_length.to_string() +
      ",version:" +
      block.high_version.to_int().to_string() +
      "." +
      block.low_version.to_int().to_string() +
      ",body_hex:" +
      @frame.bytes_to_hex(block.body, separator=""),
    )
  }
  lines.join("\n")
}