# Copyright (c) 2025 Yuki Kimoto
# MIT License

class Re {
  version_from Regex;
  
  use Regex;
  use Regex::Match;
  use Sync::Mutex;
  
  # Fields
  our $MUTEX : Sync::Mutex;
  
  our $REGEXES_H : cache Hash of Regex;
  
  INIT {
    $MUTEX = Sync::Mutex->new;
  }
  
  private static method new_regex_use_cache : Regex ($pattern : string, $flags : string, $is_global_ref : int*) {
    
    unless ($pattern) {
      die "The regex pattern $pattern must be defined.";
    }
    
    unless ($flags) {
      $flags = "";
    }
    
    my $is_global = 0;
    my $flags_without_g = (string)undef;
    if (Fn->contains($flags, "g")) {
      $is_global = 1;
      
      my $flags_length = length $flags;
      my $flags_without_g_tmp = (mutable string)new_string_len $flags_length;
      my $char_offset = 0;
      for (my $i = 0; $i < $flags_length; $i++) {
        my $char = $flags->[$i];
        unless ($char == 'g') {
          $flags_without_g_tmp->[$char_offset] = $char;
          $char_offset++;
        }
      }
      
      Fn->shorten_null_char($flags_without_g_tmp);
      $flags_without_g = $flags_without_g_tmp;
    }
    else {
      $flags_without_g = $flags;
    }
    
    $$is_global_ref = $is_global;
    
    my $regex_key = "(?$flags_without_g)$pattern";
    
    my $regex = (Regex)undef;
    
    {
      Fn->defer(method : void () {
         $MUTEX->reader_unlock;
      });
      
      $MUTEX->reader_lock;
      
      if ($REGEXES_H) {
        $regex = (Regex)$REGEXES_H->get($regex_key);
      }
    }
    
    unless ($regex) {
      Fn->defer(method : void () {
         $MUTEX->unlock;
      });
      
      $MUTEX->lock;
      
      unless ($REGEXES_H) {
        $REGEXES_H = Hash->new;
      }
      
      $regex = Regex->new($pattern, $flags_without_g);
      $REGEXES_H->set($regex_key, $regex);
    }
    
    return $regex;
  }
  
  static method m : Regex::Match ($string : string, $pattern_and_flags : string|string[], $offset_ref : int* = undef, $length : int = -1) {
    
    my $pattern = (string)undef;
    my $flags = (string)undef;
    if ($pattern_and_flags isa string) {
      $pattern = (string)$pattern_and_flags;
    }
    elsif ($pattern_and_flags isa string[]) {
      $pattern = $pattern_and_flags->(string[])->[0];
      $flags = $pattern_and_flags->(string[])->[1];
    }
    else {
      die "The type of \$pattern_and_flags must be string or string[].";
    }
    
    my $is_global = 0;
    my $regex = &new_regex_use_cache($pattern, $flags, \$is_global);
    
    my $match = $regex->match($string, $offset_ref, $length);
    
    return $match;
  }
  
  static method s : Regex::ReplaceInfo ($string : mutable string, $pattern_and_flags : string|string[], $replace : string|Regex::Replacer, $offset_ref : int* = undef, $length : int = -1) {
    
    my $pattern = (string)undef;
    my $flags = (string)undef;
    if ($pattern_and_flags isa string) {
      $pattern = (string)$pattern_and_flags;
    }
    elsif ($pattern_and_flags isa string[]) {
      $pattern = $pattern_and_flags->(string[])->[0];
      $flags = $pattern_and_flags->(string[])->[1];
    }
    else {
      die "The type of \$pattern_and_flags must be string or string[].";
    }
    
    my $is_global = 0;
    my $regex = &new_regex_use_cache($pattern, $flags, \$is_global);
    
    my $options = Fn->merge_options(undef, {global => $is_global});
    
    my $replace_info = $regex->replace($string, $replace, $offset_ref, $length, $options);
    
    return $replace_info;
  }
  
  static method split : string[] ($pattern_and_flags : string|string[], $string : string, $limit : int = 0) {
    
    my $pattern = (string)undef;
    my $flags = (string)undef;
    if ($pattern_and_flags isa string) {
      $pattern = (string)$pattern_and_flags;
    }
    elsif ($pattern_and_flags isa string[]) {
      $pattern = $pattern_and_flags->(string[])->[0];
      $flags = $pattern_and_flags->(string[])->[1];
    }
    else {
      die "The type of \$pattern_and_flags must be string or string[].";
    }
    
    my $is_global = 0;
    my $regex = &new_regex_use_cache($pattern, $flags, \$is_global);
    
    my $parts = $regex->split($string, $limit);
    
    return $parts;
  }
  
}