# Copyright (c) 2023 Yuki Kimoto
# MIT License

class StringBuffer {
  version_from SPVM;
  use Fn;
  use Array;
  
  interface Cloneable;
  interface Comparable;
  interface EqualityCheckable;
  
  # Fields
  has capacity : virtual ro int
    get {
      return capacity $self->{string};
    }
  ;
  
  has length : virtual ro int
    get {
      return length $self->{string};
    }
  ;
  
  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) {
    
    my $self = new StringBuffer;
    
    unless ($length >= 0) {
      die "The length \$length must be greater than or equal to 0.";
    }
    
    $self->{string} = new_string_len 0;
    
    if ($capacity >= 0) {
      set_capacity($self->{string}, $capacity);
    }
    
    set_length($self->{string}, $length);
    
    return $self;
  }
  
  static method new_ref : StringBuffer ($string : mutable string) {
    
    unless ($string) {
      die "The string \$string must be defined.";
    }
    
    my $self = new StringBuffer;
    
    $self->{string} = $string;
    
    return $self;
  }
  
  # Instance methods
  method push : void ($string : string, $offset : int = 0, $length : int = -1) {
    
    unless ($string) {
      die "The string \$string must be defined.";
    }
    my $string_length = length $string;
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    if ($length == -1) {
      $length = length $string - $offset;
    }
    
    unless ($offset + $length <= $string_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the string $string.";
    }
    
    my $buffer_length = $self->length;
    my $new_length = $buffer_length + $length;
    $self->_maybe_extend($new_length);
    
    $self->set_length($self->length + $length);
    
    Fn->memcpy($self->{string}, $buffer_length, $string, $offset, $length);
  }
  
  method push_char : void ($char : int) {
    my $length = $self->length;
    
    my $new_length = $length + 1;
    $self->_maybe_extend($new_length);
    
    $self->set_length($self->length + 1);
    
    $self->{string}[$self->length - 1] = (byte)$char;
  }
  
  method reserve : void ($new_capacity : int) {
    unless ($new_capacity >= 0) {
      die "The new capacity \$new_capacity must be greater than or equal to 0.";
    }
    
    my $capacity = capacity $self->{string};
    
    if ($new_capacity > $capacity) {
      set_capacity($self->{string}, $new_capacity);
    }
  }
  
  method to_string : string () {
    
    my $new_string = copy $self->{string};
    
    return $new_string;
  }
  
  method get_string : string () {
    return $self->{string};
  }
  
  private method _maybe_extend : void ($min_capacity : int) {
    my $capacity = capacity $self->{string};
    
    unless ($min_capacity > $capacity) {
      return;
    }
    
    if ($capacity < $min_capacity) {
      $capacity = $min_capacity;
    }
    
    my $new_capacity = $capacity * 2;
    
    set_capacity($self->{string}, $new_capacity);
  }
  
  method set_length : void ($length : int) {
    
    set_length($self->{string}, $length);
  }
  
  method set : void ($string : string) {
    
    $self->set_length(0);
    
    $self->push($string);
  }
  
  method clone : StringBuffer () {
    
    my $string = $self->to_string;
    
    my $clone = &new($string);
    
    return $clone;
  }
  
  method cmp : int ($a : StringBuffer, $b : StringBuffer) {
    
    my $a_string = $a ? $a->{string} : undef;
    
    my $b_string = $b ? $b->{string} : undef;
    
    return $a_string cmp $b_string ;
  }
  
  method eq : int ($a : StringBuffer, $b : StringBuffer) {
    
    my $eq = 0;
    
    if ($a && $b) {
      $eq = $a->cmp($a, $b) == 0;
    }
    elsif ($a) {
      $eq = 0;
    }
    elsif ($b) {
      $eq = 0;
    }
    else {
      $eq = 1;
    }
    
    return $eq;
  }
  
  method compare_string : int ($string : string) {
    
    return $self->{string} cmp $string;
  }
  
  method equals_string : int ($string : string) {
    
    my $cmp = $self->compare_string($string);
    
    my $eq = ($cmp == 0);
    
    return $eq;
  }
  
  method substr : string ($offset : int, $length : int = -1, $replacement : string = undef) {
    
    return Fn->substr($self->{string}, $offset, $length, $replacement);
  }
  
  method index : int ($substring : string, $begin : int = 0, $end : int = -1) {
    
    return Fn->index($self->{string}, $substring, $begin, $end);
  }
  
  method contains : int ($substring : string, $begin : int = 0, $end : int = -1) {
    
    return Fn->contains($self->{string}, $substring, $begin, $end);
  }
  
}
