package Message::Match;
{
  $Message::Match::VERSION = '1.132270';
}

use strict;use warnings;
require Exporter;
use vars qw(@ISA @EXPORT_OK);
@ISA = qw(Exporter);
@EXPORT_OK = qw(mmatch);

sub mmatch {
    my ($message, $match) = @_;
    die 'Message::Match::mmatch: two HASH references required'
        if  scalar @_ < 2 or
            scalar @_ > 2 or
            not ref $message or
            not ref $match or
            ref $message ne 'HASH' or
            ref $match ne 'HASH';

    return _match($message, $match);
}

sub _special {
    my ($message, $match) = @_;
    substr($match, 0, 8, '');
    if($match =~ m{^/}) { #regex type
        my $re;
        eval "\$re = qr$match;";    #this is hideously inefficient
                                    #but it is highly cacheable, later on
        if($message =~ $re) {
            return 1
        } else {
            return 0;
        }
    }
    die "Message::Match::mmatch: special of unknown type passed: $match";
}

sub _match {
    my ($message, $match) = @_;
    my $ref_message = ref $message; my $ref_match = ref $match;
    if(not $ref_message and not $ref_match) { #scalar on both sides
        if(substr($match, 0, 8) eq ' special') { #special handling
            return _special($message, $match);
        }
        return $message eq $match; #otherwise, brain-dead comparison
    }
    if($ref_message eq 'JSON::PP::Boolean' and $ref_match eq 'JSON::PP::Boolean') {
        return "$message" eq "$match";
    }
    if($ref_message eq 'HASH' and $ref_match eq 'HASH') {
        foreach my $key (keys %$match) {
            my $message = $message->{$key};
            my $match = $match->{$key};
            return 0 if not defined $message;
            return 0 if not defined $match;
            return 0 unless _match($message, $match);
        }
        return 1;
    }
    if($ref_message eq 'ARRAY' and not $ref_match) {    #check for scalar inside the array
        foreach my $item (@$message) {
            return 1 if $item eq $match;
        }
        return 0;
    }
    if($ref_message eq 'ARRAY' and $ref_match eq 'ARRAY') {  #check the entire array
        foreach my $item (@$match) {
            my $match = $item;
            my $message = shift @{$message};
            return 0 unless _match($message, $match);
        }
        return 1;
    }
    return 0; #anything we don't know about fails
}
1;
__END__

=head1 NAME

Message::Match - Fast, simple message matching

=head1 SYNOPSIS

    use Message::Match qw(mmatch);

    #basic usage
    mmatch(
        {a => 'b', c => 'd'},   #message
        {a => 'b'}              #match
    ); #true
    mmatch(
        {a => 'b', c => {x => 'y'}, #message
        {c => {x => 'y'}}           #match
    ); #true
    mmatch(
        {a => 'b', c => 'd'},   #message
        {x => 'y'}              #match
    ); #false

    #set membership
    mmatch(
        {a => [1,2,3], some => 'thing'},    #message
        {a => 2},                           #match
    ); #true
    mmatch(
        {a => [1,2,3], some => 'thing'},    #message
        {a => 4},                           #match
    ); #false

    #array recursion
    mmatch(
        {a => [{a => 'b'},2,3], x => 'y'},      #message
        {a => [{a => 'b'},2,3]},                #match
    ); #true

    #regex
    mmatch(
        {a => 'forefoot'},          #message
        {a => ' special/foo/'},     #match
    ); #true

    #universal match
    mmatch(
        {some => 'random', stuff => 'here'},    #message
        {},                                     #match
    ); #true

=head1 DESCRIPTION

This is a very light-weight and fast library that does some basic but
reasonably powerful message matching.

=head1 FUNCTION

=head2 mmatch($message, $match);

Takes two and only two arguments, both HASH references.

=head1 SEE ALSO

Good question; I found some things somewhat similiar to this, but not quite
close enough to mention here.

=head1 TODO

Define handling for other tuples:
 HASH,scalar
 scalar,HASH
 scalar,ARRAY
 ARRAY,HASH
 HASH,ARRAY

More special handling.

=head1 BUGS

None known.

=head1 COPYRIGHT

Copyright (c) 2012, 2013, 2016 Dana M. Diederich. All Rights Reserved.

=head1 AUTHOR

Dana M. Diederich <diederich@gmail.com>

=cut
