# This file implements the following set operators:
#   (|)     union (ASCII)
#   ∪       union

proto sub infix:<(|)>(|) is pure {*}
multi sub infix:<(|)>()               { set() }
multi sub infix:<(|)>(QuantHash:D $a) { $a    } # Set/Bag/Mix

multi sub infix:<(|)>(Setty:D $a, Setty:D $b) {
    nqp::if(
      (my \araw := $a.RAW-HASH) && nqp::elems(araw),
      nqp::if(                                    # first has elems
        (my \braw := $b.RAW-HASH) && nqp::elems(braw),
        nqp::stmts(                               # second has elems
          (my \elems := nqp::clone(araw)),
          (my \iter := nqp::iterator(braw)),
          nqp::while(                             # loop over keys of second
            iter,
            nqp::bindkey(                         # bind into clone of first
              elems,
              nqp::iterkey_s(nqp::shift(iter)),
              nqp::iterval(iter)
            )
          ),
          nqp::create($a.WHAT).SET-SELF(elems)    # make it a Set(Hash)
        ),
        $a                                        # no second, so first
      ),
      nqp::if(                                    # no first
        (my \raw := $b.RAW-HASH) && nqp::elems(raw),
        nqp::if(                                  # but second
          nqp::istype($a,Set),$b.Set,$b.SetHash
        ),
        $a                                         # both empty
      )
    )
}
multi sub infix:<(|)>(Setty:D $a, Mixy:D  $b) { $a.Mixy  (|) $b }
multi sub infix:<(|)>(Setty:D $a, Baggy:D $b) { $a.Baggy (|) $b }

multi sub infix:<(|)>(Mixy:D $a, Mixy:D $b) {
    nqp::if(
      (my \araw := $a.RAW-HASH) && nqp::elems(araw),
      nqp::if(                                    # first has elems
        (my \braw := $b.RAW-HASH) && nqp::elems(braw),
        nqp::stmts(                               # second has elems
          (my \elems := nqp::clone(araw)),
          (my \iter := nqp::iterator(braw)),
          nqp::while(                             # loop over keys of second
            iter,
            nqp::if(
              nqp::existskey(
                araw,
                (my \key := nqp::iterkey_s(nqp::shift(iter)))
              ),
              nqp::if(   # must use HLL < because values can be bignums
                nqp::getattr(
                  nqp::decont(nqp::atkey(araw,key)),Pair,'$!value')
                < nqp::getattr(                   # > hl
                    nqp::decont(nqp::atkey(braw,key)),Pair,'$!value'),
                nqp::bindkey(elems,key,nqp::atkey(braw,key))
              ),
              nqp::bindkey(elems,key,nqp::atkey(braw,key))
            )
          ),
          nqp::create($a.WHAT).SET-SELF(elems)    # make it a Mix(Hash)
        ),
        $a                                        # no second, so first
      ),
      nqp::if(                                    # no first
        (my \raw := $b.RAW-HASH) && nqp::elems(raw),
        nqp::if(                                  # but second
          nqp::istype($a,Mix),$b.Mix,$b.MixHash
        ),
        $a                                        # both empty
      )
    )
}

multi sub infix:<(|)>(Mixy:D $a, Baggy:D $b) { $a (|) $b.Mix }
multi sub infix:<(|)>(Mixy:D $a, Setty:D $b) { $a (|) $b.Mix }

multi sub infix:<(|)>(Baggy:D $a, Mixy:D $b) { $a.Mixy (|) $b }
multi sub infix:<(|)>(Baggy:D $a, Baggy:D $b) {
    nqp::if(
      (my \araw := $a.RAW-HASH) && nqp::elems(araw),
      nqp::if(                                    # first has elems
        (my \braw := $b.RAW-HASH) && nqp::elems(braw),
        nqp::stmts(                               # second has elems
          (my \elems := nqp::clone(araw)),
          (my \iter := nqp::iterator(braw)),
          nqp::while(                             # loop over keys of second
            iter,
            nqp::if(
              nqp::existskey(
                araw,
                (my \key := nqp::iterkey_s(nqp::shift(iter)))
              ),
              nqp::if(
                nqp::islt_i(
                  nqp::getattr(
                    nqp::decont(nqp::atkey(araw,key)),Pair,'$!value'),
                  nqp::getattr(
                    nqp::decont(nqp::atkey(braw,key)),Pair,'$!value')
                ),
                nqp::bindkey(elems,key,nqp::atkey(braw,key))
              ),
              nqp::bindkey(elems,key,nqp::atkey(braw,key))
            )
          ),
          nqp::create($a.WHAT).SET-SELF(elems)    # make it a Bag
        ),
        $a                                        # no second, so first
      ),
      nqp::if(                                    # no first
        (my \raw := $b.RAW-HASH) && nqp::elems(raw),
        nqp::if(                                  # but second
          nqp::istype($a,Bag),$b.Bag,$b.BagHash
        ),
        $a                                        # both empty
      )
    )
}
multi sub infix:<(|)>(Baggy:D $a, Setty:D $b) { $a (|) $b.Bag }

multi sub infix:<(|)>(Map:D \a, Map:D \b) {
    nqp::create(Set).SET-SELF(
      Rakudo::QuantHash.ADD-MAP-TO-SET(
        Rakudo::QuantHash.COERCE-MAP-TO-SET(a),
        b
      )
    )
}

multi sub infix:<(|)>(Iterable:D \a, Iterable:D \b) {
    (my $aiterator := a.flat.iterator).is-lazy
      || (my $biterator := b.flat.iterator).is-lazy
      ?? Any.fail-iterator-cannot-be-lazy('union', 'set')
      !! nqp::create(Set).SET-SELF(
           Rakudo::QuantHash.ADD-PAIRS-TO-SET(
             Rakudo::QuantHash.ADD-PAIRS-TO-SET(
               nqp::create(Rakudo::Internals::IterationSet),
               $aiterator,
               Mu
             ),
             $biterator,
             Mu
           )
         )
}

multi sub infix:<(|)>(Failure:D $a, Any) { $a.throw }
multi sub infix:<(|)>(Any, Failure:D $b) { $b.throw }
multi sub infix:<(|)>(Any \a, Any \b) {
    nqp::isconcrete(a)
      ?? nqp::istype(a,Mixy)
        ?? a (|) b.Mix
        !! nqp::istype(a,Baggy)
          ?? a (|) b.Bag
          !! nqp::istype(a,Setty)
            ?? a (|) b.Set
            !! nqp::isconcrete(b)
              ?? nqp::istype(b,Mixy)
                ?? a.Mix (|) b
                !! nqp::istype(b,Baggy)
                  ?? a.Bag (|) b
                  !! a.Set (|) b.Set
              !! a (|) b.Set
      !! a.Set (|) b
}

multi sub infix:<(|)>(+@p) {   # also Any
    my $iterator := @p.iterator;
    nqp::if(
      nqp::eqaddr((my $result := $iterator.pull-one),IterationEnd),
      set(),          # nothing to process
      nqp::if(
        nqp::eqaddr((my $pulled := $iterator.pull-one),IterationEnd),
        $result.Set,  # only 1 elem to process
        nqp::stmts(
          nqp::repeat_until(
            nqp::eqaddr(($pulled := $iterator.pull-one),IterationEnd),
            ($result := $result (|) $pulled)
          ),
          $result
        )
      )
    )
}

# U+222A UNION
my constant &infix:<∪> := &infix:<(|)>;

# vim: expandtab shiftwidth=4
