///|
pub(all) struct GetBlock {
  option : Byte
  suboption : Byte
} derive(Eq, Debug)

///|
pub(all) struct GetRequest {
  destination : @frame.MacAddress
  source : @frame.MacAddress
  xid : Int
  blocks : Array[GetBlock]
} derive(Eq)

///|
fn selector_hex_nibble(char : Char) -> Int raise @frame.FrameError {
  if char >= '0' && char <= '9' {
    char.to_int() - '0'.to_int()
  } else if char >= 'a' && char <= 'f' {
    10 + char.to_int() - 'a'.to_int()
  } else if char >= 'A' && char <= 'F' {
    10 + char.to_int() - 'A'.to_int()
  } else {
    raise @frame.FrameError::InvalidHexText(char.to_string())
  }
}

///|
fn parse_selector_hex_byte(text : StringView) -> Byte raise @frame.FrameError {
  let trimmed = text.trim()
  let body = if trimmed.has_prefix("0x") || trimmed.has_prefix("0X") {
    trimmed[2:]
  } else {
    trimmed
  }
  guard body.length() > 0 else {
    raise @frame.FrameError::InvalidHexText(text.to_owned())
  }
  let mut value = 0
  for char in body {
    value = (value << 4) + selector_hex_nibble(char)
  }
  guard value <= 255 else {
    raise @frame.FrameError::InvalidHexText(text.to_owned())
  }
  value.to_byte()
}

///|
fn make_get_block(option : Byte, suboption : Byte) -> GetBlock {
  GetBlock::{ option, suboption }
}

///|
fn named_get_block(selector : String) -> GetBlock? {
  match selector {
    "all" => Some(make_get_block(dcp_option_all, dcp_suboption_all))
    "vendor" | "device-vendor" | "device_vendor" =>
      Some(make_get_block(dcp_option_device, dcp_suboption_device_vendor))
    "name" | "station-name" | "station_name" | "name-of-station" =>
      Some(make_get_block(dcp_option_device, dcp_suboption_name_of_station))
    "identity" | "device-id" | "device_id" | "id" =>
      Some(make_get_block(dcp_option_device, dcp_suboption_device_id))
    "role" | "device-role" | "device_role" =>
      Some(make_get_block(dcp_option_device, dcp_suboption_device_role))
    "options" | "device-options" | "device_options" =>
      Some(make_get_block(dcp_option_device, dcp_suboption_device_options))
    "alias" | "alias-name" | "alias_name" =>
      Some(make_get_block(dcp_option_device, dcp_suboption_device_alias))
    "instance" | "device-instance" | "device_instance" =>
      Some(make_get_block(dcp_option_device, dcp_suboption_device_instance))
    "oem" | "oem-id" | "oem_device_id" | "oem-device-id" =>
      Some(make_get_block(dcp_option_device, dcp_suboption_device_oem_id))
    "ip" | "ip-parameter" | "ip_parameter" =>
      Some(make_get_block(dcp_option_ip, dcp_suboption_ip_parameter))
    "mac" | "mac-address" | "mac_address" =>
      Some(make_get_block(dcp_option_ip, dcp_suboption_mac_address))
    "full-ip" | "full-ip-suite" | "full_ip_suite" =>
      Some(make_get_block(dcp_option_ip, dcp_suboption_full_ip_suite))
    "initiative" | "device-initiative" | "device_initiative" =>
      Some(
        make_get_block(dcp_option_initiative, dcp_suboption_device_initiative),
      )
    _ => None
  }
}

///|
pub fn parse_get_block_selector(
  selector : StringView,
) -> GetBlock raise @frame.FrameError {
  let trimmed = selector.trim().to_owned()
  guard trimmed != "" else {
    raise @frame.FrameError::InvalidHexText(selector.to_owned())
  }
  match named_get_block(trimmed) {
    Some(block) => block
    None => {
      let parts = trimmed.split(":").to_array()
      guard parts.length() == 2 else {
        raise @frame.FrameError::InvalidHexText(trimmed)
      }
      make_get_block(
        parse_selector_hex_byte(parts[0][:]),
        parse_selector_hex_byte(parts[1][:]),
      )
    }
  }
}

///|
pub fn parse_get_request_blocks(
  selector_text : StringView,
) -> Array[GetBlock] raise @frame.FrameError {
  let blocks : Array[GetBlock] = []
  for selector in selector_text.split(",") {
    let trimmed = selector.trim().to_owned()
    if trimmed != "" {
      blocks.push(parse_get_block_selector(trimmed[:]))
    }
  }
  if blocks.is_empty() {
    [make_get_block(dcp_option_all, dcp_suboption_all)]
  } else {
    blocks
  }
}

///|
pub fn encode_get_request(request : GetRequest) -> Bytes {
  let block_bytes : Array[Byte] = []
  for block in request.blocks {
    block_bytes.push(block.option)
    block_bytes.push(block.suboption)
  }
  encode_dcp_ethernet_frame(
    request.destination,
    request.source,
    frame_id_get_set,
    dcp_service_id_get,
    dcp_service_type_request,
    request.xid,
    0,
    Bytes::from_array(block_bytes),
  )
}