# Copyright (c) 2023 Yuki Kimoto
# MIT License

class LongList {
  version_from SPVM;
  
  interface Cloneable;
  interface Countable;
  
  use Fn;
  use Array;
  
  # Fields
  has capacity : virtual ro int
    get {
      return capacity $self->{array};
    }
  ;
  
  has length : virtual ro int
    get {
      return @{$self->{array}};
    }
  ;
  
  has array : long[];
  
  # Class methods
  static method new : LongList ($array : long[] = undef, $capacity : int = -1) {
    my $length : int;
    if ($array) {
      $length = @$array;
    }
    else {
      $length = 0;
    }
    
    my $self = &new_len($length, $capacity);
    
    if ($array) {
      Array->memcpy_long($self->{array}, 0, $array, 0, $length);
    }
    
    return $self;
  }
  
  static method new_len : LongList ($length : int, $capacity : int = -1) {
    my $self = new LongList;
    
    unless ($length >= 0) {
      die "The length \$length must be greater than or equal to 0.";
    }
    
    $self->{array} = new long[0];
    
    if ($capacity >= 0) {
      set_capacity($self->{array}, $capacity);
    }
    
    set_length($self->{array}, $length);
    
    return $self;
  }
  
  static method new_ref : LongList ($array : long[]) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    my $self = new LongList;
    
    $self->{array} = $array;
    
    return $self;
  }
  
  # Instance methods
  method get : long ($index : int) {
    my $length = $self->length;
    
    unless ($index >= 0) {
      $index = $length + $index;
    }
    
    unless ($index >= 0) {
      die "The index \$index must be greater than or equal to 0.";
    }
    
    unless ($index < $length) {
      die "The index \$index must be less than the length of \$list.";
    }
    
    my $element = $self->{array}[$index];
    
    return $element;
  }
  
  method insert : void ($index : int, $element : long) {
    my $length = @{$self->{array}};
    
    unless ($index >= 0) {
      die "The index \$index must be greater than or equal to 0.";
    }
    
    unless ($index <= $length) {
      die "The index \$index must be less than or equal to the length of \$list.";
    }
    
    my $new_length = $length + 1;
    $self->_maybe_extend($new_length);
    
    set_length($self->{array}, $new_length);
    my $elements = $self->{array};
    if ($index != $length) {
      my $dist_index = $index + 1;
      my $move_length = $length - $index;
      Array->memmove_long($elements, $dist_index, $elements, $index, $move_length);
    }
    $elements->[$index] = (long)$element;
    
  }
  
  method pop : long () {
    my $length = $self->length;
    
    unless ($length > 0) {
      die "The length of the list \$list must be greater than 0.";
    }
    
    my $element = $self->{array}[$length - 1];
    
    set_length($self->{array}, @{$self->{array}} - 1);
    
    return $element;
  }
  
  method push : void ($element : long) {
    my $length = @{$self->{array}};
    
    my $new_length = $length + 1;
    $self->_maybe_extend($new_length);
    
    set_length($self->{array}, $new_length);
    $self->{array}[$length] = (long)$element;
  }
  
  method remove : long ($index : int) {
    my $length = @{$self->{array}};
    
    unless ($index >= 0) {
      die "The index \$index must be greater than or equal to 0.";
    }
    
    unless ($index < $length) {
      die "The index \$index must be less than the length of \$list.";
    }
    
    my $elements = $self->{array};
    my $remove_value = $elements->[$index];
    
    my $dist_index = $index;
    my $src_index = $index + 1;
    my $move_length = $length - $index - 1;
    Array->memmove_long($elements, $dist_index, $elements, $src_index, $move_length);
    
    set_length($self->{array}, @{$self->{array}} - 1);
    
    return $remove_value;
  }
  
  method replace : void ($offset : int, $remove_length : int, $replace : long[] = undef) {
    $self->splice($offset, $remove_length, $replace);
  }
  
  method splice : long[] ($offset : int, $remove_length : int, $replace : long[] = undef) {
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    unless ($remove_length >= 0) {
      die "The removal length \$remove_length must be greater than or equal to 0.";
    }
    unless ($offset + $remove_length <= @{$self->{array}}) {
      die "\$offset + \$removing length must be less than or equal to the length of \$list.";
    }
    
    my $replace_length = 0;
    if ($replace) {
      $replace_length = @$replace;
    }
    
    my $removed_elements = Array->copy_long($self->{array}, $offset, $remove_length);
    
    my $new_length = @{$self->{array}} - $remove_length + $replace_length;
    $self->_maybe_extend($new_length);
    
    my $move_length = @{$self->{array}} - $offset - $remove_length;
    Array->memmove_long($self->{array}, $offset + $replace_length, $self->{array}, $offset + $remove_length, $move_length);
    
    set_length($self->{array}, $new_length);
    
    if ($replace) {
      Array->memcpy_long($self->{array}, $offset, $replace, 0, $replace_length);
    }
    
    return $removed_elements;
  }
  
  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->{array};
    
    if ($new_capacity > $capacity) {
      set_capacity($self->{array}, $new_capacity);
    }
  }
  
  method resize : void ($new_length : int) {
    unless ($new_length >= 0) {
      die "The new length \$new_length must be greater than or equal to 0.";
    }
    
    set_length($self->{array}, $new_length);
  }
  
  method set : void ($index : int, $element : long) {
    my $length = $self->length;
    
    unless ($index >= 0) {
      $index = $length + $index;
    }
    
    unless ($index >= 0) {
      die "The index \$index must be greater than or equal to 0.";
    }
    
    unless ($index < $length) {
      die "The index \$index must be less than the length of \$list.";
    }
    
    $self->{array}[$index] = (long)$element;
  }
  
  method shift : long () {
    my $length = @{$self->{array}};
    
    unless ($length > 0) {
      die "The length of the list \$list must be greater than 0.";
    }
    
    my $elements = $self->{array};
    
    my $element = $elements->[0];
    
    Array->memmove_long($elements, 0, $elements, 1, $length - 1);
    
    $elements->[$length - 1] = 0;
    
    set_length($self->{array}, @{$self->{array}} - 1);
    
    return $element;
  }
  
  method to_array : long[] () {
    my $length = $self->length;
    
    my $new_array = new long[$length];
    
    my $elements = $self->{array};
    
    Array->memcpy_long($new_array, 0, $elements, 0, $length);
    
    return $new_array;
  }
  
  method get_array : long[] () {
    return $self->{array};
  }
  
  method unshift : void ($element : long) {
    my $length = @{$self->{array}};
    
    my $new_length = $length + 1;
    
    $self->_maybe_extend($new_length);
    
    my $elements = $self->{array};
    
    set_length($self->{array}, $new_length);
    Array->memmove_long($elements, 1, $elements, 0, $length);
    $elements->[0] = (long)$element;
  }
  
  private method _maybe_extend : void ($min_capacity : int) {
    my $capacity = capacity $self->{array};
    
    unless ($min_capacity > $capacity) {
      return;
    }
    
    if ($capacity < $min_capacity) {
      $capacity = $min_capacity;
    }
    
    my $new_capacity = $capacity * 2;
    
    set_capacity($self->{array}, $new_capacity);
  }
  
  method clone : LongList () {
    
    my $clone = &new_len($self->length, $self->capacity);
    
    $clone->{array} = copy $self->{array};
    
    return $clone;
  }
  
  method push_ : void ($elements : long[]) {
    
    unless ($elements) {
      die "The elements \$elements must be defined.";
    }
    
    for my $element (@$elements) {
      $self->push($element);
    }
    
  }
  
  method unshift_ : void ($elements : long[]) {
    
    unless ($elements) {
      die "The elements \$elements must be defined.";
    }
    
    for (my $i = @$elements - 1; $i >= 0; $i--) {
      my $element = $elements->[$i];
      $self->unshift($element);
    }
    
  }
  
}
