///|
fn has_range(data : BytesView, offset : Int, len : Int) -> Bool {
  offset >= 0 &&
  len >= 0 &&
  offset <= data.length() &&
  len <= data.length() - offset
}

///|
fn read_u8(data : BytesView, offset : Int) -> Int? {
  if has_range(data, offset, 1) {
    Some(data[offset].to_int())
  } else {
    None
  }
}

///|
fn read_u16_int(data : BytesView, offset : Int) -> Int? {
  if has_range(data, offset, 2) {
    Some(data[offset].to_int() * 256 + data[offset + 1].to_int())
  } else {
    None
  }
}

///|
fn read_u24_int(data : BytesView, offset : Int) -> Int? {
  if has_range(data, offset, 3) {
    Some(
      data[offset].to_int() * 65536 +
      data[offset + 1].to_int() * 256 +
      data[offset + 2].to_int(),
    )
  } else {
    None
  }
}

///|
fn read_u16(data : BytesView, offset : Int) -> UInt16? {
  match read_u16_int(data, offset) {
    Some(v) => Some(v.to_uint16())
    None => None
  }
}

///|
fn require_range(
  data : BytesView,
  offset : Int,
  len : Int,
  field : String,
) -> Result[Unit, ParseError] {
  if has_range(data, offset, len) {
    Ok(())
  } else {
    Err(BadLength(field~, offset~))
  }
}

///|
fn bytes_to_ascii(data : BytesView, offset : Int, len : Int) -> String? {
  if !has_range(data, offset, len) {
    None
  } else {
    let out : Array[Char] = []
    for i in 0.. 126 {
        return None
      }
      match b.to_char() {
        Some(c) => out.push(c)
        None => return None
      }
    }
    Some(String::from_array(out))
  }
}

///|
fn is_grease(v : UInt16) -> Bool {
  let n = v.to_int()
  let hi = n / 256
  let lo = n % 256
  hi == lo && hi % 16 == 10
}

///|
pub fn uint16_to_hex4(value : UInt16) -> String {
  value.to_string(radix=16).pad_start(4, '0')
}