package Test::Mock::Signature::Dispatcher;

use strict;
use warnings;

use Data::PatternCompare;

sub new {
    my $class  = shift;
    my $method = shift;

    my $params = {
        _method  => $method,
        _list    => [],
        _cmp     => Data::PatternCompare->new,
        _default => undef,
    };
    return bless($params, $class);
}

sub add {
    my $self = shift;
    my $meta = shift;
    my $list = $self->{'_list'};
    my $cmp  = $self->{'_cmp'};

    @$list = sort { $cmp->compare_pattern($a->params, $b->params) } @$list, $meta;
}

sub delete {
    my $self = shift;
    my $meta = shift;
    my $list = $self->{'_list'};
    my $cmp  = $self->{'_cmp'};

    @$list = grep { !$cmp->eq_pattern($_->params, $meta->params) } @$list;
}

sub compile {
    my $self = shift;
    return if defined $self->{'_default'};

    my $list = $self->{'_list'};
    my $cmp  = $self->{'_cmp'};

    $self->{'_default'} ||= do {
        no strict 'refs';

        *{$self->{'_method'}}{'CODE'}
    };
    my $default = $self->{'_default'};

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

        for my $meta ( @$list ) {
            if ($cmp->pattern_match(\@params, $meta->params)) {
                my $cb = $meta->callback;

                goto &$cb;
            }
        }

        goto &$default;
    };

    no strict 'refs';
    no warnings 'redefine';
    *{$self->{'_method'}} = $code;
}

sub DESTROY {
    my $self   = shift;
    my $method = $self->{'_method'};

    no strict 'refs';
    no warnings 'redefine';
    *$method = $self->{'_default'};
}

42;

__END__

=head1 NAME

Test::Mock::Signature::Dispatcher - method dispatcher class.

=head1 SYNOPSIS

You can add one more metadata to you dispatcher:

    my $dispatcher = $mock->dispatcher('my_method_name');
    $dispatcher->add($meta);

Or delete some meta:

    $dispatcher->delete($meta);

Also you can compile your dispatcher meta information:

    $dispatcher->compile;

=head1 DESCRIPTION

Provides a dispatching mechanism for main mock module L<Test::Mock::Signature>.

=head1 METHODS

=head2 new($method_name)

Takes C<$method_name> fully qualifed method name e.g.: Real::Module::method.
Returns instance of the L<Test::Mock::Signature::Dispatcher> class.

=head2 add($meta)

Takes object of the L<Test::Mock::Signature::Meta> class and put it into
dispatching list.

=head2 delete($meta)

Takes object of the L<Test::Mock::Signature::Meta> class and remove it from
dispatching list.

=head2 compile

Mocks the method in real class and put dispatching mechanism - on.

=head2 DESTROY

Removes mocked method and put back default behavior.

=head1 AUTHOR

cono E<lt>cono@cpan.orgE<gt>

=head1 COPYRIGHT

Copyright 2014 - cono

=head1 LICENSE

Artistic v2.0

=head1 SEE ALSO

L<Test::Mock::Signature>