package SPVM::StringBuffer {
  use SPVM::Hash;
  use SPVM::Util (sliceb);

  has value : byte[];
  has length : ro int;
  
  private enum {
    DEFAULT_CAPACITY = 16,
  }
  
  sub capacity : int ($self : self) {
    return @$self->{value};
  }

  # O($new_capacity)
  private sub _reallocate : void ($self : self, $new_capacity : int) {
    my $new_string = new byte [$new_capacity];
    for (my $i = 0; $i < $self->{length}; ++$i) {
      $new_string->[$i] = $self->{value}[$i];
    }
    $self->{value} = $new_string;
  }

  sub new : SPVM::StringBuffer () {
    my $self = new SPVM::StringBuffer;
    my $default_capacity = DEFAULT_CAPACITY();
    $self->{value} = new byte [$default_capacity];
    $self->{length} = 0;
    return $self;
  }
  
  sub new_opt : SPVM::StringBuffer ($options : object[]) {
    my $options_h = SPVM::Hash->new($options);
    
    my $self = new SPVM::StringBuffer;

    my $default_capacity : int;
    if ($options_h->exists("capacity")) {
      $default_capacity = (int)$options_h->delete("capacity");
    }
    else {
      $default_capacity = DEFAULT_CAPACITY();
    }
    
    # Minimal capacity is 1
    if ($default_capacity < 1) {
      die "Invalid capacity";
    }
    
    $self->{value} = new byte [$default_capacity];
    $self->{length} = 0;
    
    return $self;
  }

  sub push  : void ($self : self, $string : string) {
    my $length = length($string);
    my $capacity = @$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;
  }

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

  sub push_range  : void ($self : self, $string : string, $offset : int, $length : int) {
    my $capacity = @$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->[$offset + $i];
    }
    $self->{length} += $length;
  }

  sub to_string : string ($self : self) {
    return (string)(sliceb($self->{value}, 0, $self->{length}));
  }

  sub substr : string ($self : self, $offset : int, $length : int) {
    if ($offset >= $self->{length}) {
      die("substr offset is greater or equal than string length");
    }
    
    my $new_text = new byte[$length];
    
    for (my $i = 0; $i < $length; $i++) {
      $new_text->[$i] = $self->{value}[$offset + $i];
    }
    
    return (string)$new_text;
  }

  sub index : int ($self : self, $search : string, $offset : int) {
    my $slen = length $search;
    for (my $i = $offset; $i < $self->{length} - $slen + 1; ++$i) {
      my $found = 1;
      for (my $j = 0; $j < $slen; ++$j) {
        if ($self->{value}[$i + $j] != $search->[$j]) {
          $found = 0;
          last;
        }
      }
      if ($found) {
        return $i;
      }
    }
    return -1;
  }

  sub clear : void ($self : self) {
    $self->{length} = 0;
  }
}
