///|
pub(all) suberror FrameError {
  InvalidMacLength(Int)
  InvalidMacText(String)
  InvalidIpv4Length(Int)
  InvalidIpv4Text(String)
  InvalidHexText(String)
} derive(Eq, Debug, ToJson)

///|
pub(all) struct MacAddress {
  bytes : Bytes
} derive(Eq)

///|
pub(all) struct IPv4Address {
  bytes : Bytes
} derive(Eq)

///|
fn byte_to_hex(byte : Byte) -> String {
  let digits = [
    "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F",
  ]
  let value = byte.to_int()
  digits[(value >> 4) & 0x0F] + digits[value & 0x0F]
}

///|
pub fn bytes_to_hex(data : Bytes, separator? : StringView = " ") -> String {
  let mut output = ""
  for index, byte in data {
    if index > 0 {
      output = output + separator.to_owned()
    }
    output = output + byte_to_hex(byte)
  }
  output
}

///|
pub fn bytes_from_hex(
  text : StringView,
  separator? : StringView = " ",
) -> Bytes raise FrameError {
  let parts = text.split(separator).to_array().filter(part => part != "")
  let bytes : Array[Byte] = []
  for part in parts {
    guard part.length() == 2 else {
      raise FrameError::InvalidHexText(text.to_owned())
    }
    let value = @string.parse_uint(part, base=16) catch {
      _ => raise FrameError::InvalidHexText(text.to_owned())
    }
    guard value <= 255U else {
      raise FrameError::InvalidHexText(text.to_owned())
    }
    bytes.push(value.to_byte())
  }
  Bytes::from_array(bytes)
}

///|
pub fn MacAddress::new(bytes : Bytes) -> MacAddress raise FrameError {
  guard bytes.length() == 6 else {
    raise FrameError::InvalidMacLength(bytes.length())
  }
  MacAddress::{ bytes, }
}

///|
pub fn MacAddress::broadcast() -> MacAddress {
  MacAddress::{
    bytes: Bytes::from_array([
      Int::to_byte(255),
      Int::to_byte(255),
      Int::to_byte(255),
      Int::to_byte(255),
      Int::to_byte(255),
      Int::to_byte(255),
    ]),
  }
}

///|
pub fn MacAddress::from_hex(text : StringView) -> MacAddress raise FrameError {
  let parts = text.split(":").to_array()
  guard parts.length() == 6 else {
    raise FrameError::InvalidMacLength(parts.length())
  }
  let bytes : Array[Byte] = []
  for part in parts {
    guard part.length() == 2 else {
      raise FrameError::InvalidMacText(text.to_owned())
    }
    let value = @string.parse_uint(part, base=16) catch {
      _ => raise FrameError::InvalidMacText(text.to_owned())
    }
    guard value <= 255U else {
      raise FrameError::InvalidMacText(text.to_owned())
    }
    bytes.push(value.to_byte())
  }
  MacAddress::new(Bytes::from_array(bytes))
}

///|
pub fn MacAddress::to_hex(
  self : MacAddress,
  separator? : StringView = ":",
) -> String {
  bytes_to_hex(self.bytes, separator~)
}

///|
pub fn MacAddress::octets(self : MacAddress) -> Bytes {
  self.bytes
}

///|
pub fn IPv4Address::new(bytes : Bytes) -> IPv4Address raise FrameError {
  guard bytes.length() == 4 else {
    raise FrameError::InvalidIpv4Length(bytes.length())
  }
  IPv4Address::{ bytes, }
}

///|
pub fn IPv4Address::from_text(
  text : StringView,
) -> IPv4Address raise FrameError {
  let parts = text.split(".").to_array()
  guard parts.length() == 4 else {
    raise FrameError::InvalidIpv4Length(parts.length())
  }
  let bytes : Array[Byte] = []
  for part in parts {
    let value = @string.parse_uint(part, base=10) catch {
      _ => raise FrameError::InvalidIpv4Text(text.to_owned())
    }
    guard value <= 255U else {
      raise FrameError::InvalidIpv4Text(text.to_owned())
    }
    bytes.push(value.to_byte())
  }
  IPv4Address::new(Bytes::from_array(bytes))
}

///|
pub fn IPv4Address::to_text(self : IPv4Address) -> String {
  let mut output = ""
  for index, byte in self.bytes {
    if index > 0 {
      output = output + "."
    }
    output = output + byte.to_int().to_string()
  }
  output
}

///|
pub fn IPv4Address::octets(self : IPv4Address) -> Bytes {
  self.bytes
}

///|
pub fn uint32_to_hex(value : Int) -> String {
  let digits = [
    "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F",
  ]
  let bytes : Array[Byte] = []
  bytes.push(((value >> 24) & 0xFF).to_byte())
  bytes.push(((value >> 16) & 0xFF).to_byte())
  bytes.push(((value >> 8) & 0xFF).to_byte())
  bytes.push((value & 0xFF).to_byte())
  let mut output = ""
  for byte in bytes {
    let v = byte.to_int()
    output = output + digits[(v >> 4) & 0x0F] + digits[v & 0x0F]
  }
  output
}

///|
pub fn uint16_to_hex(value : Int) -> String {
  let digits = [
    "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F",
  ]
  let bytes : Array[Byte] = []
  bytes.push(((value >> 8) & 0xFF).to_byte())
  bytes.push((value & 0xFF).to_byte())
  let mut output = ""
  for byte in bytes {
    let v = byte.to_int()
    output = output + digits[(v >> 4) & 0x0F] + digits[v & 0x0F]
  }
  output
}