///|
pub let ether_type_profinet : Int = 0x8892

///|
pub let frame_id_get_set : Int = 65277

///|
pub let frame_id_identify_request : Int = 65278

///|
pub let dcp_service_id_get : Int = 3

///|
pub let dcp_service_id_set : Int = 4

///|
pub let dcp_service_id_identify : Int = 5

///|
pub let dcp_service_type_request : Int = 0

///|
pub let dcp_option_ip : Byte = 1

///|
pub let dcp_option_device : Byte = 2

///|
pub let dcp_option_dhcp : Byte = 3

///|
pub let dcp_option_control : Byte = 5

///|
pub let dcp_option_initiative : Byte = 6

///|
pub let dcp_option_all : Byte = 255

///|
pub let dcp_suboption_mac_address : Byte = 1

///|
pub let dcp_suboption_ip_parameter : Byte = 2

///|
pub let dcp_suboption_full_ip_suite : Byte = 3

///|
pub let dcp_suboption_device_vendor : Byte = 1

///|
pub let dcp_suboption_name_of_station : Byte = 2

///|
pub let dcp_suboption_device_id : Byte = 3

///|
pub let dcp_suboption_device_role : Byte = 4

///|
pub let dcp_suboption_device_options : Byte = 5

///|
pub let dcp_suboption_device_alias : Byte = 6

///|
pub let dcp_suboption_device_instance : Byte = 7

///|
pub let dcp_suboption_device_oem_id : Byte = 8

///|
pub let dcp_suboption_control_response : Byte = 4

///|
pub let dcp_suboption_device_initiative : Byte = 1

///|
pub let dcp_suboption_all : Byte = 255

///|
pub let frame_id_identify_response : Int = 65279

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

///|
fn append_payload_block(
  output : Array[Byte],
  option : Byte,
  suboption : Byte,
  payload : Bytes,
) -> Unit {
  output.push(option)
  output.push(suboption)
  push_u16_be(output, payload.length())
  append_bytes(output, payload)
  if payload.length() % 2 == 1 {
    output.push(0)
  }
}

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

///|
fn read_u32_be(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()
}

///|
fn bytes_slice(data : Bytes, start : Int, len : Int) -> Bytes {
  Bytes::from_array(data.view(start~, end=start + len).to_array())
}

///|
fn encode_dcp_ethernet_frame(
  destination : @frame.MacAddress,
  source : @frame.MacAddress,
  frame_id : Int,
  service_id : Int,
  service_type : Int,
  xid : Int,
  response_delay : Int,
  payload : Bytes,
) -> Bytes {
  let output : Array[Byte] = []
  append_bytes(output, destination.octets())
  append_bytes(output, source.octets())
  push_u16_be(output, ether_type_profinet)
  push_u16_be(output, frame_id)
  output.push(service_id.to_byte())
  output.push(service_type.to_byte())
  push_u32_be(output, xid)
  push_u16_be(output, response_delay)
  push_u16_be(output, payload.length())
  append_bytes(output, payload)
  Bytes::from_array(output)
}