my role Setty does QuantHash {
    has Rakudo::Internals::IterationSet $!elems; # key.WHICH => key

    method of() { Bool }

    # private method to create Set from iterator, check for laziness
    method !create-from-iterator(\type, \iterator --> Setty:D) {
        iterator.is-lazy
          ?? type.fail-iterator-cannot-be-lazy('coerce')
          !! nqp::create(type).SET-SELF(
               Rakudo::QuantHash.ADD-ITERATOR-TO-SET(
                 nqp::create(Rakudo::Internals::IterationSet),
                 iterator,
                 self.keyof
               )
             )
    }

    multi method new(Setty: --> Setty:D) { nqp::create(self) }
    multi method new(Setty: \value --> Setty:D) {
        nqp::if(
          nqp::istype(value,Iterable) && nqp::not_i(nqp::iscont(value)),
          self!create-from-iterator(self, value.iterator),
          nqp::stmts(
            nqp::bindkey(
              (my $elems := nqp::create(Rakudo::Internals::IterationSet)),
              value.WHICH,
              nqp::decont(value)
            ),
            nqp::create(self).SET-SELF($elems)
          )
        )
    }
    multi method new(Setty: **@args --> Setty:D) {
        self!create-from-iterator(self, @args.iterator)
    }

    method new-from-pairs(*@pairs --> Setty:D) {
        (my \iterator := @pairs.iterator).is-lazy
          ?? self.fail-iterator-cannot-be-lazy('coerce')
          !! nqp::create(self).SET-SELF(
               Rakudo::QuantHash.ADD-PAIRS-TO-SET(
                 nqp::create(Rakudo::Internals::IterationSet),
                 iterator,
                 self.keyof
               )
             )
    }

    # https://github.com/rakudo/rakudo/issues/5057
    multi method deepmap(Setty:D: &mapper) {
        my $type  := self.WHAT;
        my $elems := $!elems;
        my $clone := nqp::clone($elems);
        my $iter  := nqp::iterator($elems);

        while $iter {
            nqp::shift($iter);
            my $value = 1;  # must be Scalar
            $value := nqp::decont($value) if nqp::istype($type,Set);

            nqp::deletekey($clone,nqp::iterkey_s($iter)) unless mapper($value);
            nqp::deletekey($elems,nqp::iterkey_s($iter)) unless $value;
        }

        nqp::p6bindattrinvres(nqp::create($type),$type,'$!elems',$clone)
    }

    method default(--> False) { }

    multi method keys(Setty:D:) {
        Seq.new(Rakudo::Iterator.Mappy-values($!elems))
    }

    method elems(Setty:D:) {
        nqp::istrue($!elems) && nqp::elems($!elems)
    }
    method total(Setty:D: --> Int:D) {
        nqp::istrue($!elems) && nqp::elems($!elems)
    }

    my class AntiPairs does Rakudo::Iterator::Mappy {
        method pull-one() {
            $!iter
              ?? Pair.new(True,nqp::iterval(nqp::shift($!iter)))
              !! IterationEnd
        }
    }
    multi method antipairs(Setty:D:) { Seq.new(AntiPairs.new($!elems)) }
    multi method minpairs(Setty:D:) { self.pairs }
    multi method maxpairs(Setty:D:) { self.pairs }
    multi method Bool(Setty:D: --> Bool:D) {
        nqp::hllbool($!elems ?? nqp::elems($!elems) !! 0)
    }

    method !HASHIFY(\type) {
        my \hash := Hash.^parameterize(type,Any).new;
        my \descriptor := nqp::getattr(hash,Hash,'$!descriptor');

        nqp::if(
          $!elems && nqp::elems($!elems),
          nqp::stmts(
            (my \storage := nqp::clone($!elems)),
            (my \iter := nqp::iterator(storage)),
            nqp::while(
              iter,
              nqp::bindkey(
                storage,
                nqp::iterkey_s(nqp::shift(iter)),
                Pair.new(
                  nqp::iterval(iter),
                  (nqp::p6scalarfromdesc(descriptor) = True)
                )
              )
            ),
            nqp::bindattr(hash,Map,'$!storage',storage)
          )
        );
        hash
    }
    multi method hash(Setty:D: --> Hash:D) { self!HASHIFY(Bool) }
    multi method Hash(Setty:D: --> Hash:D) { self!HASHIFY(Any) }

    method Map {
        nqp::if(
          $!elems && nqp::elems($!elems),
          nqp::stmts(
            (my \storage := nqp::hash),
            (my \iter := nqp::iterator($!elems)),
            nqp::while(
              iter,
              nqp::bindkey(
                storage,
                nqp::iterval(nqp::shift(iter)).Str,
                True
              )
            ),
            nqp::p6bindattrinvres(nqp::create(Map),Map,'$!storage',storage)
          ),
          nqp::create(Map)
        )
    }

    multi method ACCEPTS(Setty:U: \other --> Bool:D) {
        other.^does(self)
    }
    multi method ACCEPTS(Setty:D: Setty:D \other --> Bool:D) {
        self (==) other
    }
    multi method ACCEPTS(Setty:D: \other --> Bool:D) {
        self (==) other.Set
    }

    multi method Str(Setty:D $ : --> Str:D) {
        nqp::join(" ",Rakudo::QuantHash.RAW-VALUES-MAP(self, *.Str))
    }
    multi method gist(Setty:D $ : --> Str:D) {
        nqp::concat(
          nqp::concat(
            nqp::concat(self.^name,'('),
            nqp::join(" ",
              Rakudo::Sorting.MERGESORT-str(
                Rakudo::QuantHash.RAW-VALUES-MAP(self, *.gist)
              )
            )
          ),
          ')'
        )
    }
    multi method raku(Setty:D $ : --> Str:D) {
        nqp::if(
          nqp::eqaddr(self,set()),
          'set()',
          nqp::concat(
            nqp::concat(
              nqp::concat(self.^name,'.new('),
              nqp::join(",",Rakudo::QuantHash.RAW-VALUES-MAP(self, *.raku))
            ),
            ')'
          )
        )
    }

    proto method grab(|) {*}
    proto method grabpairs(|) {*}

    multi method pick(Setty:D:) { self.roll }
    multi method pick(Setty:D: Callable:D $calculate) {
        self.pick( $calculate(self.elems) )
    }
    multi method pick(Setty:D: Whatever $) {
        self.pick(Inf)
    }

    my class PickN does Rakudo::QuantHash::Pairs {
        method pull-one() is raw {
            nqp::elems($!picked)
              ?? nqp::atkey($!elems,nqp::pop_s($!picked))
              !! IterationEnd
        }
    }
    multi method pick(Setty:D: $count) { Seq.new(PickN.new($!elems,$count)) }

    proto method pickpairs(|) {*}
    multi method pickpairs(Setty:D:) { Pair.new(self.roll,True) }
    multi method pickpairs(Setty:D: Callable:D $calculate) {
        self.pickpairs( $calculate(self.elems) )
    }
    multi method pickpairs(Setty:D: Whatever $) {
        self.pickpairs(Inf)
    }

    my class PickPairsN does Rakudo::QuantHash::Pairs {
        method pull-one() is raw {
            nqp::elems($!picked)
              ?? Pair.new(nqp::atkey($!elems,nqp::pop_s($!picked)),True)
              !! IterationEnd
        }
    }
    multi method pickpairs(Setty:D: \count) {
        Seq.new(PickPairsN.new($!elems,count))
    }

    proto method roll(|) {*}
    multi method roll(Setty:D:) {
        $!elems
          ??  nqp::iterval(Rakudo::QuantHash.ROLL($!elems))
          !! Nil
    }
    multi method roll(Setty:D: Callable:D $calculate) {
        self.roll($calculate(self.elems))
    }
    multi method roll(Setty:D: Whatever) {
        self.roll(Inf)
    }
    multi method roll(Setty:D: $count) {
        Seq.new(nqp::if(
          (my $todo = Rakudo::QuantHash.TODO($count))
            && $!elems
            && (my int $elems = nqp::elems($!elems)),
          nqp::stmts(
            (my $keys := Rakudo::QuantHash.RAW-KEYS(self)),
            nqp::if(
              $todo == Inf,
              Rakudo::Iterator.Callable(
                { nqp::atkey($!elems,nqp::atpos_s($keys,nqp::rand_n($elems))) },
                True
              ),
              Rakudo::Iterator.Callable( {
                  nqp::if(
                    $todo,
                    nqp::stmts(
                      --$todo,
                      nqp::atkey(
                        $!elems,
                        nqp::atpos_s($keys,nqp::rand_n($elems))
                      )
                    ),
                    IterationEnd
                  )
              } )
            )
          ),
          Rakudo::Iterator.Empty
        ))
    }

    multi method EXISTS-KEY(Setty:D: \k --> Bool:D) {
        nqp::hllbool($!elems ?? nqp::existskey($!elems,k.WHICH) !! 0)
    }

    multi method Bag(Setty:D:) {
        $!elems && nqp::elems($!elems)
          ?? nqp::create(Bag).SET-SELF(Rakudo::QuantHash.SET-BAGGIFY($!elems))
          !! bag()
    }
    multi method BagHash(Setty:D:) {
        $!elems && nqp::elems($!elems)
          ?? nqp::create(BagHash).SET-SELF(
               Rakudo::QuantHash.SET-BAGGIFY($!elems))
          !! nqp::create(BagHash)
    }
    multi method Mix(Setty:D:) {
        $!elems && nqp::elems($!elems)
          ?? nqp::create(Mix).SET-SELF(Rakudo::QuantHash.SET-BAGGIFY($!elems))
          !! mix()
    }
    multi method MixHash(Setty:D:) {
        $!elems && nqp::elems($!elems)
          ?? nqp::create(MixHash).SET-SELF(
               Rakudo::QuantHash.SET-BAGGIFY($!elems))
          !! nqp::create(MixHash)
    }

    method RAW-HASH() is raw is implementation-detail { $!elems }

    # TODO: WHICH will require the capability for >1 pointer in ObjAt
}

multi sub infix:<eqv>(Setty:D $a, Setty:D $b --> Bool:D) {
    nqp::hllbool(
      nqp::eqaddr($a,$b)
        || (nqp::eqaddr($a.WHAT,$b.WHAT) && $a.ACCEPTS($b))
    )
}

# vim: expandtab shiftwidth=4
