role Perl6::Metamodel::MultiMethodContainer {
    # Set of multi-methods to incorporate. Not just the method handles;
    # each is a hash containing keys name and body.
    has @!multi_methods_to_incorporate;
    has %!multi_candidate_names;

    # The proto we'll clone.
    my $autogen_method_proto;
    my $autogen_submethod_proto;

    # Sets the proto we'll auto-gen based on.
    method set_autogen_proto($method_proto, $submethod_proto) {
        $autogen_method_proto := $method_proto;
        $autogen_submethod_proto := $submethod_proto;
    }

    # We can't incorporate multis right away as we don't know all parents
    # yet, maybe, which influences whether we even can have multis, need to
    # generate a proto and so forth. So just queue them up in a todo list and
    # we handle it at class composition time.
    method add_multi_method($obj, $name, $code_obj) {
        # Represents a multi candidate to incorporate.
        my class MultiToIncorporate {
            has $!name;
            has $!code;
            method name() { $!name }
            method code() { $!code }
        }
        my $how := MultiToIncorporate.HOW.WHAT;
        my $todo := MultiToIncorporate.new( :name($name), :code(nqp::decont($code_obj)) );
        @!multi_methods_to_incorporate[+@!multi_methods_to_incorporate] := $todo;
        %!multi_candidate_names{$name} := 1;
        $code_obj;
    }

    # Gets the multi methods that are to be incorporated.
    method multi_methods_to_incorporate($obj) {
        @!multi_methods_to_incorporate
    }

    # Incorporates the multi candidates into the appropriate proto. Need to
    # implement proto incorporation yet.
    method incorporate_multi_candidates($obj) {
        my $num_todo := +@!multi_methods_to_incorporate;
        my $i := 0;
        my $submethod_type := Perl6::Metamodel::Configuration.submethod_type;
        my @new_protos;
        while $i != $num_todo {
            # Get method name and code.
            my $name := @!multi_methods_to_incorporate[$i].name;
            my $code := @!multi_methods_to_incorporate[$i].code;

            # Do we have anything in the methods table already in
            # this class?
            my $is_submethod  := nqp::istype(nqp::what($code), $submethod_type);
            my $method_table  := $is_submethod
                                    ?? 'submethod_table'
                                    !! 'method_table';
            my $autogen_proto := $is_submethod
                                    ?? $autogen_submethod_proto
                                    !! $autogen_method_proto;
            my %meths := nqp::hllize(self."$method_table"($obj));
            if nqp::existskey(%meths, $name) {
                # Yes. Only or dispatcher, though? If only, error. If
                # dispatcher, simply add new dispatchee.
                my $dispatcher := %meths{$name};
                if $dispatcher.is_dispatcher {
                    $dispatcher.add_dispatchee($code);
                }
                else {
                    nqp::die("Cannot have a multi candidate for '" ~ $name ~
                        "' when an only method is also in the package '" ~
                        self.name($obj) ~ "'");
                }
            }
            else {
                my $found := 0;
                unless $is_submethod {
                    # Go hunting in the MRO for a method proto. Note that we don't traverse MRO for submethods.
                    my @mro := self.mro($obj);
                    my $j := 1;
                    while $j != +@mro && !$found {
                        my $parent := @mro[$j];
                        my %meths := nqp::hllize($parent.HOW."$method_table"($parent));
                        if nqp::existskey(%meths, $name) {
                            # Found a possible - make sure it's a dispatcher, not
                            # an only.
                            my $dispatcher := %meths{$name};
                            if $dispatcher.is_dispatcher {
                                # Clone it and install it in our method table.
                                my $copy := $dispatcher.derive_dispatcher();
                                $copy.add_dispatchee($code);
                                self.add_method($obj, $name, $copy);
                                nqp::push(@new_protos, $copy);
                                $found := 1;
                            }
                        }
                        $j := $j + 1;
                    }
                }
                unless $found {
                    # No proto found, so we'll generate one here.
                    unless $autogen_proto {
                        nqp::die("Cannot auto-generate a proto method for '$name' in the setting");
                    }
                    my $proto := $autogen_proto.instantiate_generic(
                        nqp::hash('T', $obj));
                    $proto.set_name($name);
                    $proto.add_dispatchee($code);
                    self.add_method($obj, $name, $proto);
                    nqp::push(@new_protos, $proto);
                }
            }
            if nqp::can($code, 'apply_handles') && nqp::can($obj.HOW, 'find_method_fallback') {
                $code.apply_handles($obj);
            }
            $i := $i + 1;
        }
        for @new_protos {
            if nqp::can($_, 'sort_dispatchees') {
                $_.sort_dispatchees();
            }
        }
        @!multi_methods_to_incorporate := [];
        %!multi_candidate_names := nqp::hash();
    }

    method has_multi_candidate($obj, $name) {
        %!multi_candidate_names{$name}
    }
}

# vim: expandtab sw=4
