# Copyright (c) 2023 Yuki Kimoto
# MIT License

class StringBuffer {
  use Fn;
  use Array;
  
  # Enumerations
  private enum {
    DEFAULT_CAPACITY = 4,
  }
  
  # Fields
  has capacity : ro int;
  has length : ro int;
  has string : mutable string;
  
  # Class methods
  static method new : StringBuffer ($string : string = undef, $capacity : int = -1) {
    my $length : int;
    if ($string) {
      $length = length $string;
    }
    else {
      $length = 0;
    }
    
    my $self = &new_len($length, $capacity);
    
    if ($string) {
      Fn->memcpy($self->{string}, 0, $string, 0, $length);
    }
    
    return $self;
  }
  
  static method new_len : StringBuffer ($length : int, $capacity : int = -1) {
    
    unless ($length >= 0) {
      die "\$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->{string} = (mutable string)new_string_len($capacity);
    $self->{capacity} = $capacity;
    $self->{length} = $length;
    
    return $self;
  }
  
  # Instance methods
  method push : void ($string : string, $offset : int = 0, $length : int = -1) {
    
    unless ($string) {
      die "\$string must be defined.";
    }
    my $string_length = length $string;
    
    unless ($offset >= 0) {
      die "\$offset must be greater than or equal to 0.";
    }
    
    if ($length == -1) {
      $length = length $string - $offset;
    }
    
    unless ($offset + $length <= $string_length) {
      die "\$offset + \$length must be less than or equal to the length of \$string.";
    }
    
    my $buffer_length = $self->{length};
    my $new_length = $buffer_length + $length;
    $self->_maybe_extend($new_length);
    
    Fn->memcpy($self->{string}, $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->{string}[$self->{length}++] = (byte)$char;
  }
  
  method replace : void ($offset : int, $remove_length : int, $replace : string) {
    unless ($offset >= 0) {
      die "\$offset must be greater than or equal to 0.";
    }
    
    unless ($remove_length >= 0) {
      die "\$remove_length must be greater than or equal to 0.";
    }
    unless ($offset + $remove_length <= $self->{length}) {
      die "\$offset + \$removing lenght must be less than or equal to the length of \$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->{string}, $offset + $replace_length, $self->{string}, $offset + $remove_length, $move_length);
    
    if ($replace) {
      Fn->memcpy($self->{string}, $offset, $replace, 0, $replace_length);
    }
    
    $self->{length} = $new_length;
  }
  
  method reserve : void ($new_capacity : int) {
    unless ($new_capacity >= 0) {
      die "\$new_capacity must be greater than or equal to 0.";
    }
    
    my $capacity = $self->{capacity};
    
    if ($new_capacity > $capacity) {
      my $length = $self->{length};
      my $new_buffer = (mutable string)new_string_len $new_capacity;
      Fn->memcpy($new_buffer, 0, $self->{string}, 0, $length);
      $self->{string} = $new_buffer;
      $self->{capacity} = $new_capacity;
    }
  }
  
  method to_string : string () {
    my $new_string = Fn->substr($self->{string}, 0, $self->{length});
    return $new_string;
  }
  
  method get_string_unsafe : string () {
    return $self->{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_buffer = (mutable string)new_string_len $new_capacity;
    
    my $length = $self->{length};
    my $buffer = $self->{string};
    
    Fn->memcpy($new_buffer, 0, $buffer, 0, $length);
    
    $self->{string} = $new_buffer;
    $self->{capacity} = $new_capacity;
  }
  
  method set_length : void ($length : int) {
    
    unless ($length >= 0) {
      die "\$length must be greater than or equal to 0.";
    }
    
    $self->_maybe_extend($length);
    
    $self->{length} = $length;
    
    if ($length < $self->{capacity}) {
      Fn->memset_char($self->{string}, '\0', $length, $self->{capacity} - $length);
    }
    
  }
  
  method set : void ($string : string) {
    
    $self->set_length(0);
    
    $self->push($string);
  }
}