class StringBuffer(var buf: Array[Byte], var length: Int) {
  @static fun empty() -> StringBuffer = StringBuffer(Array[Byte](0), 0);

  fun length() -> Int {
    return self.length;
  }

  fun capacity() -> Int {
    return self.buf.length();
  }

  // reserve `elements` bytes
  // (capacity - length >= elements)
  fun reserve(elements: Int) {
    if self.capacity() - self.length() >= elements {
      return;
    }

    let newcap = self.newCapacity(elements);
    let newbuf = Array[Byte](newcap);
    var i = 0;

    while i < self.buf.length() {
      newbuf.set(i, self.buf.get(i));
      i = i + 1;
    }

    self.buf = newbuf;
  }

  fun newCapacity(reserve: Int) -> Int {
    var len = self.length;

    if len == 0 {
      len = 4;
    }

    let c1 = (len + reserve + 7) & !8;
    let c2 = len * 2;

    if c1 > c2 {
      return c1;
    } else if c2 - c1 > 32 {
      return c1;
    } else {
      return c2;
    }
  }

  fun appendChar(ch: Char) -> StringBuffer {
    let chLen = ch.lenUtf8();
    self.reserve(chLen);
    ch.encodeUtf8(self.buf, self.length);
    self.length = self.length + chLen;
    return self;
  }

  fun append(value: String) -> StringBuffer {
    self.reserve(value.length());
    var i = 0;

    while i < value.length() {
      self.buf.set(self.length + i, value.getByte(i));
      i = i + 1;
    }

    self.length = self.length + value.length();
    return self;
  }

  fun toString() -> String = try! String::fromBytesPart(self.buf, 0, self.length());
}