class StringBuffer : precompile {
  use Fn;

  has value : ro mutable string;
  has length : ro int;
  
  static method new : StringBuffer () {
    my $self = new StringBuffer;
    my $default_capacity = StringBuffer->DEFAULT_CAPACITY();
    $self->{value} = (mutable string)new_string_len($default_capacity);
    $self->{length} = 0;
    return $self;
  }
  
  method push : void ($string : string) {
    my $length = length($string);
    my $capacity = length $self->{value};
    if ($self->{length} + $length > $capacity) {
      my $new_capacity : int;
      if ($self->{length} + $length > $capacity * 2) {
        $new_capacity = $self->{length} + $length;
      } else {
        $new_capacity = $capacity * 2;
      }
      $self->_reallocate($new_capacity);
    }
    for (my $i = 0; $i < $length; ++$i) {
      $self->{value}[$self->{length} + $i] = $string->[$i];
    }
    $self->{length} += $length;
  }

  method push_char : void ($char : byte) {
    my $capacity = length $self->{value};
    if ($self->{length} + 1 > $capacity) {
      my $new_capacity = $capacity * 2;
      $self->_reallocate($new_capacity);
    }
    $self->{value}[$self->{length}++] = $char;
  }

  method to_string : string () {
    return (Fn->substr($self->{value}, 0, $self->{length}));
  }

  private enum {
    DEFAULT_CAPACITY = 16,
  }
  
  # O($new_capacity)
  private method _reallocate : void ($new_capacity : int) {
    my $new_string = (mutable string)new_string_len($new_capacity);
    for (my $i = 0; $i < $self->{length}; ++$i) {
      $new_string->[$i] = $self->{value}[$i];
    }
    $self->{value} = $new_string;
  }
}