# Copyright (c) 2023 Yuki Kimoto
# MIT License

class Array {
  version_from SPVM;

  use Stringer;
  use Cloner;
  use EqualityChecker;
  use Format;
  use StringList;
  
  static method copy_byte : byte[] ($array : byte[], $offset : int = 0, $length : int = -1) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $array_length = @$array;
    if ($length < 0) {
      $length = $array_length - $offset;
    }
    
    unless ($offset + $length <= $array_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the array \$array.";
    }
    
    my $new_array = new byte[$length];
    
    Array->memcpy_byte($new_array, 0, $array, $offset, $length);
    
    return $new_array;
  }
  
  static method copy_double : double[] ($array : double[], $offset : int = 0, $length : int = -1) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $array_length = @$array;
    if ($length < 0) {
      $length = $array_length - $offset;
    }
    
    unless ($offset + $length <= $array_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the array \$array.";
    }
    
    my $new_array = new double[$length];
    
    Array->memcpy_double($new_array, 0, $array, $offset, $length);
    
    return $new_array;
  }
  
  static method copy_float : float[] ($array : float[], $offset : int = 0, $length : int = -1) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $array_length = @$array;
    if ($length < 0) {
      $length = $array_length - $offset;
    }
    
    unless ($offset + $length <= $array_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the array \$array.";
    }
    
    my $new_array = new float[$length];
    
    Array->memcpy_float($new_array, 0, $array, $offset, $length);
    
    return $new_array;
  }
  
  static method copy_int : int[] ($array : int[], $offset : int = 0, $length : int = -1) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $array_length = @$array;
    if ($length < 0) {
      $length = $array_length - $offset;
    }
    
    unless ($offset + $length <= $array_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the array \$array.";
    }
    
    my $new_array = new int[$length];
    
    Array->memcpy_int($new_array, 0, $array, $offset, $length);
    
    return $new_array;
  }
  
  static method copy_long : long[] ($array : long[], $offset : int = 0, $length : int = -1) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $array_length = @$array;
    if ($length < 0) {
      $length = $array_length - $offset;
    }
    
    unless ($offset + $length <= $array_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the array \$array.";
    }
    
    my $new_array = new long[$length];
    
    Array->memcpy_long($new_array, 0, $array, $offset, $length);
    
    return $new_array;
  }
  
  precompile static method copy_object : object[] ($array : object[], $cloner : Cloner = undef, $offset : int = 0, $length : int = -1) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    my $proto = &new_proto($array, 0);
    
    my $new_array = &copy_object_with_proto($array, $proto, $cloner, $offset, $length);
    
    return $new_array;
  }
  
  static method copy_object_address : object[] ($array : object[], $offset : int = 0, $length : int = -1) {
    return &copy_object($array, undef, $offset, $length);
  }
  
  static method copy_short : short[] ($array : short[], $offset : int = 0, $length : int = -1) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $array_length = @$array;
    if ($length < 0) {
      $length = $array_length - $offset;
    }
    
    unless ($offset + $length <= $array_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the array \$array.";
    }
    
    my $new_array = new short[$length];
    
    Array->memcpy_short($new_array, 0, $array, $offset, $length);
    
    return $new_array;
  }
  
  precompile static method copy_string : string[] ($array : string[], $offset : int = 0, $length : int = -1) {
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $array_length = @$array;
    if ($length < 0) {
      $length = $array_length - $offset;
    }
    
    unless ($offset + $length <= $array_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the array \$array.";
    }
    
    my $new_array = new string[$length];
    
    for (my $i = 0; $i < $length; $i++) {
      $new_array->[$i] = copy $array->[$offset + $i];
    }
    
    return $new_array;
  }
  
  static method copy_string_address : string[] ($array : string[], $offset : int = 0, $length : int = -1) {
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $array_length = @$array;
    if ($length < 0) {
      $length = $array_length - $offset;
    }
    
    unless ($offset + $length <= $array_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the array \$array.";
    }
    
    my $new_array = new string[$length];
    
    Array->memcpy_string_address($new_array, 0, $array, $offset, $length);
    
    return $new_array;
  }
  
  precompile static method dump_unsigned_byte : string ($array : byte[]) {
    
    unless ($array) {
      return "undef";
    }
    
    my $dump = "[\n";
    
    for (my $i = 0; $i < @$array; $i++) {
      my $string = Format->sprintf("%u", [(object)((int)$array->[$i] & 0xFF)]);
      $dump .= "  $string";
      if ($i != @$array - 1) {
        $dump .= ",\n";
      }
      else {
        $dump .= "\n";
      }
    }
    
    $dump .= "] : " . type_name $array . "(" . Format->sprintf("%p", [(object)$array])  . ")";
    
    return $dump;
  }
  
  precompile static method dump_unsigned_int : string ($array : int[]) {
    
    unless ($array) {
      return "undef";
    }
    
    my $dump = "[\n";
    
    for (my $i = 0; $i < @$array; $i++) {
      my $string = Format->sprintf("%u", [(object)$array->[$i]]);
      $dump .= "  $string";
      if ($i != @$array - 1) {
        $dump .= ",\n";
      }
      else {
        $dump .= "\n";
      }
    }
    
    $dump .= "] : " . type_name $array . "(" . Format->sprintf("%p", [(object)$array])  . ")";
    
    return $dump;
  }
  
  precompile static method dump_unsigned_long : string ($array : long[]) {
    
    unless ($array) {
      return "undef";
    }
    
    my $dump = "[\n";
    
    for (my $i = 0; $i < @$array; $i++) {
      my $string = Format->sprintf("%lu", [(object)$array->[$i]]);
      $dump .= "  $string";
      if ($i != @$array - 1) {
        $dump .= ",\n";
      }
      else {
        $dump .= "\n";
      }
    }
    
    $dump .= "] : " . type_name $array . "(" . Format->sprintf("%p", [(object)$array])  . ")";
    return $dump;
  }
  
  precompile static method dump_unsigned_short : string ($array : short[]) {
    
    unless ($array) {
      return "undef";
    }
    
    my $dump = "[\n";
    
    for (my $i = 0; $i < @$array; $i++) {
      my $string = Format->sprintf("%u", [(object)((int)$array->[$i] & 0xFFFF)]);
      $dump .= "  $string";
      if ($i != @$array - 1) {
        $dump .= ",\n";
      }
      else {
        $dump .= "\n";
      }
    }
    
    $dump .= "] : " . type_name $array . "(" . Format->sprintf("%p", [(object)$array])  . ")";
    
    return $dump;
  }
  
  precompile static method equals_byte : int ($array1 : byte[], $array2 : byte[]) {
    
    if ($array1 == undef && $array2 == undef) {
      return 1;
    }
    elsif ($array1 != undef && $array2 == undef) {
      return 0;
    }
    elsif ($array1 == undef && $array2 != undef) {
      return 0;
    }
    
    my $is_equals = 1;
    if (@$array1 == @$array2) {
      for (my $i = 0; $i < @$array1; $i++) {
        if ($array1->[$i] != $array2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }
    
    return $is_equals;
  }
  
  precompile static method equals_double : int ($array1 : double[], $array2 : double[]) {
    if ($array1 == undef && $array2 == undef) {
      return 1;
    }
    elsif ($array1 != undef && $array2 == undef) {
      return 0;
    }
    elsif ($array1 == undef && $array2 != undef) {
      return 0;
    }
    
    my $is_equals = 1;
    if (@$array1 == @$array2) {
      for (my $i = 0; $i < @$array1; $i++) {
        if ($array1->[$i] != $array2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }

  precompile static method equals_float : int ($array1 : float[], $array2 : float[]) {
    if ($array1 == undef && $array2 == undef) {
      return 1;
    }
    elsif ($array1 != undef && $array2 == undef) {
      return 0;
    }
    elsif ($array1 == undef && $array2 != undef) {
      return 0;
    }
    
    my $is_equals = 1;
    if (@$array1 == @$array2) {
      for (my $i = 0; $i < @$array1; $i++) {
        if ($array1->[$i] != $array2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }

  precompile static method equals_int : int ($array1 : int[], $array2 : int[]) {
    if ($array1 == undef && $array2 == undef) {
      return 1;
    }
    elsif ($array1 != undef && $array2 == undef) {
      return 0;
    }
    elsif ($array1 == undef && $array2 != undef) {
      return 0;
    }
    
    my $is_equals = 1;
    if (@$array1 == @$array2) {
      for (my $i = 0; $i < @$array1; $i++) {
        if ($array1->[$i] != $array2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }
  precompile static method equals_long : int ($array1 : long[], $array2 : long[]) {
    if ($array1 == undef && $array2 == undef) {
      return 1;
    }
    elsif ($array1 != undef && $array2 == undef) {
      return 0;
    }
    elsif ($array1 == undef && $array2 != undef) {
      return 0;
    }
    
    my $is_equals = 1;
    if (@$array1 == @$array2) {
      for (my $i = 0; $i < @$array1; $i++) {
        if ($array1->[$i] != $array2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }

  precompile static method equals_object : int ($array1 : object[], $array2 : object[], $equality_checker : EqualityChecker) {
    if ($array1 == undef && $array2 == undef) {
      return 1;
    }
    elsif ($array1 != undef && $array2 == undef) {
      return 0;
    }
    elsif ($array1 == undef && $array2 != undef) {
      return 0;
    }
    
    my $is_equals = 1;
    if (@$array1 == @$array2) {
      if ($equality_checker) {
        for (my $i = 0; $i < @$array1; $i++) {
          unless ($equality_checker->($array1->[$i], $array2->[$i])) {
            $is_equals = 0;
            last;
          }
        }
      }
      else {
        for (my $i = 0; $i < @$array1; $i++) {
          unless ($array1->[$i] == $array2->[$i]) {
            $is_equals = 0;
            last;
          }
        }
      }
    }
    else {
      $is_equals = 0;
    }
   
    return $is_equals;
  }
  
  static method equals_object_address : int ($array1 : object[], $array2 : object[]) {
    return &equals_object($array1, $array2, undef);
  }
  
  precompile static method equals_short : int ($array1 : short[], $array2 : short[]) {
    if ($array1 == undef && $array2 == undef) {
      return 1;
    }
    elsif ($array1 != undef && $array2 == undef) {
      return 0;
    }
    elsif ($array1 == undef && $array2 != undef) {
      return 0;
    }
    
    my $is_equals = 1;
    if (@$array1 == @$array2) {
      for (my $i = 0; $i < @$array1; $i++) {
        if ($array1->[$i] != $array2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }

  precompile static method equals_string : int ($array1 : string[], $array2 : string[]) {
    if ($array1 == undef && $array2 == undef) {
      return 1;
    }
    elsif ($array1 != undef && $array2 == undef) {
      return 0;
    }
    elsif ($array1 == undef && $array2 != undef) {
      return 0;
    }
    
    my $is_equals = 1;
    if (@$array1 == @$array2) {
      for (my $i = 0; $i < @$array1; $i++) {
        if ($array1->[$i] ne $array2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }
  
  static method equals_string_address : int ($array1 : string[], $array2 : string[]) {
    return &equals_object_address($array1, $array2);
  }

  native static method memcpy_byte : void ($dest : byte[], $dest_offset : int, $source : byte[], $source_offset : int, $length : int);
  native static method memcpy_double : void ($dest : double[], $dest_offset : int, $source : double[], $source_offset : int, $length : int);
  native static method memcpy_float : void ($dest : float[], $dest_offset : int, $source : float[], $source_offset : int, $length : int);
  native static method memcpy_int : void ($dest : int[], $dest_offset : int, $source : int[], $source_offset : int, $length : int);
  native static method memcpy_long : void ($dest : long[], $dest_offset : int, $source : long[], $source_offset : int, $length : int);
  native static method memcpy_short : void ($dest : short[], $dest_offset : int, $source : short[], $source_offset : int, $length : int);
  static method memcpy_string_address : void ($dest : string[], $dest_offset : int, $source : string[], $source_offset : int, $length : int) {
    &memcpy_object_address($dest, $dest_offset, $source, $source_offset, $length);
  }
  precompile static method memcpy_object_address : void ($dest : object[], $dest_offset : int, $source : object[], $source_offset : int, $length : int) {
    unless ($dest) {
      die "The destination \$dest must be defined.";
    }
    unless ($source) {
      die "The source \$source must be defined.";
    }
    unless ($length >= 0) {
      die "The length \$length must be greater than or equal to 0.";
    }
    
    unless ($dest_offset >= 0) {
      die "The destination offset \$dest_offset must be greater than or equal to 0.";
    }
    
    unless ($source_offset >= 0) {
      die "The source offset \$source_offset must be greater than or equal to 0.";
    }
    
    my $dest_length = @$dest;
    my $source_length = @$source;
    
    unless ($dest_offset + $length <= $dest_length) {
      die "The destination offset \$dest_offset + the length \$length must be less than or equal to the length of the destination \$dest.";
    }
    
    unless ($source_offset + $length <= $source_length) {
      die "The source offset \$source_offset + the length \$length must be less than or equal to the length of the source \$source.";
    }
    
    for (my $i = 0; $i < $length; $i++) {
      my $dist_index = $dest_offset + $i;
      my $source_index = $source_offset + $i;
      $dest->[$dist_index] = $source->[$source_index];
    }
  }
  
  native static method memmove_byte : void ($dest : byte[], $dest_offset : int, $source : byte[], $source_offset : int, $length : int);
  native static method memmove_double : void ($dest : double[], $dest_offset : int, $source : double[], $source_offset : int, $length : int);
  native static method memmove_float : void ($dest : float[], $dest_offset : int, $source : float[], $source_offset : int, $length : int);
  native static method memmove_int : void ($dest : int[], $dest_offset : int, $source : int[], $source_offset : int, $length : int);
  native static method memmove_long : void ($dest : long[], $dest_offset : int, $source : long[], $source_offset : int, $length : int);
  native static method memmove_short : void ($dest : short[], $dest_offset : int, $source : short[], $source_offset : int, $length : int);
  static method memmove_string_address : void ($dest : string[], $dest_offset : int, $source : string[], $source_offset : int, $length : int) {
    &memmove_object_address($dest, $dest_offset, $source, $source_offset, $length);
  }
  static method memmove_object_address : void ($dest : object[], $dest_offset : int, $source : object[], $source_offset : int, $length : int) {
    unless ($dest) {
      die "The destination \$dest must be defined.";
    }
    unless ($source) {
      die "The source \$source must be defined.";
    }
    unless ($length >= 0) {
      die "The length \$length must be greater than or equal to 0.";
    }
    
    unless ($dest_offset >= 0) {
      die "The destination offset \$dest_offset must be greater than or equal to 0.";
    }
    
    unless ($source_offset >= 0) {
      die "The source offset \$source_offset must be greater than or equal to 0.";
    }
    
    my $dest_length = @$dest;
    my $source_length = @$source;
    
    unless ($dest_offset + $length <= $dest_length) {
      die "The destination offset \$dest_offset + the length \$length must be less than or equal to the length of the destination \$dest.";
    }
    
    unless ($source_offset + $length <= $source_length) {
      die "The source offset \$source_offset + the length \$length must be less than or equal to the length of the source \$source.";
    }
    
    if ($dest_offset < $source_offset) {
      for (my $i = 0; $i < $length; $i++) {
        my $dist_index = $dest_offset + $i;
        my $source_index = $source_offset + $i;
        $dest->[$dist_index] = $source->[$source_index];
      }
    }
    else {
      for (my $i = $length - 1; $i >= 0; $i--) {
        my $dist_index = $dest_offset + $i;
        my $source_index = $source_offset + $i;
        $dest->[$dist_index] = $source->[$source_index];
      }
    }
  }
  
  precompile static method memset_byte : void ($array : byte[], $element : int, $offset : int = 0, $length : int = -1) {
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $array_length = @$array;
    if ($length < 0) {
      $length = $array_length - $offset;
    }
    
    unless ($offset + $length <= $array_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the array \$array.";
    }
    
    for (my $i = 0; $i < $length; $i++) {
      $array->[$offset + $i] = (byte)$element;
    }
  }
  
  precompile static method memset_double : void ($array : double[], $element : double, $offset : int = 0, $length : int = -1) {
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $array_length = @$array;
    if ($length < 0) {
      $length = $array_length - $offset;
    }
    
    unless ($offset + $length <= $array_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the array \$array.";
    }
    
    for (my $i = 0; $i < $length; $i++) {
      $array->[$offset + $i] = $element;
    }
  }
  
  precompile static method memset_float : void ($array : float[], $element : float, $offset : int = 0, $length : int = -1) {
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $array_length = @$array;
    if ($length < 0) {
      $length = $array_length - $offset;
    }
    
    unless ($offset + $length <= $array_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the array \$array.";
    }
    
    for (my $i = 0; $i < $length; $i++) {
      $array->[$offset + $i] = $element;
    }
  }
  
  precompile static method memset_int : void ($array : int[], $element : int, $offset : int = 0, $length : int = -1) {
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $array_length = @$array;
    if ($length < 0) {
      $length = $array_length - $offset;
    }
    
    unless ($offset + $length <= $array_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the array \$array.";
    }
    
    for (my $i = 0; $i < $length; $i++) {
      $array->[$offset + $i] = $element;
    }
  }
  
  precompile static method memset_long : void ($array : long[], $element : long, $offset : int = 0, $length : int = -1) {
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $array_length = @$array;
    if ($length < 0) {
      $length = $array_length - $offset;
    }
    
    unless ($offset + $length <= $array_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the array \$array.";
    }
    
    for (my $i = 0; $i < $length; $i++) {
      $array->[$offset + $i] = $element;
    }
  }
  
  precompile static method memset_object : void ($array : object[], $element : object, $offset : int = 0, $length : int = -1) {
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $array_length = @$array;
    if ($length < 0) {
      $length = $array_length - $offset;
    }
    
    unless ($offset + $length <= $array_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the array \$array.";
    }
    
    for (my $i = 0; $i < $length; $i++) {
      $array->[$offset + $i] = $element;
    }
  }
  
  precompile static method memset_short : void ($array : short[], $element : int, $offset : int = 0, $length : int = -1) {
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $array_length = @$array;
    if ($length < 0) {
      $length = $array_length - $offset;
    }
    
    unless ($offset + $length <= $array_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the array \$array.";
    }
    
    for (my $i = 0; $i < $length; $i++) {
      $array->[$offset + $i] = (short)$element;
    }
  }
  
  static method memset_string : void ($array : string[], $element : string, $offset : int = 0, $length : int = -1) {
    &memset_object($array, $element, $offset, $length);
  }
  
  precompile static method merge_byte : byte[] ($array1 : byte[], $array2 : byte[]) {
    unless ($array1) {
      die "The array1 \$array1 must be defined.";
    }
    
    unless ($array2) {
      die "The array2 \$array2 must be defined.";
    }
    
    my $array1_length = @$array1;
    my $array2_length = @$array2;
    my $merged_array_length = $array1_length + $array2_length;
    
    my $merged_array = new byte[$merged_array_length];
    
    for (my $i = 0; $i < $array1_length; $i++) {
      $merged_array->[$i] = $array1->[$i];
    }
    
    for (my $i = 0; $i < $array2_length; $i++) {
      $merged_array->[$array1_length + $i] = $array2->[$i];
    }
    
    return $merged_array;
  }
  
  precompile static method merge_short : short[] ($array1 : short[], $array2 : short[]) {
    unless ($array1) {
      die "The array1 \$array1 must be defined.";
    }
    
    unless ($array2) {
      die "The array2 \$array2 must be defined.";
    }
    
    my $array1_length = @$array1;
    my $array2_length = @$array2;
    my $merged_array_length = $array1_length + $array2_length;
    
    my $merged_array = new short[$merged_array_length];
    
    for (my $i = 0; $i < $array1_length; $i++) {
      $merged_array->[$i] = $array1->[$i];
    }
    
    for (my $i = 0; $i < $array2_length; $i++) {
      $merged_array->[$array1_length + $i] = $array2->[$i];
    }
    
    return $merged_array;
  }
  
  precompile static method merge_int : int[] ($array1 : int[], $array2 : int[]) {
    unless ($array1) {
      die "The array1 \$array1 must be defined.";
    }
    
    unless ($array2) {
      die "The array2 \$array2 must be defined.";
    }
    
    my $array1_length = @$array1;
    my $array2_length = @$array2;
    my $merged_array_length = $array1_length + $array2_length;
    
    my $merged_array = new int[$merged_array_length];
    
    for (my $i = 0; $i < $array1_length; $i++) {
      $merged_array->[$i] = $array1->[$i];
    }
    
    for (my $i = 0; $i < $array2_length; $i++) {
      $merged_array->[$array1_length + $i] = $array2->[$i];
    }
    
    return $merged_array;
  }
  
  precompile static method merge_long : long[] ($array1 : long[], $array2 : long[]) {
    unless ($array1) {
      die "The array1 \$array1 must be defined.";
    }
    
    unless ($array2) {
      die "The array2 \$array2 must be defined.";
    }
    
    my $array1_length = @$array1;
    my $array2_length = @$array2;
    my $merged_array_length = $array1_length + $array2_length;
    
    my $merged_array = new long[$merged_array_length];
    
    for (my $i = 0; $i < $array1_length; $i++) {
      $merged_array->[$i] = $array1->[$i];
    }
    
    for (my $i = 0; $i < $array2_length; $i++) {
      $merged_array->[$array1_length + $i] = $array2->[$i];
    }
    
    return $merged_array;
  }
  
  precompile static method merge_float : float[] ($array1 : float[], $array2 : float[]) {
    unless ($array1) {
      die "The array1 \$array1 must be defined.";
    }
    
    unless ($array2) {
      die "The array2 \$array2 must be defined.";
    }
    
    my $array1_length = @$array1;
    my $array2_length = @$array2;
    my $merged_array_length = $array1_length + $array2_length;
    
    my $merged_array = new float[$merged_array_length];
    
    for (my $i = 0; $i < $array1_length; $i++) {
      $merged_array->[$i] = $array1->[$i];
    }
    
    for (my $i = 0; $i < $array2_length; $i++) {
      $merged_array->[$array1_length + $i] = $array2->[$i];
    }
    
    return $merged_array;
  }
  
  precompile static method merge_double : double[] ($array1 : double[], $array2 : double[]) {
    unless ($array1) {
      die "The array1 \$array1 must be defined.";
    }
    
    unless ($array2) {
      die "The array2 \$array2 must be defined.";
    }
    
    my $array1_length = @$array1;
    my $array2_length = @$array2;
    my $merged_array_length = $array1_length + $array2_length;
    
    my $merged_array = new double[$merged_array_length];
    
    for (my $i = 0; $i < $array1_length; $i++) {
      $merged_array->[$i] = $array1->[$i];
    }
    
    for (my $i = 0; $i < $array2_length; $i++) {
      $merged_array->[$array1_length + $i] = $array2->[$i];
    }
    
    return $merged_array;
  }
  
  precompile static method merge_string : string[] ($array1 : string[], $array2 : string[]) {
    unless ($array1) {
      die "The array1 \$array1 must be defined.";
    }
    
    unless ($array2) {
      die "The array2 \$array2 must be defined.";
    }
    
    my $array1_length = @$array1;
    my $array2_length = @$array2;
    my $merged_array_length = $array1_length + $array2_length;
    
    my $merged_array = new string[$merged_array_length];
    
    for (my $i = 0; $i < $array1_length; $i++) {
      $merged_array->[$i] = $array1->[$i];
    }
    
    for (my $i = 0; $i < $array2_length; $i++) {
      $merged_array->[$array1_length + $i] = $array2->[$i];
    }
    
    return $merged_array;
  }
  
  precompile static method merge_object : object[] ($array1 : object[], $array2 : object[]) {
    unless ($array1) {
      die "The array1 \$array1 must be defined.";
    }
    
    unless ($array2) {
      die "The array2 \$array2 must be defined.";
    }
    
    my $array1_length = @$array1;
    my $array2_length = @$array2;
    my $merged_array_length = $array1_length + $array2_length;
    
    my $merged_array = new object[$merged_array_length];
    
    for (my $i = 0; $i < $array1_length; $i++) {
      $merged_array->[$i] = $array1->[$i];
    }
    
    for (my $i = 0; $i < $array2_length; $i++) {
      $merged_array->[$array1_length + $i] = $array2->[$i];
    }
    
    return $merged_array;
  }
  
  static method new_proto : object[] ($proto_array : object[], $length : int) {
    return (object[])&new_proto_any($proto_array, $length);
  }
  
  native static method new_proto_any : object ($proto_array : object, $length : int);
  
  static method shuffle_object : void ($array : object[], $seed_ref : int*) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    my $array_length = @$array;
    
    for (my $i = $array_length - 1; $i > 0; $i--) {
      
      my $j = Fn->crand($seed_ref) % ($i + 1);
      
      my $temp = $array->[$i];
      
      $array->[$i] = $array->[$j];
      
      $array->[$j] = $temp;
    }
  }
  
  precompile static method repeat_string : string[] ($strings : string[], $count : int) {
    
    unless ($strings) {
      die "The strings \$strings must be defined.";
    }
    
    unless ($count >= 0) {
      die "The repeat count \$count must be greater than or equal to 0.";
    }
    
    my $string_list = StringList->new;
    
    for (my $i = 0; $i < $count; $i++) {
      for my $string (@$strings) {
        $string_list->push($string);
      }
    }
    
    my $repeat_array = $string_list->to_array;
    
    return $repeat_array;
  }
  
  static method copy_object_with_proto : object[] ($array : object[], $proto : object[], $cloner : Cloner = undef, $offset : int = 0, $length : int = -1) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $array_length = @$array;
    if ($length < 0) {
      $length = $array_length - $offset;
    }
    
    unless ($offset + $length <= $array_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the array \$array.";
    }
    
    unless ($proto) {
      $proto = $array;
    }
    
    my $new_array = &new_proto($proto, $length);
    
    if ($cloner) {
      for (my $i = 0; $i < $length; $i++) {
        $new_array->[$i] = $cloner->($array->[$offset + $i]);
      }
    }
    else {
      Array->memcpy_object_address($new_array, 0, $array, $offset, $length);
    }
    
    return $new_array;
  }
  
  static method copy_object_address_with_proto : object[] ($array : object[], $proto : object[], $offset : int = 0, $length : int = -1) {
    return &copy_object_with_proto($array, $proto, undef, $offset, $length);
  }
  
  precompile static method to_object_array_byte : Byte[] ($array : byte[]) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    my $array_length = @$array;
    
    my $object_array = new Byte[$array_length];
    
    for (my $i = 0; $i < $array_length; $i++) {
      $object_array->[$i] = (Byte)$array->[$i];
    }
    
    return $object_array;
  }
  
  precompile static method to_object_array_short : Short[] ($array : short[]) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    my $array_length = @$array;
    
    my $object_array = new Short[$array_length];
    
    for (my $i = 0; $i < $array_length; $i++) {
      $object_array->[$i] = (Short)$array->[$i];
    }
    
    return $object_array;
  }
  
  precompile static method to_object_array_int : Int[] ($array : int[]) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    my $array_length = @$array;
    
    my $object_array = new Int[$array_length];
    
    for (my $i = 0; $i < $array_length; $i++) {
      $object_array->[$i] = (Int)$array->[$i];
    }
    
    return $object_array;
  }
  
  precompile static method to_object_array_long : Long[] ($array : long[]) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    my $array_length = @$array;
    
    my $object_array = new Long[$array_length];
    
    for (my $i = 0; $i < $array_length; $i++) {
      $object_array->[$i] = (Long)$array->[$i];
    }
    
    return $object_array;
  }
  
  precompile static method to_object_array_float : Float[] ($array : float[]) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    my $array_length = @$array;
    
    my $object_array = new Float[$array_length];
    
    for (my $i = 0; $i < $array_length; $i++) {
      $object_array->[$i] = (Float)$array->[$i];
    }
    
    return $object_array;
  }
  
  precompile static method to_object_array_double : Double[] ($array : double[]) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    my $array_length = @$array;
    
    my $object_array = new Double[$array_length];
    
    for (my $i = 0; $i < $array_length; $i++) {
      $object_array->[$i] = (Double)$array->[$i];
    }
    
    return $object_array;
  }
  
  precompile static method to_array_byte : byte[] ($object_array : Byte[]) {
    
    unless ($object_array) {
      die "The array \$object_array must be defined.";
    }
    
    my $object_array_length = @$object_array;
    
    my $array = new byte[$object_array_length];
    
    for (my $i = 0; $i < $object_array_length; $i++) {
      $array->[$i] = (byte)$object_array->[$i];
    }
    
    return $array;
  }
  
  precompile static method to_array_short : short[] ($object_array : Short[]) {
    
    unless ($object_array) {
      die "The array \$object_array must be defined.";
    }
    
    my $object_array_length = @$object_array;
    
    my $array = new short[$object_array_length];
    
    for (my $i = 0; $i < $object_array_length; $i++) {
      $array->[$i] = (short)$object_array->[$i];
    }
    
    return $array;
  }
  
  precompile static method to_array_int : int[] ($object_array : Int[]) {
    
    unless ($object_array) {
      die "The array \$object_array must be defined.";
    }
    
    my $object_array_length = @$object_array;
    
    my $array = new int[$object_array_length];
    
    for (my $i = 0; $i < $object_array_length; $i++) {
      $array->[$i] = (int)$object_array->[$i];
    }
    
    return $array;
  }
  
  precompile static method to_array_long : long[] ($object_array : Long[]) {
    
    unless ($object_array) {
      die "The array \$object_array must be defined.";
    }
    
    my $object_array_length = @$object_array;
    
    my $array = new long[$object_array_length];
    
    for (my $i = 0; $i < $object_array_length; $i++) {
      $array->[$i] = (long)$object_array->[$i];
    }
    
    return $array;
  }
  
  precompile static method to_array_float : float[] ($object_array : Float[]) {
    
    unless ($object_array) {
      die "The array \$object_array must be defined.";
    }
    
    my $object_array_length = @$object_array;
    
    my $array = new float[$object_array_length];
    
    for (my $i = 0; $i < $object_array_length; $i++) {
      $array->[$i] = (float)$object_array->[$i];
    }
    
    return $array;
  }
  
  precompile static method to_array_double : double[] ($object_array : Double[]) {
    
    unless ($object_array) {
      die "The array \$object_array must be defined.";
    }
    
    my $object_array_length = @$object_array;
    
    my $array = new double[$object_array_length];
    
    for (my $i = 0; $i < $object_array_length; $i++) {
      $array->[$i] = (double)$object_array->[$i];
    }
    
    return $array;
  }
  
  native static method new_array_proto_element : object[] ($proto_element : object, $length : int);
  
  static method equals : int ($array1 : object, $array2 : object, $shallow : int = 0) {
    
    if ($array1 == undef && $array2 == undef) {
      return 1;
    }
    elsif ($array1 != undef && $array2 == undef) {
      return 0;
    }
    elsif ($array1 == undef && $array2 != undef) {
      return 0;
    }
    
    unless (Fn->is_array($array1)) {
      die "The type of the array \$array1 must be an array type.";
    }
    
    unless (Fn->is_array($array2)) {
      die "The type of the array \$array2 must be an array type.";
    }
    
    unless (type_name $array1 eq type_name $array2) {
      return 0;
    }
    
    my $elem_type_name = Fn->get_elem_type_name($array1);
    
    my $equals = 0;
    if (Fn->is_string_array($array1)) {
      if ($shallow) {
        $equals = &equals_string_address((string[])$array1, (string[])$array2);
      }
      else {
        $equals = &equals_string((string[])$array1, (string[])$array2);
      }
    }
    elsif (Fn->is_object_array($array1)) {
      if ($shallow) {
        $equals = &equals_object_address((object[])$array1, (object[])$array2);
      }
      else {
        $equals = &equals_object((object[])$array1, (object[])$array2, EqualityChecker->default_equality_checker);
      }
    }
    elsif (Fn->is_any_numeric_array($array1)) {
      
      my $length1 = Fn->array_length($array1);
      
      my $length2 = Fn->array_length($array2);
      
      if ($length1 == $length2) {
        my $elem_size = Fn->get_elem_size($array1);
        
        $equals = Fn->memcmp($array1, 0, $array2, 0, $elem_size * $length1) == 0;
      }
      else {
        $equals = 0;
      }
    }
    else {
      die "The type of the \$array must be a numeric array type, a multi-numeric array type, string array type, or an object array type";
    }
    
    return $equals;
  }
  
  static method copy_any_numeric : object ($array : object, $offset : int = 0, $length : int = -1) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless (Fn->is_any_numeric_array($array)) {
      die "The type of the array \$array must be a numeric array type or a multi-numeric type.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $array_length = Fn->array_length($array);
    if ($length < 0) {
      $length = $array_length - $offset;
    }
    
    unless ($offset + $length <= $array_length) {
      die "The offset \$offset + the length \$length must be less than or equal to the length of the array \$array.";
    }
    
    my $elem_size = Fn->get_elem_size($array);
    
    my $new_array = Array->new_proto_any($array, $length);
    
    Fn->memcpy($new_array, 0, $array, $elem_size * $offset, $elem_size * $length);
    
    return $new_array;
  }
  
  static method shuffle_any_numeric : void ($array : object, $seed_ref : int*) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless (Fn->is_any_numeric_array($array)) {
      die "The type of the array \$array must be a numeric type or a multi-numeric type.";
    }
    
    my $array_length = Fn->array_length($array);
    
    my $elem_size = Fn->get_elem_size($array);
    
    my $temp = new byte[$elem_size];
    
    for (my $i = $array_length - 1; $i > 0; $i--) {
      
      my $j = Fn->crand($seed_ref) % ($i + 1);
      
      Fn->memcpy($temp, 0, $array, $elem_size * $i, $elem_size);
      
      Fn->memcpy($array, $elem_size * $i, $array, $elem_size * $j, $elem_size);
      
      Fn->memcpy($array, $elem_size * $j, $temp, 0, $elem_size);
    }
  }
  
  static method shuffle : void ($array : object, $seed_ref : int*) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    if (Fn->is_object_array($array)) {
      &shuffle_object((object[])$array, $seed_ref);
    }
    elsif (Fn->is_any_numeric_array($array)) {
      &shuffle_any_numeric($array, $seed_ref);
    }
    else {
      die "The type of the array \$array must be an object array type, a numeric array type or a multi-numeric array type.";
    }
  }
  
  static method repeat : object ($array : object, $count : int) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless (Fn->is_array($array)) {
      die "The type of the array \$array must be an array type.";
    }
    
    unless ($count >= 0) {
      die "The repeat count \$count must be a non-negative integer.";
    }
    
    my $length = Fn->array_length($array);
    
    my $elem_size = Fn->get_elem_size($array);
    
    my $ret_length = $count * $length;
    
    my $ret_array = Array->new_proto_any($array, $ret_length);
    
    if (Fn->is_object_array($array)) {
      for (my $i = 0; $i < $length; $i++) {
        Array->memcpy_object_address((object[])$ret_array, $count * $i, (object[])$array, 0, $count);
      }
    }
    else {
      for (my $i = 0; $i < $length; $i++) {
        Fn->memcpy($ret_array, $elem_size * $count * $i, $array, 0, $elem_size * $count);
      }
    }
    
    return $ret_array;
  }
  
}