
# This file is an unfinished sketch of :perl5 regex support.

sub JS::Root::rxbare_(Str $re) is primitive {
    my %h;
    rx_helper_(%h, $re, "/", "/");
}
sub JS::Root::rx_(%mods, Str $re, Str $qo, Str $qc) is primitive {
    rx_helper_(%mods, $re, $qo, $qc);
}
sub JS::Root::m_(%mods, Str $re, Str $qo, Str $qc) is primitive {
    rx_helper_(%mods, $re, $qo, $qc);
}

our(%modifiers_known, %modifiers_supported_p6, %modifiers_supported_p5);
our(%rx_helper_cache);
sub JS::Root::rx_helper_(%mods0, Str $pat0, Str $qo, Str $qc) is primitive {

  if !%modifiers_known.keys {
    %modifiers_known = map {;($_ => 1)}
        <perl5 Perl5 P5 i ignorecase w words g global c continue p pos
        once bytes codes graphs langs x nth ov overlap ex exhaustive
        rw keepall e each any parsetree>;
    %modifiers_supported_p6 = map {;($_ => 1)}
        <i ignorecase w words g global>;
    %modifiers_supported_p5 = map {;($_ => 1)}
        <perl5 Perl5 P5 i ignorecase g global ov overlap>;
    %rx_helper_cache{'re_x'}   = rx_core_({perl5=>1},'^(\d+)x$','/','/');
    %rx_helper_cache{'re_nth'} = rx_core_({perl5=>1},'^(\d+)(?:th|st|nd|rd)$','/','/');
  }

  my $pat = $pat0;
  my %mods = %mods0;
  my $p5 = %mods{"perl5"} || %mods{"Perl5"} || %mods{"P5"};

  my sub warning($e){warn("Warning: $e")};
  for %mods.keys -> $k {
    if %modifiers_known{$k} {
      if $p5 && !%modifiers_supported_p5{$k} {
        warning "Modifier :$k is not (yet?) supported by :perl5 regexps.";
      } elsif !$p5 && !%modifiers_supported_p6{$k} {
        warning "Modifier :$k is not yet supported by PGE/pugs.";
      }
    }
    elsif (($k.chars > 1) && (substr($k,-1,1) eq "x") # XXX - excess parens required
           && ($k ~~ %rx_helper_cache{'re_x'})) {
      my $n = 0+ ~$0; # +$0 XXX
      %mods.delete($k);
      %mods{'x'} = $n;
    }
    elsif (($k.chars > 2) && (substr($k,-2,2) eq ("th"|"st"|"nd"|"rd"))
           && ($k ~~ %rx_helper_cache{'re_nth'})) {
      my $n = 0+ ~$0; # +$0 XXX
      %mods.delete($k);
      %mods{'nth'} = $n;
    }
    else {
      my $msg = "Unknown modifier :$k will probably be ignored.";
      $msg ~= "  Perhaps you meant :i:w ?" if $k eq ("iw"|"wi");
      warning $msg;
    }
  }

  if !$p5 { 
    die "Perl 6 Rules are not supported." if !$perl5;
  }

  rx_core_(%mods,$pat,$qo,$qc);
}

sub JS::Root::rx_core_(%mods, Str $pat, Str $qo, Str $qc) is primitive {

  my $global     = %mods{'g'}  || %mods{'global'};
  my $overlap    = %mods{'ov'} || %mods{'overlap'};
  my $exhaustive = %mods{'ex'} || %mods{'exhaustive'};
  my $ignore     = %mods{'i'}  || %mods{'ignore'};
  my $nth        = %mods.exists('nth');
  my $multiline;

  my $new_Match = JS::inline('
     PIL2JS.toPIL2JSBox(function (as_b, from, to, as_s, as_a, as_h) {
        return new PIL2JS.Match(as_b, from, to, as_s, as_a, as_h);
     })');

  my $matcher;
  if $global {
    my %mods_altered = %mods;
    %mods_altered.delete('g');
    %mods_altered.delete('global');
    my $rx = rx_core_(%mods_altered,$pat,$qo,$qc);
    $matcher = sub ($string) {
      my $s = ~$string;
      my $offset = 0;
      my @a;
      while 1 {
        my $m = $s ~~ $rx;
        last if !$m;
        push(@a,$m);

        my $off = $m.to +1;
#        $m.from += $offset; # XXX - should be writable, but isnt.
#        $m.to   += $offset; # XXX - should be writable, but isnt.

        last if $s eq '';

        $offset += $off;
        $s = substr($s,$off);
      }
      my $match;
      if !@a {
        $match = $new_Match(?0, undef, undef, "", [], ());
      } else {
        my $from = @a[0].from;
        my $to   = $offset -1; # @a[-1].to; # XXX - .to is incorrect - see above
        my $str  = substr($string,$from,$to-$from+1);
        $match = $new_Match(?1, $from, $to, $str, @a, ());
      }
      $/ := $match;
    };
  } elsif $nth {
    my %mods_altered = %mods;
    %mods_altered.delete('nth');
    %mods_altered{'global'} = 1;
    my $rx = rx_core_(%mods_altered,$pat,$qo,$qc);
    my $nth_keys = %mods{'nth'};
    $matcher = sub ($string) {
      my $m = $string ~~ $rx;
      return $m if !$m;
      my @a = @$m; # XXX Error: Can't use "[object Object]" as a generic reference!
      @a = map {$_[0]} @$m if @{@a[0]};
      unshift(@a,undef); # 1-based.
      @a[$nth_keys];
    };
  } elsif $overlap {
    my %mods_altered = %mods;
    %mods_altered.delete('ov');
    %mods_altered.delete('overlap');
    my $rx = rx_core_(%mods_altered,$pat,$qo,$qc);
    $matcher = sub ($string) {
       my $pos = 0;  my $a = []; my $prev = -1; my $m;
       while 1 {
        my $s = substr($string,$pos) // last;
        $m = $s ~~ $rx or last;
        last if !$m; # XXX - should be caught by  or last;
        my $m0 = $m[0] // $m;
        my $at = $pos + $m0.from;
        $a.push($m0) if $at > $prev; $prev = $at;
        $pos += $m.from + 1;
       }
       # 0 ?? ([|] @$a) !! $a;  # XXX - 0 should be want.Item
       $a;
    };
  } else {
    my $pattern = $pat;
    if (substr($pattern,0,2) eq '(?') {
       if substr($pattern,0,4) eq '(?i)' { # XXX - kludge - use regex
         $pattern = substr($pattern,4);
         $ignore = 1;
       }
       if substr($pattern,0,4) eq '(?m)' {
         $pattern = substr($pattern,4);
         $multiline = 1;
       }
       # TODO - x
    }
    # TODO - \A \Z \z
    my $flags   = "";
    $flags ~= "i" if $ignore;
    $flags ~= "m" if $multiline;
    $matcher = sub ($string) {   # XXX - Are things getting boxed?
      my $ret = JS::inline('(
        function (pattern,flags,string) {

          var mkMatch = function (ok, from, to, s, a, h) {
            return new PIL2JS.Match(ok, from, to, s, a, h);
          };
          var unpack_match = function (match) {
            var from = match.index;
            var str  = match[0];
            var to   = from + str.length -1;
            var arr  = [];
            for(var i = 1; i < match.length; i++) {
              var s = match[i];
              var tf = s != null;
              if (!tf) { s = "" }
              arr.push( mkMatch(tf, null,null, s, null,null));
            }
            var m = mkMatch(true, from, to, str, arr, null);
            return m;
          };

          var regexp;
          try { // Guard against malformed patterns.
            regexp = new RegExp(pattern,flags);
          } catch (e) {
            return null;
          }
          var  match  = regexp.exec(string);
          if (!match) {
            return mkMatch(false, null,null, null, null,null);
          }
          var m = unpack_match(match);
          return m;
        }
      )')(~$pattern,$flags,~$string);
      if !defined $ret {  # XXX - should really be die, not warn.
        warn "JavaScript RegExp syntax error in /$pattern/.\n";
        $ret = $new_Match(?0, undef, undef, "", [], ());
      }
      $/ := $ret;
    };
  }

  JS::inline('(function (matcher) { return new PIL2JS.Rul(matcher) })')($matcher);
}


method JS::Root::matcher (Rul $rule:) {
  JS::inline('(function (rul) { return rul.matcher })')($rule);
}
for <ok from to str subpos subnamed> -> $attr {
  Pugs::Internals::eval "method JS::Root::$attr (Match \$match:) \{
    JS::inline('(function (match) \{ return match.$attr \})')(\$match);
  \}";
}

# Needs PIL2 and MMD to be done without hacks
sub PIL2JS::Internals::Hacks::postcircumfix_for_match_objects (
  Match $match, Int *@idxs
) is primitive {
  $match.subpos[*@idxs];
}

sub PIL2JS::Internals::Hacks::init_match_postcircumfix_method () is primitive {
  JS::inline('(function () {
    PIL2JS.addmethod(
      _3amain_3a_3aMatch,
      "postcircumfix:[]",
      _26PIL2JS_3a_3aInternals_3a_3aHacks_3a_3apostcircumfix_for_match_objects
    );
  })')();
}

# $0 etc. are aliases for $/[0] etc.

    # Here is an old JS version of :global.  Fyi.
    #      var regexp = new RegExp(pattern,flags + "g");
    #      var match  = regexp.exec(string);
    #      if (!match) {
    #        return mkMatch(false, null,null, null, null,null);
    #      }
    #      var g_from = match.index;
    #      var g_array = [];
    #      var g_to;
    #      while (match) {
    #        var m = unpack_match(match);
    #        g_array.push(m);
    #        g_to = regexp.lastIndex - 1;
    #        match = regexp.exec(string);
    #      }
    #      var g_str = string.substring(g_from,g_to+1);
    #      var m = mkMatch(true, g_from, g_to, g_str, g_array, null);
    #      return m;
