# all 6.e specific sub postcircumfix [; ] candidates here please

proto sub postcircumfix:<[; ]>($, $, $?, *%) is nodal {*}

# This candidate must be provided because assignment to a multi-level
# hash is codegenned this way.
multi sub postcircumfix:<[; ]>(\SELF, @indices, Mu \assignee) is raw {
    my int $dims = @indices.elems;   # reifies
    my $indices := nqp::getattr(@indices,List,'$!reified');

    my int $i;
    nqp::while(
      nqp::islt_i($i,$dims) && nqp::istype(nqp::atpos($indices,$i),Int),
      ++$i
    );

    nqp::iseq_i($i,$dims)                   # True if all indices are Int
      ?? nqp::iseq_i($dims,2)
        ?? SELF.ASSIGN-POS(
             nqp::atpos($indices,0),
             nqp::atpos($indices,1),
             assignee
           )
        !! nqp::iseq_i($dims,3)
          ?? SELF.ASSIGN-POS(
               nqp::atpos($indices,0),
               nqp::atpos($indices,1),
               nqp::atpos($indices,2),
               assignee
             )
          !! SELF.ASSIGN-POS(|@indices, assignee)
      # need an extra named here to prevent infilooping because otherwise
      # this will code-gen to a call to this candidate again.
      !! (postcircumfix:<[; ]>(SELF, @indices, :none) = assignee)
}

# This candidate must be provided because binding to a multi-level
# hash is codegenned this way.
multi sub postcircumfix:<[; ]>(\SELF, @indices, :$BIND! is raw) is raw {
    my int $dims = @indices.elems;   # reifies
    my $indices := nqp::getattr(@indices,List,'$!reified');

    my int $i;
    nqp::while(
      nqp::islt_i($i,$dims) && nqp::istype(nqp::atpos($indices,$i),Int),
      ++$i
    );

    nqp::iseq_i($i,$dims)                   # True if all indices are Int
      ?? nqp::iseq_i($dims,2)
        ?? SELF.BIND-POS(
             nqp::atpos($indices,0),
             nqp::atpos($indices,1),
             $BIND
           )
        !! nqp::iseq_i($dims,3)
          ?? SELF.BIND-POS(
               nqp::atpos($indices,0),
               nqp::atpos($indices,1),
               nqp::atpos($indices,2),
               $BIND
             )
          !! SELF.BIND-POS(|@indices, $BIND)
      !! X::Bind::Slice.new(type => SELF.WHAT).throw
}

# handle the case of @a[|| 0]
multi sub postcircumfix:<[; ]>(\initial-SELF, \value, *%_) is raw {
    postcircumfix:<[; ]>(initial-SELF, value.List, |%_)
}

# This candidate provides all of the multi-level array access, as well
# as providing the slow-path for assignment of a multi-level array.
multi sub postcircumfix:<[; ]>(\initial-SELF, @indices, *%_) is raw {

    # helper sub to ensure non-assignability
    sub non-assignable(\result) is raw {
        nqp::istype(result,Array)
          ?? result.List
          !! nqp::decont(result)
    }

    # find out what we actually got
    my str $adverbs;
    if nqp::getattr(%_,Map,'$!storage') -> $nameds is raw {
        $adverbs = nqp::atkey($nameds,'exists')
          ?? ":exists"
          !! ":!exists"
          if nqp::existskey($nameds,'exists');

        $adverbs =
               nqp::concat($adverbs,":delete") if nqp::atkey($nameds,'delete');
        $adverbs = nqp::concat($adverbs,":k" ) if nqp::atkey($nameds,'k');
        $adverbs = nqp::concat($adverbs,":kv") if nqp::atkey($nameds,'kv');
        $adverbs = nqp::concat($adverbs,":p" ) if nqp::atkey($nameds,'p');
        $adverbs = nqp::concat($adverbs,":v" ) if nqp::atkey($nameds,'v');
    }

    my int $topdim = @indices.elems;  # .elems reifies
    my $indices   := nqp::getattr(@indices,List,'$!reified');
    my int $i;
    nqp::while(
      nqp::islt_i($i,$topdim) && nqp::istype(nqp::atpos($indices,$i),Int),
      ++$i
    );

    # potential fast paths
    if nqp::iseq_i($i,$topdim) {    # all indices are Ints
        if $adverbs {
            if nqp::iseq_s($adverbs,":delete") {
                return nqp::iseq_i($topdim,2)
                  ?? initial-SELF.EXISTS-POS(
                       nqp::atpos($indices,0),
                       nqp::atpos($indices,1)
                     ) ?? non-assignable(initial-SELF.DELETE-POS(
                            nqp::atpos($indices,0),
                            nqp::atpos($indices,1)
                          ))
                       !! Nil
                  !! nqp::iseq_i($topdim,3)
                    ?? initial-SELF.EXISTS-POS(
                         nqp::atpos($indices,0),
                         nqp::atpos($indices,1),
                         nqp::atpos($indices,2)
                       ) ?? non-assignable(initial-SELF.DELETE-POS(
                              nqp::atpos($indices,0),
                              nqp::atpos($indices,1),
                              nqp::atpos($indices,2)
                            ))
                         !! Nil
                    !! initial-SELF.EXISTS-POS(|@indices)
                      ?? non-assignable(initial-SELF.DELETE-POS(|@indices))
                      !! Nil
            }

            elsif nqp::iseq_s($adverbs,":exists") {
                return nqp::iseq_i($topdim,2)
                  ?? initial-SELF.EXISTS-POS(
                       nqp::atpos($indices,0),
                       nqp::atpos($indices,1)
                     )
                  !! nqp::iseq_i($topdim,3)
                    ?? initial-SELF.EXISTS-POS(
                         nqp::atpos($indices,0),
                         nqp::atpos($indices,1),
                         nqp::atpos($indices,2)
                       )
                    !! initial-SELF.EXISTS-POS(|@indices)
            }

            elsif nqp::iseq_s($adverbs,":!exists") {
                return not nqp::iseq_i($topdim,2)
                  ?? initial-SELF.EXISTS-POS(
                       nqp::atpos($indices,0),
                       nqp::atpos($indices,1)
                     )
                  !! nqp::iseq_i($topdim,3)
                    ?? initial-SELF.EXISTS-POS(
                         nqp::atpos($indices,0),
                         nqp::atpos($indices,1),
                         nqp::atpos($indices,2)
                       )
                    !! initial-SELF.EXISTS-POS(|@indices)
            }
        }

        # fast path without adverbs
        else {
            return-rw nqp::iseq_i($topdim,2)
              ?? initial-SELF.AT-POS(
                   nqp::atpos($indices,0),
                   nqp::atpos($indices,1)
                 )
              !! nqp::iseq_i($topdim,3)
                ?? initial-SELF.AT-POS(
                     nqp::atpos($indices,0),
                     nqp::atpos($indices,1),
                     nqp::atpos($indices,2)
                   )
                !! initial-SELF.AT-POS(|@indices)
        }
    }

    # Did not fast path.  Map $topdim to the highest index number, so that
    # it can be easier used in recursion checks.
    --$topdim;

    # set up standard lexical info for recursing subs
    my \target = nqp::create(IterationBuffer);
    my int $dim;
    my int $return-list;

    if $adverbs {

        if nqp::iseq_s($adverbs,":exists") || nqp::iseq_s($adverbs,":!exists") {
            my $wantnot := nqp::iseq_s($adverbs,":!exists").Bool;

            my sub EXISTS-POS-recursively(\SELF, \idx --> Nil) {
                if nqp::istype(idx, Iterable) && nqp::not_i(nqp::iscont(idx)) {
                    $return-list  = 1;
                    my $iterator := idx.iterator;
                    nqp::until(
                      nqp::eqaddr(
                        (my \pulled := $iterator.pull-one),
                        IterationEnd
                      ),
                      EXISTS-POS-recursively(SELF, pulled)
                    );
                }
                elsif nqp::islt_i($dim,$topdim) {
                    ++$dim;  # going higher
                    if nqp::istype(idx,Whatever) {
                        $return-list  = 1;
                        my \next-idx := nqp::atpos($indices,$dim);
                        my int $i     = -1;
                        my int $elems = SELF.elems;
                        nqp::while(
                          nqp::islt_i(++$i,$elems),
                          EXISTS-POS-recursively(SELF.AT-POS($i), next-idx)
                        );
                    }
                    elsif nqp::istype(idx,Callable) {
                        EXISTS-POS-recursively(
                          SELF.AT-POS(.Int), nqp::atpos($indices,$dim)
                        ) for idx.(SELF.elems);
                    }
                    else  {
                        EXISTS-POS-recursively(
                          SELF.AT-POS(idx.Int), nqp::atpos($indices,$dim)
                        );
                    }
                    --$dim;  # done at this level
                }
                # $next-dim == $topdim, reached leaves
                elsif nqp::istype(idx,Whatever) {
                    $return-list  = 1;
                    my int $i     = -1;
                    my int $elems = SELF.elems;
                    nqp::while(
                      nqp::islt_i(++$i,$elems),
                      nqp::push(target,$wantnot ?^ SELF.EXISTS-POS($i))
                    );
                }
                elsif nqp::istype(idx,Callable) {
                    nqp::push(target, $wantnot ?^ SELF.EXISTS-POS(.Int))
                      for idx.(SELF.elems);
                }
                else {
                    nqp::push(target, $wantnot ?^ SELF.EXISTS-POS(idx.Int));
                }
            }

            EXISTS-POS-recursively(initial-SELF,nqp::atpos($indices,0));
        }

        elsif nqp::iseq_s($adverbs,":delete") {
            my sub DELETE-POS-recursively(\SELF, \idx --> Nil) {
                if nqp::istype(idx, Iterable) && nqp::not_i(nqp::iscont(idx)) {
                    $return-list  = 1;
                    my $iterator := idx.iterator;
                    nqp::until(
                      nqp::eqaddr(
                        (my \pulled := $iterator.pull-one),
                        IterationEnd
                      ),
                      DELETE-POS-recursively(SELF, pulled)
                    );
                }
                elsif nqp::islt_i($dim,$topdim) {
                    ++$dim;  # going higher
                    if nqp::istype(idx,Whatever) {
                        $return-list  = 1;
                        my \next-idx := nqp::atpos($indices,$dim);
                        my int $i     = -1;
                        my int $elems = SELF.elems;
                        nqp::while(
                          nqp::islt_i(++$i,$elems),
                          DELETE-POS-recursively(SELF.AT-POS($i), next-idx)
                        );
                    }
                    elsif nqp::istype(idx,Callable) {
                        DELETE-POS-recursively(
                          SELF.AT-POS(.Int), nqp::atpos($indices,$dim)
                        ) for idx.(SELF.elems);
                    }
                    else  {
                        DELETE-POS-recursively(
                          SELF.AT-POS(idx.Int), nqp::atpos($indices,$dim)
                        );
                    }
                    --$dim;  # done at this level
                }
                # $next-dim == $topdim, reached leaves
                elsif nqp::istype(idx,Whatever) {
                    $return-list  = 1;
                    my int $i     = -1;
                    my int $elems = SELF.elems;
                    nqp::while(
                      nqp::islt_i(++$i,$elems),
                      nqp::push(
                        target,
                        SELF.EXISTS-POS($i)
                          ?? non-assignable(SELF.DELETE-POS($i))
                          !! Nil
                      )
                    );
                }
                elsif nqp::istype(idx,Callable) {
                    nqp::push(
                      target,
                      SELF.EXISTS-POS(.Int)
                        ?? non-assignable(SELF.DELETE-POS(.Int))
                        !! Nil
                    ) for idx.(SELF.elems);
                }
                else {
                    nqp::push(
                      target,
                      SELF.EXISTS-POS(idx.Int)
                        ?? non-assignable(SELF.DELETE-POS(idx.Int))
                        !! Nil
                    );
                }
            }

            DELETE-POS-recursively(initial-SELF,nqp::atpos($indices,0));
        }

        # some other combination of adverbs
        else {

            # helper sub to create multi-level keys
            my $keys := nqp::create(IterationBuffer);  # keys encountered
            sub keys-to-list($index) is raw {
                nqp::push((my $list := nqp::clone($keys)),$index);
                $list.List
            }

            # determine the processor to be used
            my &process =
               nqp::iseq_s($adverbs,":exists:delete")
              ?? -> \SELF, \key {
                     SELF.DELETE-POS(key)
                       if nqp::push(target,SELF.EXISTS-POS(key));
                 }
            !! nqp::iseq_s($adverbs,":exists:delete:kv")
              ?? do {
                     $return-list = 1;
                     -> \SELF, \key {
                         if SELF.EXISTS-POS(key) {
                             SELF.DELETE-POS(key);
                             nqp::push(target,keys-to-list(key));
                             nqp::push(target,True);
                         }
                     }
                 }
            !! nqp::iseq_s($adverbs,":exists:delete:p")
              ?? -> \SELF, \key {
                     if SELF.EXISTS-POS(key) {
                         SELF.DELETE-POS(key);
                         nqp::push(
                           target,
                           Pair.new(keys-to-list(key), True)
                        );
                     }
                 }
            !! nqp::iseq_s($adverbs,":exists:kv")
              ?? do {
                     $return-list = 1;
                     -> \SELF, \key {
                         if SELF.EXISTS-POS(key) {
                             nqp::push(target,keys-to-list(key));
                             nqp::push(target,True);
                         }
                     }
                 }
            !! nqp::iseq_s($adverbs,":exists:p")
              ?? -> \SELF, \key {
                     nqp::push(
                       target,
                       Pair.new(keys-to-list(key), True)
                     ) if SELF.EXISTS-POS(key);
                 }
            !! nqp::iseq_s($adverbs,":delete:k")
              ?? -> \SELF, \key {
                     if SELF.EXISTS-POS(key) {
                         SELF.DELETE-POS(key);
                         nqp::push(target,keys-to-list(key));
                     }
                 }
            !! nqp::iseq_s($adverbs,":delete:kv")
              ?? do {
                     $return-list = 1;
                     -> \SELF, \key {
                         if SELF.EXISTS-POS(key) {
                             nqp::push(target,keys-to-list(key));
                             nqp::push(
                               target,
                               non-assignable(SELF.DELETE-POS(key))
                             );
                         }
                     }
                 }
            !! nqp::iseq_s($adverbs,":delete:p")
              ?? -> \SELF, \key {
                     nqp::push(
                       target,
                       Pair.new(
                         keys-to-list(key),
                         non-assignable(SELF.DELETE-POS(key))
                       )
                     ) if SELF.EXISTS-POS(key);
                 }
            !! nqp::iseq_s($adverbs,":delete:v")
              ?? -> \SELF, \key {
                     nqp::push(target,non-assignable(SELF.DELETE-POS(key)))
                       if SELF.EXISTS-POS(key);
                 }
            !! nqp::iseq_s($adverbs,":k")
              ?? -> \SELF, \key {
                     nqp::push(target,keys-to-list(key))
                       if SELF.EXISTS-POS(key);
                 }
            !! nqp::iseq_s($adverbs,":kv")
              ?? do {
                     $return-list = 1;
                     -> \SELF, \key {
                         if SELF.EXISTS-POS(key) {
                             nqp::push(target,keys-to-list(key));
                             nqp::push(target,non-assignable(SELF.AT-POS(key)));
                         }
                     }
                 }
            !! nqp::iseq_s($adverbs,":p")
              ?? -> \SELF, \key {
                     nqp::push(
                       target,
                       Pair.new(
                         keys-to-list(key),
                         non-assignable(SELF.AT-POS(key))
                       )
                     ) if SELF.EXISTS-POS(key);
                 }
            !! nqp::iseq_s($adverbs,":v")
              ?? -> \SELF, \key {
                     nqp::push(target,non-assignable(SELF.AT-POS(key)))
                       if SELF.EXISTS-POS(key);
                 }
            !! return X::Adverb.new(
                 :what<slice>,
                 :source(try { initial-SELF.VAR.name } // initial-SELF.^name),
                 :nogo(nqp::split(':',nqp::substr($adverbs,1)))
               ).Failure;

            my sub PROCESS-POS-recursively(\SELF, \idx --> Nil) {
                if nqp::istype(idx,Iterable) && nqp::not_i(nqp::iscont(idx)) {
                    $return-list  = 1;
                    my $iterator := idx.iterator;
                    nqp::until(
                      nqp::eqaddr(
                        (my \pulled := $iterator.pull-one),
                        IterationEnd
                      ),
                      PROCESS-POS-recursively(SELF, pulled)
                    );
                }
                elsif nqp::islt_i($dim,$topdim) {
                    ++$dim;  # going higher
                    if nqp::istype(idx,Whatever) {
                        $return-list  = 1;
                        my \next-idx := nqp::atpos($indices,$dim);
                        my int $i     = -1;
                        my int $elems = SELF.elems;
                        nqp::while(
                          nqp::islt_i(++$i,$elems),
                          nqp::stmts(
                            nqp::push($keys,nqp::clone($i)),
                            PROCESS-POS-recursively(
                              SELF.AT-POS($i),
                              next-idx
                            ),
                            nqp::pop($keys)
                          )
                        );
                    }
                    elsif nqp::istype(idx,Callable) {
                        for idx.(SELF.elems) {
                            my $index = .Int;
                            nqp::push($keys,$index);
                            PROCESS-POS-recursively(
                              SELF.AT-POS($index),
                              nqp::atpos($indices,$dim)
                            );
                            nqp::pop($keys)
                        }
                    }
                    else  {
                        nqp::push($keys,idx.Int);
                        PROCESS-POS-recursively(
                          SELF.AT-POS(idx.Int), nqp::atpos($indices,$dim)
                        );
                        nqp::pop($keys);
                    }
                    --$dim;  # done at this level
                }
                # $next-dim == $topdim, reached leaves
                elsif nqp::istype(idx,Whatever) {
                    $return-list  = 1;
                    my int $i     = -1;
                    my int $elems = SELF.elems;
                    nqp::while(
                      nqp::islt_i(++$i,$elems),
                      process(SELF, $i)
                    );
                }
                elsif nqp::istype(idx,Callable) {
                    process(SELF, .Int) for idx.(SELF.elems);
                }
                else {
                    process(SELF, idx.Int);
                }
            }

            PROCESS-POS-recursively(initial-SELF, nqp::atpos($indices,0));
        }
    }

    # no adverbs whatsoever
    else {
        my sub AT-POS-recursively(\SELF, \idx --> Nil) {
            if nqp::istype(idx,Iterable) && nqp::not_i(nqp::iscont(idx)) {
                $return-list = 1;
                my $iterator := idx.iterator;
                nqp::until(
                  nqp::eqaddr(
                    (my \pulled := $iterator.pull-one),
                    IterationEnd
                  ),
                  AT-POS-recursively(SELF, pulled)
                );
            }
            elsif nqp::islt_i($dim,$topdim) {
                ++$dim;  # going higher
                if nqp::istype(idx,Whatever) {
                    $return-list  = 1;
                    my \next-idx := nqp::atpos($indices,$dim);
                    my int $i     = -1;
                    my int $elems = SELF.elems;
                    nqp::while(
                      nqp::islt_i(++$i,$elems),
                      AT-POS-recursively(SELF.AT-POS($i), next-idx)
                    );
                }
                elsif nqp::istype(idx,Callable) {
                    AT-POS-recursively(
                      SELF.AT-POS(.Int), nqp::atpos($indices,$dim)
                    ) for idx.(SELF.elems);
                }
                else  {
                    AT-POS-recursively(
                      SELF.AT-POS(idx.Int), nqp::atpos($indices,$dim)
                    );
                }
                --$dim;  # done at this level
            }
            # $next-dim == $topdim, reached leaves
            elsif nqp::istype(idx,Whatever) {
                $return-list  = 1;
                my int $i     = -1;
                my int $elems = SELF.elems;
                nqp::while(
                  nqp::islt_i(++$i,$elems),
                  nqp::push(target,SELF.AT-POS($i))
                );
            }
            elsif nqp::istype(idx,Callable) {
                nqp::push(target,SELF.AT-POS(.Int)) for idx.(SELF.elems);
            }
            else {
                nqp::push(target,SELF.AT-POS(idx.Int));
            }
        }

        AT-POS-recursively(initial-SELF, nqp::atpos($indices,0));
    }

    # post-process recursive result
    $return-list
      ?? target.List
      !! nqp::elems(target) ?? nqp::atpos(target,0) !! Nil
}

# Can be REMOVED **AFTER** the Raku grammar has become the default grammar
BEGIN &postcircumfix:<[; ]>.set_op_props;

# vim: expandtab shiftwidth=4
