package SPVM::StringList {
  has values   : string[];
  has capacity : ro int;
  has length   : ro int;
  
  private enum {
    DEFAULT_CAPACITY = 16,
  }
  sub new_capacity : SPVM::StringList ($capacity : int) {
    my $self = new SPVM::StringList;
    unless ($capacity > 0) {
      die "capacity must be positive";
    }
    $self->{capacity} = $capacity;
    $self->{values} = new string[$capacity];
    return $self;
  }

  sub new : SPVM::StringList ($array : string[]) {
    my $self = new SPVM::StringList;
    my $length = @$array;
    
    my $capacity = 0;
    if ($length < DEFAULT_CAPACITY()) {
      $capacity = DEFAULT_CAPACITY();
    }
    else {
      $capacity = $length;
    }
    $self->{capacity} = $capacity;
    my $values = new string[$capacity];
    
    $self->{length} = $length;
    
    for (my $i = 0; $i < $length; $i++) {
      $values->[$i] = $array->[$i];
    }
    
    $self->{values} = $values;
    
    return $self;
  }

  sub new_len : SPVM::StringList ($length : int) {
    my $self = new SPVM::StringList;
    unless ($length >= 0) {
      die "length must be more than or equals to zero";
    }
    my $capacity = 0;
    if ($length < DEFAULT_CAPACITY()) {
      $capacity = DEFAULT_CAPACITY();
    }
    else {
      $capacity = $length;
    }
    $self->{capacity} = $capacity;
    $self->{length} = $length;
    $self->{values} = new string[$capacity];
    return $self;
  }
  
  sub insert : void ($self : self, $index : int, $value : string) {
    my $length = $self->{length};
    my $capacity = $self->{capacity};
    
    unless ($index >= 0 && $index <= $length) {
      die "Out of range";
    }
    
    if ($length >= $capacity) {
      $self->_extend;
    }
    
    my $values = $self->{values};
    if ($index != $length) {
      for (my $i = $length - 1; $i >= $index; $i--) {
        $values->[$i + 1] = $values->[$i];
      }
    }
    $values->[$index] = $value;
    
    $self->{length}++;
  }

  sub remove : string ($self : self, $index : int) {
    my $length = $self->{length};
    my $capacity = $self->{capacity};
    
    unless ($index >= 0 && $index < $length) {
      die "Out of range";
    }
    
    my $values = $self->{values};
    my $remove_value = $values->[$index];

    for (my $i = $index; $i < $length - 1; $i++) {
      $values->[$i] = $values->[$i + 1];
    }
    $values->[$length - 1] = undef;
    
    $self->{length}--;
    
    return $remove_value;
  }
  
  sub push : void ($self : self, $value : string) {
    my $length = $self->{length};
    my $capacity = $self->{capacity};
    
    if ($length >= $capacity) {
      $self->_extend;
    }
    
    $self->{values}[$length] = $value;
    
    $self->{length}++;
  }
  
  sub pop : string ($self : self) {
    my $length = $self->length;
    
    if ($length == 0) {
      die "Length must be more than 0 for pop";
    }
    
    my $value = $self->{values}[$length - 1];
    
    $self->{length}--;
    
    return $value;
  }
  
  sub unshift : void ($self : self, $value : string) {
    my $length = $self->{length};
    my $capacity = $self->{capacity};
    
    if ($length >= $capacity) {
      $self->_extend;
    }
    
    my $values = $self->{values};
    
    for (my $i = 0; $i < $length; $i++) {
      $values->[$length - $i] = $values->[$length - $i - 1];
    }
    
    $values->[0] = $value;
    $self->{length}++;
  }
  
  sub shift : string ($self : self) {
    my $length = $self->{length};
    my $capacity = $self->{capacity};
    
    if ($length == 0) {
      die "Length must be more than 0 for shift";
    }
    
    my $values = $self->{values};
    
    my $value = $values->[0];
    
    for (my $i = 0; $i < $length - 1; $i++) {
      $values->[$i] = $values->[$i + 1];
    }
    
    $self->{length}--;
    
    return $value;
  }
  
  sub set : void ($self : self, $index : int, $value : string) {
    my $length = $self->length;
    
    if ($index < 0 || $index > $length - 1) {
      die "Out of range";
    }
    
    $self->{values}[$index] = $value;
  }
  
  sub get : string ($self : self, $index : int) {
    my $length = $self->length;
    
    if ($index < 0 || $index > $length - 1) {
      die "Out of range";
    }
    
    my $value = $self->{values}[$index];
    
    return $value;
  }
  
  sub grep : int ($self : self, $search : string) {
    for (my $i = 0; $i < $self->length; ++$i) {
      if ($self->{values}->[$i] eq $search) {
        return 1;
      }
    }
    return 0;
  }
  
  sub to_array : string[] ($self : self) {
    my $length = $self->length;
    
    my $array = new string[$length];
    
    my $values = $self->{values};
    
    for (my $i = 0; $i < $length; $i++) {
      $array->[$i] = $values->[$i];
    }
    
    return $array;
  }

  private sub _extend : void ($self : self) {
    my $capacity = $self->{capacity};
    my $length = $self->{length};
    my $values = $self->{values};
    
    my $new_capacity = $capacity * 2;
    
    my $new_values = new string[$new_capacity];
    
    for (my $i = 0; $i < $length; $i++) {
      $new_values->[$i] = $values->[$i];
    }
    
    $self->{values} = $new_values;
    $self->{capacity} = $new_capacity;
  }
}
