///|
/// Standard PROFINET RPC UDP port.
pub let rpc_udp_port : Int = 34964

///|
/// PROFINET multicast MAC for DCE/RPC.
pub let rpc_multicast_mac : String = "01:0E:CF:00:00:00"

///|
/// DCE/RPC packet types.
pub let packet_type_request : Byte = 0

///|
pub let packet_type_ping : Byte = 1

///|
pub let packet_type_response : Byte = 2

///|
pub let packet_type_no_call : Byte = 5

///|
pub let packet_type_reject : Byte = 6

///|
pub fn packet_type_label(pt : Byte) -> String {
  match pt {
    b'\x00' => "Request"
    b'\x01' => "Ping"
    b'\x02' => "Response"
    b'\x05' => "NoCall"
    b'\x06' => "Reject"
    _ => "Unknown(" + pt.to_int().to_string() + ")"
  }
}

///|
/// Well-known PROFINET IO CM activity UUIDs (as hex strings).
/// PNIO Device UUID for endpoint mapper.
pub let uuid_pnio_device : String = "DEA00001-6C97-11D1-8271-00A02442DF7D"

///|
/// PNIO Controller UUID.
pub let uuid_pnio_controller : String = "DEA00002-6C97-11D1-8271-00A02442DF7D"

///|
/// PNIO Supervisor UUID.
pub let uuid_pnio_supervisor : String = "DEA00003-6C97-11D1-8271-00A02442DF7D"

///|
/// PNIO Parameterization UUID.
pub let uuid_pnio_parameterization : String = "DEA00004-6C97-11D1-8271-00A02442DF7D"

///|
/// Endpoint Mapper UUID (DCE standard).
pub let uuid_endpoint_mapper : String = "E1AF8308-5D1F-11C9-91A4-08002B14A0FA"

///|
/// UUID data type — 16-byte identifier.
pub(all) struct Uuid {
  bytes : Bytes
} derive(Eq, Debug)

///|
pub fn Uuid::empty() -> Uuid {
  Uuid::{ bytes: Bytes::make(16, b'\x00') }
}

///|
pub fn Uuid::from_bytes(
  data : Bytes,
  offset : Int,
) -> Uuid raise @frame.FrameError {
  guard data.length() >= offset + 16 else {
    raise @frame.FrameError::InvalidMacLength(data.length())
  }
  let bytes : Array[Byte] = []
  for i = 0; i < 16; i = i + 1 {
    bytes.push(data[offset + i])
  }
  Uuid::{ bytes: Bytes::from_array(bytes) }
}

///|
fn swap_dce_uuid_fields(src : Bytes) -> Bytes {
  Bytes::from_array([
    src[3],
    src[2],
    src[1],
    src[0],
    src[5],
    src[4],
    src[7],
    src[6],
    src[8],
    src[9],
    src[10],
    src[11],
    src[12],
    src[13],
    src[14],
    src[15],
  ])
}

///|
/// Parse a DCE/RPC little-endian wire UUID into canonical UUID byte order.
pub fn Uuid::from_dce_bytes_le(
  data : Bytes,
  offset : Int,
) -> Uuid raise @frame.FrameError {
  guard data.length() >= offset + 16 else {
    raise @frame.FrameError::InvalidMacLength(data.length())
  }
  Uuid::{ bytes: swap_dce_uuid_fields(data[offset:offset + 16].to_owned()) }
}

///|
fn hex_digit_value(c : Char) -> Int raise @frame.FrameError {
  match c {
    '0'..='9' => c.to_int() - '0'.to_int()
    'a'..='f' => c.to_int() - 'a'.to_int() + 10
    'A'..='F' => c.to_int() - 'A'.to_int() + 10
    _ => raise @frame.FrameError::InvalidHexText(c.to_string())
  }
}

///|
/// Parse a UUID from its canonical hex string (e.g.
/// "DEA00001-6C97-11D1-8271-00A02442DF7D"). Bytes are taken in written
/// (big-endian field) order; dashes are ignored.
pub fn Uuid::from_hex(text : String) -> Uuid raise @frame.FrameError {
  let nibbles : Array[Int] = []
  for c in text {
    if c != '-' {
      nibbles.push(hex_digit_value(c))
    }
  }
  guard nibbles.length() == 32 else {
    raise @frame.FrameError::InvalidHexText(text)
  }
  let bytes : Array[Byte] = []
  for i = 0; i < 16; i = i + 1 {
    bytes.push((((nibbles[i * 2] << 4) | nibbles[i * 2 + 1]) & 0xFF).to_byte())
  }
  Uuid::{ bytes: Bytes::from_array(bytes) }
}

///|
pub fn Uuid::to_hex(self : Uuid) -> String {
  let b = self.bytes
  let hex = fn(idx : Int) -> String {
    @frame.uint16_to_hex((b[idx].to_int() << 8) + b[idx + 1].to_int())
  }
  let h0 = @frame.uint32_to_hex(
    (b[0].to_int() << 24) +
    (b[1].to_int() << 16) +
    (b[2].to_int() << 8) +
    b[3].to_int(),
  )
  h0 +
  "-" +
  hex(4) +
  "-" +
  hex(6) +
  "-" +
  hex(8) +
  "-" +
  hex(10) +
  hex(12) +
  hex(14)
}

///|
pub fn Uuid::to_bytes(self : Uuid) -> Bytes {
  self.bytes
}

///|
/// Return the DCE/RPC little-endian wire representation used when DREP=0x10.
pub fn Uuid::to_dce_bytes_le(self : Uuid) -> Bytes {
  swap_dce_uuid_fields(self.bytes)
}

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

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

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

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

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