///|
struct FoldState {
  width : Int
  bytes : Bool
  characters : Bool
  spaces : Bool
  mut pending : Array[Byte]
  mut column : Int
  mut last_width : Int
}

///|
fn FoldState::new(
  width : Int,
  bytes : Bool,
  characters : Bool,
  spaces : Bool,
) -> FoldState {
  { width, bytes, characters, spaces, pending: [], column: 0, last_width: 0, }
}

///|
fn FoldState::advance(self : FoldState, byte : Byte) -> Int {
  if self.bytes {
    return self.column + 1
  }
  match byte {
    b'\b' => (self.column - self.last_width).max(0)
    b'\r' => 0
    b'\t' => self.column + 8 - self.column % 8
    _ => {
      self.last_width = if byte == b'\x00' && !self.characters { 0 } else { 1 }
      self.column + self.last_width
    }
  }
}

///|
fn FoldState::feed(self : FoldState, data : Bytes) -> Bytes {
  let out : Array[Byte] = []
  for byte in data {
    if byte == b'\n' {
      out.append(self.pending)
      out.push(byte)
      self.pending.clear()
      self.column = 0
      continue
    }
    for ;; {
      let next = self.advance(byte)
      if next <= self.width || self.pending.is_empty() {
        self.column = next
        self.pending.push(byte)
        break
      }
      let mut split = self.pending.length()
      if self.spaces {
        for index = self.pending.length() - 1; index >= 0; index = index - 1 {
          if self.pending[index] is (b' ' | b'\t') {
            split = index + 1
            break
          }
        }
      }
      out.append(self.pending[:split])
      out.push(b'\n')
      self.pending = self.pending[split:].to_owned()
      self.column = 0
      for buffered in self.pending {
        self.column = self.advance(buffered)
      }
    }
  }
  Bytes::from_array(out)
}

///|
async fn fold_stream(reader : &@io.Reader, state : FoldState) -> Unit {
  while @stream.read_chunk(reader) is Some(chunk) {
    @stdio.stdout.write(state.feed(chunk))
  }
  @stdio.stdout.write(Bytes::from_array(state.pending))
}