package SPVM::Util {
  use SPVM::Stringer;
  use SPVM::Cloner;
  use SPVM::EqualityChecker;
  use SPVM::StringBuffer;
  use SPVM::StringList;
  use SPVM::Unicode (uchar_to_utf8);
  use SPVM::Math (fabs, floor, log10, pow, sqrt, sqrtf);
  use SPVM::Time (time);
  
  INIT {
    srand(time());
  }
  
  precompile sub copy_oarray : object[] ($objects : object[], $cloner : SPVM::Cloner) {
    my $length = @$objects;

    my $new_objects = new object[$length];

    for (my $i = 0; $i < $length; $i++) {
      $new_objects->[$i] = $cloner->($objects->[$i]);
    }

    return $new_objects;
  }

  precompile sub equals_oarray : int ($objs1 : oarray, $objs2 : oarray, $equality_checker : SPVM::EqualityChecker) {
    my $is_equals = 1;
    if (@$objs1 == @$objs2) {
      for (my $i = 0; $i < @$objs1; $i++) {
        if (!$equality_checker->($objs1->[$i], $objs2->[$i])) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }

  precompile sub joino : string ($sep : string, $objects : oarray, $stringer : SPVM::Stringer) {
    my $join = "";

    for (my $i = 0; $i < @$objects; $i++) {
      my $str = $stringer->($objects->[$i]);
      $join .= $str;
      if ($i != @$objects - 1) {
        $join .= $sep;
      }
    }

    return $join;
  }

  precompile sub split : string[] ($sep : string, $string : string) {
    my $string_length = length $string;
    my $sep_length = length $sep;

    if ($sep_length == 0) {
      die "Separator must not be empty string";
    }

    my $separated_strings_list = SPVM::StringList->new_len(0);

    my $match_start = 0;
    my $string_base = 0;
    for (my $string_index = 0; $string_index < $string_length; $string_index++) {
      if ($string->[$string_index] == $sep->[0]) {
        $match_start = 1;
      }

      if ($match_start) {
        my $match = 1;
        for (my $sep_index = 0; $sep_index < $sep_length; $sep_index++) {
          if ($string_index + $sep_index > $string_length - 1) {
            $match = 0;
            last;
          }
          else {
            unless ($string->[$string_index + $sep_index] == $sep->[$sep_index]) {
              $match = 0;
              last;
            }
          }
        }
        if ($match) {
          my $separated_string = substr($string, $string_base, $string_index - $string_base);
          $separated_strings_list->push($separated_string);
          $string_base = $string_index + $sep_length;
          $string_index += $sep_length - 1;
        }
      }
    }
    if ($string_base == $string_length) {
      $separated_strings_list->push("");
    }
    else {
      my $separated_string = substr($string, $string_base, $string_length - $string_base);
      $separated_strings_list->push($separated_string);
    }

    my $separated_strings = $separated_strings_list->to_array;

    return $separated_strings;
  }

  native sub _snsprintf_double : string ($format : string, $value : double);

  precompile sub sprintf : string ($format : string, $args : object[]...) {
    my $format_length = length $format;
    my $index = 0;

    my $buffer = SPVM::StringBuffer->new;
    my $arg_count = 0;
    my $constant_string_length = 0;

    while ($index + $constant_string_length < $format_length) {

      if ($format->[$index + $constant_string_length] != '%') {
        # Read constant string
        ++$constant_string_length;
      }
      elsif ($index + $constant_string_length + 1 < $format_length &&
          $format->[$index + $constant_string_length + 1] == '%') {
        # Read %%
        ++$constant_string_length;

        # Add constant string
        if ($constant_string_length > 0) {
          $buffer->push_range($format, $index, $constant_string_length);
          $index += $constant_string_length;
          $constant_string_length = 0;
        }

        # Skip second %
        ++$index;
      }
      elsif ($index + $constant_string_length + 1 >= $format_length) {
        die "Invalid conversion in sprintf: end of string";
      }
      else {
        # Add constant string
        if ($constant_string_length > 0) {
          $buffer->push_range($format, $index, $constant_string_length);
          $index += $constant_string_length;
          $constant_string_length = 0;
        }

        # Read format string

        # Check the next element of @$args corresponding to the specifier
        unless ($arg_count < @$args) {
          die "Missing argument in sprintf";
        }

        # Read specifier %[flags][width][.precision][length]type

        my $specifier_base_index = $index;
        ++$index; # '%'

        # Read `flags`
        my $pad_char = ' ';
        my $plus_sign = 0;
        my $left_justified = 0;

        while ($index < $format_length) {
          my $flag = (int)($format->[$index]);
          switch($flag) {
            case '0': {
              ++$index;
              $pad_char = '0';
              break;
            }
            case '+': {
              ++$index;
              $plus_sign = 1;
              break;
            }
            case '-': {
              ++$index;
              $left_justified = 1;
              break;
            }
            default: {
              last;
              break;
            }
          }
        }

        # Read `width`
        my $width = 0;
        while ($index < $format_length) {
          my $c = $format->[$index];
          if ($c < '0' || '9' < $c) {
            last;
          }
          $width = $width * 10 + $c - '0';
          ++$index;
        }

        # Skip `precision` because of using native sprintf.
        if ($index < $format_length && $format->[$index] == '.') {
          ++$index;
          while ($index < $format_length) {
            my $c = $format->[$index];
            if ($c < '0' || '9' < $c) {
              last;
            }
            ++$index;
          }
        }

        unless ($index < $format_length) {
          die "Invalid conversion in sprintf: \""
              . substr($format, $specifier_base_index, $index - $specifier_base_index) . "\"";
        }

        my $specifier_char = $format->[$index];
        if ($specifier_char == 'c') {
          ++$index;
          my $arg = ((SPVM::Byte)$args->[$arg_count])->val;

          if ($left_justified) {
            $buffer->push_char($arg);
            my $space_count = $width - 1;
            if ($space_count > 0) {
              for (; $space_count > 0; --$space_count) {
                $buffer->push_char($pad_char);
              }
            }
          }
          else {
            my $space_count = $width - 1;
            if ($space_count > 0) {
              for (; $space_count > 0; --$space_count) {
                $buffer->push_char($pad_char);
              }
            }
            $buffer->push_char($arg);
          }
        }
        elsif ($specifier_char == 's') {
          ++$index;
          my $arg = (string)$args->[$arg_count];

          my $space_count = $width - length $arg;;
          if ($left_justified) {
            $buffer->push($arg);
            for (my $i = 0; $i < $space_count; ++$i) {
              $buffer->push_char($pad_char);
            }
          }
          else {
            for (my $i = 0; $i < $space_count; ++$i) {
              $buffer->push_char($pad_char);
            }
            $buffer->push($arg);
          }
        }
        elsif ($specifier_char == 'd') {
          ++$index;
          my $arg = ((SPVM::Int)$args->[$arg_count])->val;
          my $digits = new byte[11]; # -2147483648 has 11 digits
          my $digit_count = 0;
          while ($arg > 0) {
            $digits->[$digit_count++] = (byte)('0' + $arg % 10);
            $arg /= 10;
          }

          if ($left_justified) {
            my $space_count = $width - $digit_count;

            if ($arg < 0) {
              $buffer->push_char('-');
              --$space_count;
            }
            elsif ($plus_sign && $arg >= 0) {
              $buffer->push_char('+');
              --$space_count;
            }

            for (my $i = 0; $i < $digit_count; ++$i) {
              $buffer->push_char($digits->[$digit_count - $i - 1]);
            }

            if ($space_count > 0) {
              for (; $space_count > 0; --$space_count) {
                $buffer->push_char($pad_char);
              }
            }
          }
          else {
            my $space_count = $width - $digit_count;

            if ($arg < 0) {
              --$space_count;
            }
            elsif ($plus_sign && $arg >= 0) {
              --$space_count;
            }

            if ($space_count > 0) {
              for (; $space_count > 0; --$space_count) {
                $buffer->push_char($pad_char);
              }
            }

            if ($arg < 0) {
              $buffer->push_char('-');
            }
            elsif ($plus_sign && $arg >= 0) {
              $buffer->push_char('+');
            }

            for (my $i = 0; $i < $digit_count; ++$i) {
              $buffer->push_char($digits->[$digit_count - $i - 1]);
            }
          }
        }
        elsif ($specifier_char == 'l') {
          unless ($index + 1 < $format_length && $format->[$index + 1] == 'd') {
            die "Invalid conversion in sprintf: \""
                . substr($format, $specifier_base_index, $index - $specifier_base_index + 1) . "\"";
          }
          $index += 2;
          my $arg = ((SPVM::Long)$args->[$arg_count])->val;
          my $digits = new byte[20]; # -9223372036854775808 has 20 digits
          my $digit_count = 0;
          while ($arg > 0) {
            $digits->[$digit_count++] = (byte)('0' + $arg % 10);
            $arg /= 10;
          }

          if ($left_justified) {
            my $space_count = $width - $digit_count;

            if ($arg < 0) {
              $buffer->push_char('-');
              --$space_count;
            }
            elsif ($plus_sign && $arg >= 0) {
              $buffer->push_char('+');
              --$space_count;
            }

            for (my $i = 0; $i < $digit_count; ++$i) {
              $buffer->push_char($digits->[$digit_count - $i - 1]);
            }

            if ($space_count > 0) {
              for (; $space_count > 0; --$space_count) {
                $buffer->push_char($pad_char);
              }
            }
          }
          else {
            my $space_count = $width - $digit_count;

            if ($arg < 0) {
              --$space_count;
            }
            elsif ($plus_sign && $arg >= 0) {
              --$space_count;
            }

            if ($space_count > 0) {
              for (; $space_count > 0; --$space_count) {
                $buffer->push_char($pad_char);
              }
            }

            if ($arg < 0) {
              $buffer->push_char('-');
            }
            elsif ($plus_sign && $arg >= 0) {
              $buffer->push_char('+');
            }

            for (my $i = 0; $i < $digit_count; ++$i) {
              $buffer->push_char($digits->[$digit_count - $i - 1]);
            }
          }
        }
        elsif ($specifier_char == 'f' || $specifier_char == 'g') {
          ++$index;
          my $arg = (SPVM::Double)$args->[$arg_count];
          my $specifier_str = substr($format, $specifier_base_index, $index - $specifier_base_index);
          my $str = _snsprintf_double($specifier_str, $arg->val);
          $buffer->push($str);
        }
        elsif ($specifier_char == 'U') {
          ++$index;
          my $arg = (SPVM::Int)$args->[$arg_count];
          my $utf8 = uchar_to_utf8($arg->val);
          $buffer->push($utf8);
        }
        else {
          die "Invalid conversion in sprintf: \""
              . substr($format, $specifier_base_index, $index - $specifier_base_index + 1) . "\"";
        }

        ++$arg_count;
      }
    }

    # Add constant string
    if ($constant_string_length > 0) {
      $buffer->push_range($format, $index, $constant_string_length);
      $index += $constant_string_length;
      $constant_string_length = 0;
    }

    my $result = $buffer->to_string;
    return $result;
  }

  precompile sub chomp_lf : string ($string : string) {
    unless ($string) {
      return undef;
    }

    my $new_string : string;
    my $length = length $string;
    if ($length == 0) {
      $new_string = copy_str($string);
    }
    else {
      if ($string->[$length - 1] == '\n') {
        $new_string = (string)sliceb((byte[])$string, 0, $length - 1);
      }
      else {
        $new_string = copy_str($string);
      }
    }

    return $new_string;
  }

  precompile sub chomp_crlf : string ($string : string) {
    unless ($string) {
      return undef;
    }

    my $new_string : string;
    my $length = length $string;
    if ($length == 0 || $length == 1) {
      $new_string = copy_str($string);
    }
    else {
      if ($string->[$length - 2] == '\r' && $string->[$length - 1] == '\n') {
        $new_string = (string)sliceb((byte[])$string, 0, $length - 2);
      }
      else {
        $new_string = copy_str($string);
      }
    }

    return $new_string;
  }

  precompile sub copy_barray : byte[] ($nums : byte[]) {
    my $length = @$nums;

    my $new_nums = new byte[$length];

    for (my $i = 0; $i < $length; $i++) {
      $new_nums->[$i] = $nums->[$i];
    }

    return $new_nums;
  }

  precompile sub copy_str : string ($string : string) {
    my $bytes = (byte[])$string;

    return (string)$bytes;
  }

  precompile sub copy_sarray : short[] ($nums : short[]) {
    my $length = @$nums;

    my $new_nums = new short[$length];

    for (my $i = 0; $i < $length; $i++) {
      $new_nums->[$i] = $nums->[$i];
    }

    return $new_nums;
  }

  precompile sub copy_iarray : int[] ($nums : int[]) {
    my $length = @$nums;

    my $new_nums = new int[$length];

    for (my $i = 0; $i < $length; $i++) {
      $new_nums->[$i] = $nums->[$i];
    }

    return $new_nums;
  }

  precompile sub copy_larray : long[] ($nums : long[]) {
    my $length = @$nums;

    my $new_nums = new long[$length];

    for (my $i = 0; $i < $length; $i++) {
      $new_nums->[$i] = $nums->[$i];
    }

    return $new_nums;
  }

  precompile sub copy_farray : float[] ($nums : float[]) {
    my $length = @$nums;

    my $new_nums = new float[$length];

    for (my $i = 0; $i < $length; $i++) {
      $new_nums->[$i] = $nums->[$i];
    }

    return $new_nums;
  }

  precompile sub copy_darray : double[] ($nums : double[]) {
    my $length = @$nums;

    my $new_nums = new double[$length];

    for (my $i = 0; $i < $length; $i++) {
      $new_nums->[$i] = $nums->[$i];
    }

    return $new_nums;
  }

  precompile sub copy_strarray : string[] ($strings : string[]) {
    my $length = @$strings;

    my $new_strings = new string[$length];

    for (my $i = 0; $i < $length; $i++) {
      $new_strings->[$i] = copy_str($strings->[$i]);
    }

    return $new_strings;
  }

  precompile sub equals_barray : int ($nums1 : byte[], $nums2 : byte[]) {
    my $is_equals = 1;
    if (@$nums1 == @$nums2) {
      for (my $i = 0; $i < @$nums1; $i++) {
        if ($nums1->[$i] != $nums2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }

  precompile sub equals_sarray : int ($nums1 : short[], $nums2 : short[]) {
    my $is_equals = 1;
    if (@$nums1 == @$nums2) {
      for (my $i = 0; $i < @$nums1; $i++) {
        if ($nums1->[$i] != $nums2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }
  precompile sub equals_iarray : int ($nums1 : int[], $nums2 : int[]) {
    my $is_equals = 1;
    if (@$nums1 == @$nums2) {
      for (my $i = 0; $i < @$nums1; $i++) {
        if ($nums1->[$i] != $nums2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }
  precompile sub equals_larray : int ($nums1 : long[], $nums2 : long[]) {
    my $is_equals = 1;
    if (@$nums1 == @$nums2) {
      for (my $i = 0; $i < @$nums1; $i++) {
        if ($nums1->[$i] != $nums2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }
  precompile sub equals_farray : int ($nums1 : float[], $nums2 : float[]) {
    my $is_equals = 1;
    if (@$nums1 == @$nums2) {
      for (my $i = 0; $i < @$nums1; $i++) {
        if ($nums1->[$i] != $nums2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }
  precompile sub equals_darray : int ($nums1 : double[], $nums2 : double[]) {
    my $is_equals = 1;
    if (@$nums1 == @$nums2) {
      for (my $i = 0; $i < @$nums1; $i++) {
        if ($nums1->[$i] != $nums2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }

  precompile sub equals_strarray : int ($strings1 : string[], $strings2 : string[]) {
    my $is_equals = 1;
    if (@$strings1 == @$strings2) {
      for (my $i = 0; $i < @$strings1; $i++) {
        if ($strings1->[$i] ne $strings2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }

  sub E : double () { return 2.7182818284590452354; }

  native sub get_type_name : string ($obj : object);

  precompile sub index : int ($str : string, $substr : string, $posision : int) {
    my $strCount = length $str;
    my $substrCount = length $substr;

    if ($posision >= $strCount) {
      my $ret : int;
      if ($substrCount == 0) {
        $ret = $strCount;
      }
      else {
        $ret = -1;
      }
      return $ret;
    }
    if ($posision < 0) {
      $posision = 0;
    }
    if ($substrCount == 0) {
      return $posision;
    }

    my $first : byte  = $substr->[0];
    my $max : int = ($strCount - $substrCount);

    for (my $i = $posision; $i <= $max; $i++) {
      if ($str->[$i] != $first) {
        while (++$i <= $max && $str->[$i] != $first) {}
      }

      if ($i <= $max) {
        my $j = $i + 1;
        my $end = $j + $substrCount - 1;
        for (my $k = 01; $j < $end && $str->[$j] == $substr->[$k]; ($j++, $k++)) {}

        if ($j == $end) {
          return $i;
        }
      }
    }
    return -1;
  }

  sub INT8_MIN : byte () { return -128; }
  sub INT8_MAX : byte () { return 127; }

  sub INT16_MIN : short () { return -32768; }
  sub INT16_MAX : short () { return 32767; }

  sub INT32_MIN : int () { return -2147483648; }
  sub INT32_MAX : int () { return 2147483647; }

  sub UINT32_MAX : long () { return 4294967295L; }

  sub INT64_MIN : long () { return -9223372036854775808L; }
  sub INT64_MAX : long () { return 9223372036854775807L; }
  
  native sub RAND_MAX : int ();
    
  native sub DBL_MAX : double ();
  native sub DBL_MIN : double ();

  native sub FLT_MAX : float ();
  native sub FLT_MIN : float();

  precompile sub isalnum : int ($char : int) {

    if (($char >= 'A' && $char <= 'Z') || ($char >= 'a' && $char <= 'z') || ($char >= '0' && $char <= '9')) {
      return 1;
    }
    else {
      return 0;
    }
  }

  precompile sub isalpha : int ($char : int) {

    if (($char >= 'A' && $char <= 'Z') || ($char >= 'a' && $char <= 'z')) {
      return 1;
    }
    else {
      return 0;
    }
  }

  precompile sub isblank : int ($char : int) {

    if ($char >= ' ' ||  $char <= '\t') {
      return 1;
    }
    else {
      return 0;
    }
  }

  precompile sub iscntrl : int ($char : int) {

    if (($char >= 0x00 && $char <= 0x1f) || $char == 0x7f) {
      return 1;
    }
    else {
      return 0;
    }
  }

  precompile sub isdigit : int ($char : int) {

    if ($char >= '0' && $char <= '9') {
      return 1;
    }
    else {
      return 0;
    }
  }

  precompile sub isgraph : int ($char : int) {

    if ($char >= 0x21 && $char <= 0x7e) {
      return 1;
    }
    else {
      return 0;
    }
  }

  precompile sub islower : int ($char : int) {

    if ($char >= 'a' && $char <= 'z') {
      return 1;
    }
    else {
      return 0;
    }
  }

  precompile sub isprint : int ($char : int) {

    if ($char >= 0x20 && $char <= 0x7e) {
      return 1;
    }
    else {
      return 0;
    }
  }

  precompile sub ispunct : int ($char : int) {

    if (($char >= 0x21 && $char <= 0x2f) || ($char >= 0x3a && $char <= 0x40) || ($char >= 0x5b && $char <= 0x60) || ($char >= 0x7b && $char <= 0x7e)) {
      return 1;
    }
    else {
      return 0;
    }
  }

  precompile sub isspace : int ($char : int) {

    if (($char >= 0x09 && $char <= 0x0d) || $char == 0x20) {
      return 1;
    }
    else {
      return 0;
    }
  }

  precompile sub isupper : int ($char : int) {

    if ($char >= 'A' && $char <= 'Z') {
      return 1;
    }
    else {
      return 0;
    }
  }

  precompile sub isxdigit : int ($char : int) {

    if (($char >= 'A' && $char <= 'F') || ($char >= 'a' && $char <= 'f') || ($char >= '0' && $char <= '9')) {
      return 1;
    }
    else {
      return 0;
    }
  }

  precompile sub tolower : int ($char : int) {

    if ($char >= 'A' && $char <= 'Z') {
      $char = $char + 0x20;
    }
    return $char;
  }

  precompile sub toupper : int ($char : int) {

    if ($char >= 'a' && $char <= 'z') {
      $char = $char - 0x20;
    }
    return $char;
  }

  # This is same as Perl \s
  precompile sub is_perl_space : int ($char : int) {
    my $ispspace = 0;
    switch ($char) {
      case ' ':
      case '\r':
      case '\n':
      case '\t':
      case '\f':
      {
        $ispspace = 1;
        break;
      }
    }
    return $ispspace;
  }

  precompile sub is_perl_word : int ($char : int) {
    my $ispword = 0;

    if ($char >= 'a' && $char <= 'z') {
      $ispword = 1;
    }
    elsif ($char >= 'A' && $char <= 'Z') {
      $ispword = 1;
    }
    elsif ($char == '_') {
      $ispword = 1;
    }
    elsif ($char >= '0' && $char <= '9') {
      $ispword = 1;
    }
    return $ispword;
  }

  precompile sub joinb : string ($sep : string, $nums : byte[]) {
    my $join = "";

    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $join .= $string;
      if ($i != @$nums - 1) {
        $join .= $sep;
      }
    }

    return $join;
  }

  precompile sub joins : string ($sep : string, $nums : short[]) {
    my $join = "";

    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $join .= $string;
      if ($i != @$nums - 1) {
        $join .= $sep;
      }
    }

    return $join;
  }

  precompile sub joini : string ($sep : string, $nums : int[]) {
    my $join = "";

    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $join .= $string;
      if ($i != @$nums - 1) {
        $join .= $sep;
      }
    }

    return $join;
  }

  precompile sub joinl : string ($sep : string, $nums : long[]) {
    my $join = "";

    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $join .= $string;
      if ($i != @$nums - 1) {
        $join .= $sep;
      }
    }

    return $join;
  }

  precompile sub joinf : string ($sep : string, $nums : float[]) {
    my $join = "";

    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $join .= $string;
      if ($i != @$nums - 1) {
        $join .= $sep;
      }
    }

    return $join;
  }

  precompile sub joind : string ($sep : string, $nums : double[]) {
    my $join = "";

    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $join .= $string;
      if ($i != @$nums - 1) {
        $join .= $sep;
      }
    }

    return $join;
  }

  precompile sub join : string ($sep : string, $strings : string[]) {
    my $join = "";

    for (my $i = 0; $i < @$strings; $i++) {
      my $string = $strings->[$i];
      $join .= $string;
      if ($i != @$strings - 1) {
        $join .= $sep;
      }
    }

    return $join;
  }

  precompile sub lc : string($str : string) {

    unless ($str) { die "String must be defined"; }

    my $length = length $str;
    my $new_string = new byte [$length];
    for (my $i = 0; $i < $length; $i++) {
      my $char = $str->[$i];
      if ($char >= 'A' && $char <= 'Z') {
        $new_string->[$i] = (byte)($str->[$i] + 32);
      }
      else {
        $new_string->[$i] = $str->[$i];
      }
    }
    return (string)$new_string;
  }

  precompile sub lcfirst : string($str : string) {

    unless ($str) { die "String must be defined"; }

    my $length = length $str;
    my $new_string = new byte [$length];
    if ($length > 0) {
      my $char = $str->[0];
      if ($char >= 'A' && $char <= 'Z') {
        $new_string->[0] = (byte)($char + 32);
      }
      else {
        $new_string->[0] = $char;
      }
    }
    for (my $i = 1; $i < $length; ++$i) {
      $new_string->[$i] = $str->[$i];
    }
    return (string)$new_string;
  }

  native sub memcpyb : void ($dest_data : byte[], $dest_offset : int, $src_data : byte[], $src_offset : int, $length : int);
  native sub memcpys : void ($dest_data : short[], $dest_offset : int, $src_data : short[], $src_offset : int, $length : int);
  native sub memcpyi : void ($dest_data : int[], $dest_offset : int, $src_data : int[], $src_offset : int, $length : int);
  native sub memcpyl : void ($dest_data : long[], $dest_offset : int, $src_data : long[], $src_offset : int, $length : int);
  native sub memcpyf : void ($dest_data : float[], $dest_offset : int, $src_data : float[], $src_offset : int, $length : int);
  native sub memcpyd : void ($dest_data : double[], $dest_offset : int, $src_data : double[], $src_offset : int, $length : int);

  native sub memmoveb : void ($dest_data : byte[], $dest_offset : int, $src_data : byte[], $src_offset : int, $length : int);
  native sub memmoves : void ($dest_data : short[], $dest_offset : int, $src_data : short[], $src_offset : int, $length : int);
  native sub memmovei : void ($dest_data : int[], $dest_offset : int, $src_data : int[], $src_offset : int, $length : int);
  native sub memmovel : void ($dest_data : long[], $dest_offset : int, $src_data : long[], $src_offset : int, $length : int);
  native sub memmovef : void ($dest_data : float[], $dest_offset : int, $src_data : float[], $src_offset : int, $length : int);
  native sub memmoved : void ($dest_data : double[], $dest_offset : int, $src_data : double[], $src_offset : int, $length : int);

  native sub new_object_array_proto : oarray ($proto_array : oarray, $length : int);

  sub PI : double () { return 3.14159265358979323846; }

  native sub srand : void ($seed : long);
  native sub crand : int ();
  
  sub rand : double () {
    
    # 0 <= random_number < 1
    my $random_number = (double)crand() / ((double)RAND_MAX() + 1);
    
    return $random_number;
  }

  precompile sub replace : string ($str : string, $substr : string, $replace : string, $start_offset : int, $found_offset_ref : int&) {
    unless ($substr && length $substr > 0) {
      die "Sub string must have length";
    }
    my $found_offset = index($str, $substr, $start_offset);
    $$found_offset_ref = $found_offset;
    if ($found_offset >= 0) {
      my $str_len = length $str;
      my $substr_len = length $substr;
      my $replace_len = length $replace;

      my $result_str_len = $str_len + $replace_len - $substr_len;
      my $result_str = new byte[$result_str_len];

      if ($found_offset > 0) {
        memmoveb((byte[])$result_str, 0, (byte[])$str, 0, $found_offset);
      }
      memmoveb((byte[])$result_str, $found_offset, (byte[])$replace, 0, $replace_len);

      my $str_rest_offset = $found_offset + $substr_len;
      my $str_rest_length = $str_len - $str_rest_offset;
      my $result_rest_offset = $found_offset + $replace_len;
      my $result_rest_length = $result_str_len - $result_rest_offset;
      memmoveb((byte[])$result_str, $result_rest_offset, (byte[])$str, $str_rest_offset, $str_rest_length);

      return (string)$result_str;
    }
    else {
      my $copy_str = copy_str($str);
      return $copy_str;
    }
  }

  precompile sub replace_all : string ($str : string, $substr : string, $replace : string) {

    unless ($str) { die "String must be defined"; }

    my $offset = 0;
    my $found_offset : int;
    my $substr_len = length $substr;
    my $result_str = $str;
    while (1) {
      $result_str = replace($result_str, $substr, $replace, $offset, \$found_offset);
      if ($found_offset >= 0) {
        $offset = $found_offset + $substr_len;
      }
      else {
        return $result_str;
      }
    }
  }

  native sub strtoi : int ($string : string, $digit : int);
  native sub strtol : long ($string : string, $digit : int);
  native sub strtof : float ($string : string);
  native sub strtod : double ($string : string);

  precompile sub uc : string($str : string) {

    unless ($str) { die "String must be defined"; }

    my $length = length $str;
    my $new_string = new byte [$length];
    for (my $i = 0; $i < $length; $i++) {
      my $char = $str->[$i];
      if ($char >= 'a' && $char <= 'z') {
        $new_string->[$i] = (byte)($str->[$i] - 32);
      }
      else {
        $new_string->[$i] = $str->[$i];
      }
    }
    return (string)$new_string;
  }

  precompile sub ucfirst : string($str : string) {

    unless ($str) { die "String must be defined"; }

    my $length = length $str;
    my $new_string = new byte [$length];
    if ($length > 0) {
      my $char = $str->[0];
      if ($char >= 'a' && $char <= 'z') {
        $new_string->[0] = (byte)($char - 32);
      }
      else {
        $new_string->[0] = $char;
      }
    }
    for (my $i = 1; $i < $length; ++$i) {
      $new_string->[$i] = $str->[$i];
    }
    return (string)$new_string;
  }

  precompile sub rindex : int ($str : string, $substr : string, $offset : int) {
    my $str_len = length $str;
    my $substr_len = length $substr;
    if ($offset > $str_len - $substr_len) {
      $offset = $str_len - $substr_len;
    }
    for (my $i = $offset; $i >= 0; --$i) {
      my $match = 1;
      for (my $j = 0; $j < $substr_len; ++$j) {
        if ($str->[$i + $j] != $substr->[$j]) {
          $match = 0;
          last;
        }
      }
      if ($match) {
        return $i;
      }
    }
    return -1;
  }

  precompile sub contains : int ($str : string, $substr : string) {
    if (!$str) {
      die "Target string must be defined";
    }
    if (!$substr) {
      die "Search string must be defined";
    }

    my $pat_len = length $substr;
    my $str_len = length $str;
    my $i = 0;
    my $j = 0;

    while ($i < $str_len && $j < $pat_len) {
      if ($str->[$i] == $substr->[$j]) {
        $i++;
        $j++;
      } else {
        $i = $i - $j + 1;
        $j = 0;
      }
    }

    if ($j == $pat_len) {
      return 1;
    }
    else {
      return 0;
    }
  }

  native sub abs : int ($x : int);
  native sub labs : long ($x : long);

  native sub reverseb : void ($nums : byte[]);
  native sub reverses : void ($nums : short[]);
  native sub reversei : void ($nums : int[]);
  native sub reversel : void ($nums : long[]);
  native sub reversef : void ($nums : float[]);
  native sub reversed : void ($nums : double[]);
  native sub reverseo : void ($objs : oarray);

  precompile sub substr : string ($string : string, $offset : int, $length : int) {

    if ($string == undef) {
      die "String must be defined";
    }

    my $string_length = length $string;

    if ($offset < 0 || $offset > $string_length - 1) {
      die "Invalid offset $offset";
    }

    if ($length < 0) {
      die "Length must be non-zero value";
    }

    if ($offset + $length > $string_length) {
      die "Offset + length must not be over array length";
    }

    my $slice = new byte[$length];

    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $slice->[$pos] = $string->[$i];
      $pos++;
    }

    return (string)$slice;
  }

  precompile sub sliceb : byte[] ($nums : byte[], $offset : int, $length : int) {

    if ($nums == undef) {
      die "Array must be defined";
    }

    my $array_length = @$nums;

    if ($offset < 0 || $offset > $array_length - 1) {
      die "Invalid offset $offset";
    }

    if ($length < 0) {
      die "Length must be non-zero value";
    }

    if ($offset + $length > $array_length) {
      die "Offset + length must not be over array length";
    }

    my $slice = new byte[$length];

    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $slice->[$pos] = $nums->[$i];
      $pos++;
    }

    return $slice;
  }

  precompile sub slices : short[] ($nums : short[], $offset : int, $length : int) {

    if ($nums == undef) {
      die "Array must be defined";
    }

    my $array_length = @$nums;

    if ($offset < 0 || $offset > $array_length - 1) {
      die "Invalid offset $offset";
    }

    if ($length < 0) {
      die "Length must be non-zero value";
    }

    if ($offset + $length > $array_length) {
      die "Offset + length must not be over array length";
    }

    my $slice = new short[$length];

    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $slice->[$pos] = $nums->[$i];
      $pos++;
    }

    return $slice;
  }

  precompile sub slicei : int[] ($nums : int[], $offset : int, $length : int) {

    if ($nums == undef) {
      die "Array must be defined";
    }

    my $array_length = @$nums;

    if ($offset < 0 || $offset > $array_length - 1) {
      die "Invalid offset $offset";
    }

    if ($length < 0) {
      die "Length must be non-zero value";
    }

    if ($offset + $length > $array_length) {
      die "Offset + length must not be over array length";
    }

    my $slice = new int[$length];

    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $slice->[$pos] = $nums->[$i];
      $pos++;
    }

    return $slice;
  }

  precompile sub slicel : long[] ($nums : long[], $offset : int, $length : int) {

    if ($nums == undef) {
      die "Array must be defined";
    }

    my $array_length = @$nums;

    if ($offset < 0 || $offset > $array_length - 1) {
      die "Invalid offset $offset";
    }

    if ($length < 0) {
      die "Length must be non-zero value";
    }

    if ($offset + $length > $array_length) {
      die "Offset + length must not be over array length";
    }

    my $slice = new long[$length];

    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $slice->[$pos] = $nums->[$i];
      $pos++;
    }

    return $slice;
  }

  precompile sub slicef : float[] ($nums : float[], $offset : int, $length : int) {

    if ($nums == undef) {
      die "Array must be defined";
    }

    my $array_length = @$nums;

    if ($offset < 0 || $offset > $array_length - 1) {
      die "Invalid offset $offset";
    }

    if ($length < 0) {
      die "Length must be non-zero value";
    }

    if ($offset + $length > $array_length) {
      die "Offset + length must not be over array length";
    }

    my $slice = new float[$length];

    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $slice->[$pos] = $nums->[$i];
      $pos++;
    }

    return $slice;
  }

  precompile sub sliced : double[] ($nums : double[], $offset : int, $length : int) {

    if ($nums == undef) {
      die "Array must be defined";
    }

    my $array_length = @$nums;

    if ($offset < 0 || $offset > $array_length - 1) {
      die "Invalid offset $offset";
    }

    if ($length < 0) {
      die "Length must be non-zero value";
    }

    if ($offset + $length > $array_length) {
      die "Offset + length must not be over array length";
    }

    my $slice = new double[$length];

    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $slice->[$pos] = $nums->[$i];
      $pos++;
    }

    return $slice;
  }

  precompile sub sliceo : oarray ($elems : oarray, $offset : int, $length : int) {

    if ($elems == undef) {
      die "Array must be defined";
    }

    my $array_length = @$elems;

    if ($offset < 0 || $offset > $array_length - 1) {
      die "Invalid offset $offset";
    }

    if ($length < 0) {
      die "Length must be non-zero value";
    }

    if ($offset + $length > $array_length) {
      die "Offset + length must not be over array length";
    }

    my $slice = new_object_array_proto($elems, $length);

    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $slice->[$pos] = $elems->[$i];
      $pos++;
    }

    return $slice;
  }

}
