package Bread::Board::Container::Parameterized;
our $AUTHORITY = 'cpan:STEVAN';
$Bread::Board::Container::Parameterized::VERSION = '0.34';
use Moose;
use Moose::Util 'find_meta';
use Bread::Board::Container::FromParameterized;
# ABSTRACT: A parameterized container

use Bread::Board::Container;

with 'Bread::Board::Traversable';

has 'name' => (
    is       => 'rw',
    isa      => 'Str',
    required => 1
);

has 'allowed_parameter_names' => (
    is       => 'ro',
    isa      => 'ArrayRef',
    required => 1,
);

has 'container' => (
    is      => 'ro',
    isa     => 'Bread::Board::Container',
    lazy    => 1,
    builder => '_build_container',
    handles => [qw[
        add_service
        get_service
        has_service
        get_service_list
        has_services

        add_sub_container
        get_sub_container
        has_sub_container
        get_sub_container_list
        has_sub_containers
    ]]
);

sub _build_container {
    my $self = shift;
    Bread::Board::Container->new( name => $self->name )
}

sub fetch   { die "Cannot fetch from a parameterized container";   }
sub resolve { die "Cannot resolve from a parameterized container"; }

sub create {
    my ($self, %params) = @_;

    my @allowed_names = sort @{ $self->allowed_parameter_names };
    my @given_names   = sort keys %params;

    (scalar @allowed_names == scalar @given_names)
        || confess "You did not pass the correct number of parameters";

    ((join "" => @allowed_names) eq (join "" => @given_names))
        || confess "Incorrect parameter list, got: ("
                 . (join "" => @given_names)
                 . ") expected: ("
                 . (join "" => @allowed_names)
                 . ")";


    my $clone = $self->container->clone(
        name => ($self->container->name eq $self->name
                    ? join "|" => $self->name, @given_names
                    : $self->container->name)
    );

    my $from_parameterized_meta = find_meta('Bread::Board::Container::FromParameterized');
    $clone = $from_parameterized_meta->rebless_instance($clone);

    if ($self->has_parent) {
        my $cloned_parent = $self->parent->clone;

        $cloned_parent->sub_containers({
            %{ $cloned_parent->sub_containers },
            $self->name => $clone,
        });

        $clone->parent($cloned_parent);
    }

    foreach my $key ( @given_names ) {
        $clone->add_sub_container(
            $params{ $key }->clone( name => $key )
        );
    }

    $clone;
}

__PACKAGE__->meta->make_immutable;

no Moose; no Moose::Util; 1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Bread::Board::Container::Parameterized - A parameterized container

=head1 VERSION

version 0.34

=head1 DESCRIPTION

This class implements a sort of container factory for L<Bread::Board>:
a parameterized container is a, in practice, a function from a set of
parameters (which must be containers) to an actual container. See
L<Bread::Board::Manual::Example::FormSensible> for an example.

=head1 ATTRIBUTES

=head2 C<name>

Read/write string, required. Every container needs a name, by which it
can be referenced when L<fetching it|Bread::Board::Traversable/fetch>.

=head2 C<allowed_parameter_names>

Read-only arrayref of strings, required. These are the names of the
containers that must be passed to L<< C<create>|create ( %params ) >>
to get an actual container out of this parameterized object.

=head2 C<container>

This attribute holds the "prototype" container. Services inside it can
depend on service paths that include the container names given in
L</allowed_parameter_names>.

=head1 METHODS

=head2 C<add_service>

=head2 C<get_service>

=head2 C<has_service>

=head2 C<get_service_list>

=head2 C<has_services>

=head2 C<add_sub_container>

=head2 C<get_sub_container>

=head2 C<has_sub_container>

=head2 C<get_sub_container_list>

=head2 C<has_sub_containers>

All these methods are delegated to the "prototype" L</container>, so
that this object can be defined as if it were a normal container.

=head2 C<create>

  my $container = $parameterized_container->create(%params);

After checking that the keys of C<%params> are exactly the same
strings that are present in L</allowed_parameter_names>, this method
clones the prototype L</container>, adds the C<%params> to the clone
as sub-containers, and returns the clone.

If this was not a top-level container, the parent is also cloned, and
the container clone is added to the parent clone.

Please note that the container returned by this method does I<not>
have the same name as the parameterized container, and that calling
this method with different parameter values will return different
containers, but all with the same name. It's probably a bad idea to
instantiate a non-top-level parameterized container more than once.

=head2 C<fetch>

=head2 C<resolve>

These two methods die, since services in a parameterized container
won't usually resolve, and attempting to do so is almost always a
mistake.

=head1 AUTHOR

Stevan Little <stevan@iinteractive.com>

=head1 BUGS

Please report any bugs or feature requests on the bugtracker website
https://github.com/stevan/BreadBoard/issues

When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2016 by Infinity Interactive.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
