///|
struct DumpState {
  formats : Array[Format]
  base : Int
  width : Int
  big : Bool
  verbose : Bool
  pending : Array[Byte]
  mut skip : Int64
  mut remaining : Int64?
  mut offset : Int64
  mut previous : Bytes?
  mut squeezed : Bool
}

///|
fn DumpState::row(self : DumpState) -> String {
  let data = Bytes::from_array(self.pending)
  self.pending.clear()
  let out = if !self.verbose &&
    data.length() == self.width &&
    self.previous == Some(data) {
    if self.squeezed {
      ""
    } else {
      self.squeezed = true
      "*\n"
    }
  } else {
    self.squeezed = false
    render_row(data, self.offset, self.width, self.formats, self.base, self.big)
  }
  self.previous = Some(data)
  self.offset += data.length().to_int64()
  out
}

///|
fn DumpState::feed(self : DumpState, chunk : Bytes) -> String {
  let out = StringBuilder()
  for byte in chunk {
    if self.skip > 0L {
      self.skip -= 1L
      self.offset += 1L
      continue
    }
    if self.remaining == Some(0L) {
      break
    }
    self.pending.push(byte)
    if self.remaining is Some(n) {
      self.remaining = Some(n - 1L)
    }
    if self.pending.length() == self.width {
      out.write_string(self.row())
    }
  }
  out.to_string()
}

///|
async fn DumpState::read(self : DumpState, reader : &@io.Reader) -> Unit {
  let buffer : FixedArray[Byte] = FixedArray::make(65536, 0)
  while self.skip > 0L || self.remaining != Some(0L) {
    let maximum = match self.remaining {
      Some(n) => self.skip.min(65536L) + n.min(65536L)
      None => 65536L
    }
    let count = reader.read(buffer, max_len=maximum.min(65536L).to_int())
    if count == 0 {
      break
    }
    @stdio.stdout.write(self.feed(Bytes::from_array(buffer[:count].to_owned())))
  }
}

///|
fn parse_count(text : String) -> Int64 raise OdError {
  if text == "" || text.has_prefix("-") {
    raise OdError("invalid byte count '\{text}'")
  }
  let mut digits = text
  let mut multiplier = 1L
  if text.has_suffix("b") {
    digits = text[:text.length() - 1].to_owned()
    multiplier = 512L
  } else if text.has_suffix("K") {
    digits = text[:text.length() - 1].to_owned()
    multiplier = 1024L
  } else if text.has_suffix("M") {
    digits = text[:text.length() - 1].to_owned()
    multiplier = 1048576L
  } else if text.has_suffix("G") {
    digits = text[:text.length() - 1].to_owned()
    multiplier = 1073741824L
  }
  let radix = if digits.has_prefix("0x") || digits.has_prefix("0X") {
    16
  } else if digits.length() > 1 && digits[0] == '0' {
    8
  } else {
    10
  }
  if radix == 16 {
    digits = digits[2:].to_owned()
  }
  let value = @string.parse_int64(digits, base=radix) catch {
    _ => raise OdError("invalid byte count '\{text}'")
  }
  if value < 0L || value > 9223372036854775807L / multiplier {
    raise OdError("byte count out of range")
  }
  value * multiplier
}