///|
/// Exact byte counts: unsigned decimal or hexadecimal and binary unit suffixes.
pub fn parse_size(text : String) -> UInt64 raise QcowError {
  let mut digits = text
  let mut multiplier = 1UL
  let suffixes = [
    ("KiB", 1024UL),
    ("MiB", 1048576UL),
    ("GiB", 1073741824UL),
    ("TiB", 1099511627776UL),
  ]
  for (suffix, factor) in suffixes {
    if text.has_suffix(suffix) {
      digits = text[:text.length() - suffix.length()].to_owned()
      multiplier = factor
      break
    }
  }
  let bytes = @utf8.encode(digits)
  let hexadecimal = bytes.length() >= 2 &&
    bytes[0] == b'0' &&
    (bytes[1] == b'x' || bytes[1] == b'X')
  let start = if hexadecimal { 2 } else { 0 }
  let base = if hexadecimal { 16UL } else { 10UL }
  if bytes.length() == start {
    raise Invalid("empty byte count")
  }
  let mut value = 0UL
  for i = start; i < bytes.length(); i = i + 1 {
    let byte = bytes[i].to_int()
    let digit = if byte >= 48 && byte <= 57 {
      byte - 48
    } else if hexadecimal && byte >= 65 && byte <= 70 {
      byte - 65 + 10
    } else if hexadecimal && byte >= 97 && byte <= 102 {
      byte - 97 + 10
    } else {
      raise Invalid("invalid byte count digit")
    }
    if digit.to_uint64() >= base {
      raise Invalid("digit outside number base")
    }
    value = add(multiply(value, base), digit.to_uint64())
  }
  multiply(value, multiplier)
}

///|
fn hex_digit(value : Int32) -> String {
  "0123456789abcdef"[value:value + 1].to_owned()
}

///|
pub fn hex_offset(value : UInt64) -> String {
  let mut text = ""
  let mut remaining = value
  for _i = 0; _i < 16; _i = _i + 1 {
    text = hex_digit((remaining & 15UL).to_int()) + text
    remaining = remaining >> 4
  }
  text
}

///|
/// Bounded binary display. Arbitrary guest bytes are not decoded as UTF-8.
pub fn hexdump(
  data : BytesView,
  offset~ : UInt64 = 0UL,
) -> String raise QcowError {
  if data.length() > 65536 {
    raise LimitExceeded
  }
  ignore(add(offset, data.length().to_uint64()))
  let mut result = ""
  for start = 0; start < data.length(); start = start + 16 {
    result = result + hex_offset(offset + start.to_uint64()) + "  "
    let stop = if start + 16 < data.length() {
      start + 16
    } else {
      data.length()
    }
    for i = start; i < start + 16; i = i + 1 {
      if i < stop {
        let value = data[i].to_int()
        result = result + hex_digit(value >> 4) + hex_digit(value & 15) + " "
      } else {
        result = result + "   "
      }
      if i == start + 7 {
        result = result + " "
      }
    }
    result = result + " |"
    for i = start; i < stop; i = i + 1 {
      let value = data[i].to_int()
      if value >= 32 && value <= 126 {
        result = result + decode_text(Bytes::from_array([data[i]]))
      } else {
        result = result + "."
      }
    }
    result = result + "|\n"
  }
  result
}

///|
pub(all) struct ByteRange {
  offset : UInt64
  length : UInt64
}

///|
pub fn ByteRange::end(self : ByteRange) -> UInt64 raise QcowError {
  add(self.offset, self.length)
}

///|
pub fn ByteRange::intersection(
  self : ByteRange,
  other : ByteRange,
) -> ByteRange? raise QcowError {
  let start = if self.offset > other.offset {
    self.offset
  } else {
    other.offset
  }
  let stop = min64(self.end(), other.end())
  if start < stop {
    Some({ offset: start, length: stop - start, })
  } else {
    None
  }
}

///|
pub fn ByteRange::split(
  self : ByteRange,
  chunk_size : UInt64,
  max_chunks : Int32,
) -> Array[ByteRange] raise QcowError {
  let end = self.end()
  if chunk_size == 0UL || max_chunks <= 0 {
    raise Invalid("invalid range split limit")
  }
  if ceiling(self.length, chunk_size) > max_chunks.to_uint64() {
    raise LimitExceeded
  }
  let result : Array[ByteRange] = []
  let mut position = self.offset
  while position < end {
    let length = min64(end - position, chunk_size)
    result.push({ offset: position, length, })
    position = position + length
  }
  result
}