package SPVM::Unicode {
  sub encode_utf16 : short[] ($code_point_string : int[]) {
    my $length = 0;
    
    # Culcurate length
    for (my $i = 0; $i < @$code_point_string; $i++) {
      my $code_point = $code_point_string->[$i];

      if ($code_point < 0 || $code_point > 0x10FFFF) {
        croak "Invalid code point in code point string";
      }

      if ($code_point < 0x10000) {
        $length++;
      }
      else {
        $length += 2;
      }
    }
    
    # Convert conde point to UTF-16
    my $utf16_string = new short[$length];
    my $pos = 0;
    
    for (my $i = 0; $i < @$code_point_string; $i++) {
      my $code_point = $code_point_string->[$i];

      if ($code_point < 0x10000) {
        $utf16_string->[$pos] = (short)$code_point;
        $pos++;
      }
      else {
        $utf16_string->[$pos] = (short)(($code_point - 0x10000) / 0x400 + 0xD800);
        $utf16_string->[$pos + 1] = (short)(($code_point - 0x10000) % 0x400 + 0xDC00);
        $pos += 2;
      }
    }
    
    return $utf16_string;
  }

  sub is_utf16_high_surrogate : int($ch : int) {
    if ($ch >= 0xD800 && $ch < 0xDC00) {
      return 1;
    }
    else {
      return 0;
    }
  }

  sub is_utf16_low_surrogate : int($ch : int) {
    if ($ch >= 0xDC00 && $ch < 0xE000) {
      return 1;
    }
    else {
      return 0;
    }
  }

  sub decode_utf16 : int[] ($utf16_string : short[]) {

    my $length = 0;
    
    # Culcurate length
    for (my $i = 0; $i < @$utf16_string; $i++) {
      if (is_utf16_high_surrogate($utf16_string->[$i])) {
        if ($i + 1 == @$utf16_string - 1) {
          croak "Invalid UTF-16 string";
        }
        else {
          $i++;
          unless (is_utf16_low_surrogate($utf16_string->[$i])) {
            croak "Invalid UTF-16 string";
          }
        }
      }
      elsif (is_utf16_low_surrogate($utf16_string->[$i])) {
        croak "Invalid UTF-16 string";
      }
      $length++;
    }
    
    # Convert UTF-16 to code point
    my $code_point_string = new int[$length];
    my $pos = 0;
    for (my $i = 0; $i < @$utf16_string; $i++) {
      if (is_utf16_high_surrogate($utf16_string->[$i])) {
        $code_point_string->[$pos] = 0x10000 + ($utf16_string->[$i] - 0xD800) * 0x400 + ($utf16_string->[$i + 1] - 0xDC00);
        $i++;
      }
      else {
        $code_point_string->[$pos] = $utf16_string->[$i];
      }
      $pos++;
    }
    
    return $code_point_string;
  }
}
