# Copyright (c) 2023 Yuki Kimoto
# MIT License

class StringBuffer {
  use Fn;
  use Array;
  
  # Enumerations
  enum {
    DEFAULT_CAPACITY = 4,
  }
  
  # Fields
  has capacity : ro int;
  has length : ro int;
  has value : ro mutable string;
  
  # Class methods
  static method new : StringBuffer ($string = undef : string, $capacity = -1 : int) {
    my $length : int;
    if ($string) {
      $length = length $string;
    }
    else {
      $length = 0;
    }
    
    my $self = &new_len($length, $capacity);
    
    if ($string) {
      Fn->memcpy($self->{value}, 0, $string, 0, $length);
    }
    
    return $self;
  }
  
  static method new_len : StringBuffer ($length : int, $capacity = -1 : int) {
    
    unless ($length >= 0) {
      die "The \$length must be greater than or equal to 0";
    }
    
    if ($capacity < 0) {
      $capacity = &DEFAULT_CAPACITY;
    }
    
    if ($length > $capacity) {
      $capacity = $length;
    }
    
    my $self = new StringBuffer;
    $self->{value} = (mutable string)new_string_len($capacity);
    $self->{capacity} = $capacity;
    $self->{length} = $length;
    
    return $self;
  }
  
  # Instance methods
  method push : void ($string : string, $offset = 0 : int, $length = -1 : int) {
    
    unless ($string) {
      die "The \$string must be defined";
    }
    my $string_length = length $string;
    
    unless ($offset >= 0) {
      die "The \$offset must be greater than or equal to 0";
    }
    
    if ($length == -1) {
      $length = length $string - $offset;
    }
    
    unless ($offset + $length <= $string_length) {
      die "The \$offset + \$length must be less than or equal to the length of the \$string";
    }
    
    my $buffer_length = $self->{length};
    my $new_length = $buffer_length + $length;
    $self->_maybe_extend($new_length);
    
    Fn->memcpy($self->{value}, $buffer_length, $string, $offset, $length);
    
    $self->{length} += $length;
  }
  
  method push_char : void ($char : int) {
    my $length = $self->{length};
    
    my $new_length = $length + 1;
    $self->_maybe_extend($new_length);
    
    $self->{value}[$self->{length}++] = (byte)$char;
  }
  
  method replace : void ($offset : int, $remove_length : int, $replace : string) {
    unless ($offset >= 0) {
      die("The \$offset must be greater than or equal to 0");
    }
    
    unless ($remove_length >= 0) {
      die("The \$remove_length must be greater than or equal to 0");
    }
    unless ($offset + $remove_length <= $self->{length}) {
      die("The \$offset + the \$removing lenght must be less than or equal to the length of the \$string buffer");
    }
    
    my $replace_length = 0;
    if ($replace) {
      $replace_length = length $replace;
    }
    
    my $new_length = $self->{length} - $remove_length + $replace_length;
    $self->_maybe_extend($new_length);
    
    my $move_length = $self->{length} - $offset - $remove_length;
    Fn->memmove($self->{value}, $offset + $replace_length, $self->{value}, $offset + $remove_length, $move_length);
    
    if ($replace) {
      Fn->memcpy($self->{value}, $offset, $replace, 0, $replace_length);
    }
    
    $self->{length} = $new_length;
  }
  
  method reserve : void ($new_capacity : int) {
    unless ($new_capacity >= 0) {
      die "The \$new_capacity must be greater than or equal to 0";
    }
    
    my $capacity = $self->{capacity};
    
    if ($new_capacity > $capacity) {
      my $length = $self->{length};
      my $new_value = (mutable string)new_string_len $new_capacity;
      Fn->memcpy($new_value, 0, $self->{value}, 0, $length);
      $self->{value} = $new_value;
      $self->{capacity} = $new_capacity;
    }
  }
  
  method to_string : string () {
    my $string = Fn->substr($self->{value}, 0, $self->{length});
    return $string;
  }
  
  private method _maybe_extend : void ($min_capacity : int) {
    my $capacity = $self->{capacity};
    
    unless ($min_capacity > $capacity) {
      return;
    }
    
    if ($capacity < $min_capacity) {
      $capacity = $min_capacity;
    }
    
    my $new_capacity = $capacity * 2;
    my $new_value = (mutable string)new_string_len $new_capacity;
    
    my $length = $self->{length};
    my $value = $self->{value};
    
    Fn->memcpy($new_value, 0, $value, 0, $length);
    
    $self->{value} = $new_value;
    $self->{capacity} = $new_capacity;
  }
}