#!/usr/bin/perl

use strict;
BEGIN {
	$|  = 1;
	$^W = 1;
}

use Test::More tests => 35;
use Test::NoWarnings;
use Aspect;

my $good = 'SomePackage::some_method';
my $bad  = 'SomePackage::no_method';

pointcut_ok( string => 'SomePackage::some_method' );
pointcut_ok( re     => qr/some_method/            );
pointcut_ok( code   => sub { shift eq $good }     );

sub pointcut_ok {
	my $type      = shift;
	my $subject   = Aspect::Pointcut::Call->new(shift);

	# Do we get a compiled match function?
	my $compiled1 = $subject->compiled_weave;
	is( ref($compiled1), 'CODE', '->compiled_weave returns a CODE reference' );

	# Does it match the expected functions?
	my $good_matches = do { local $_ = $good; $compiled1->() };
	my $bad_matches  = do { local $_ = $bad;  $compiled1->() };
	ok(   $good_matches, "$type match"    );
	ok( ! $bad_matches,  "$type no match" );

	# Does it curry away to nothing?
	my $curried = $subject->match_curry;
	is( $curried, undef, 'Simple call curries away to nothing' );

	# Do we produce an appropriate compiled run-time function
	my $compiled2 = $subject->compiled_runtime;
	is( ref($compiled2), 'CODE', '->compiled_runtime returns a CODE reference' );

	# Does the compiled code work properly?
	my $good_match = do { local $_ = { sub_name => $good }; $compiled2->() };
	my $bad_match  = do { local $_ = { sub_name => $bad  }; $compiled2->() };
	ok(   $good_match, "$type match"    );
	ok( ! $bad_match,  "$type no match" );
}





######################################################################
# Overloading Tests

# Pointcut currying code will need to do boolean context checks on
# pointcuts, as will some user code.
# Validate we can actually be used in boolean context (and provide an
# entry point to examine where this overloads to in the debugger).
my $pointcut = call 'Foo::bar';
isa_ok( $pointcut, 'Aspect::Pointcut::Call' );
ok( $pointcut, 'Pointcut is usable in boolean context' );

# Test that negation creates a not pointcut
isa_ok( ! $pointcut, 'Aspect::Pointcut::Not' );





######################################################################
# Regression: Validate that the "not call and call" pattern works.

# The following package has two methods.
# A pointcut that defines "Not one and any method" should match two but
# not match one. And this rule should apply BOTH to the match_all
# define-time rule AND for the runtime rule.
SCOPE: {
	package One;

	sub one { }

	sub two { }
}

my $not_call_and_call = ! call('One::one') & call(qr/^One::/);
isa_ok( $not_call_and_call, 'Aspect::Pointcut::And' );

# Does match_all find only the second method?
is_deeply(
	[ $not_call_and_call->match_all ],
	[ 'One::two' ],
	'->match_all works as expected',
);

# Create the runtime-curried pointcut
my $curried = $not_call_and_call->match_curry;
is( $curried, undef, 'A call-only pointcut curries away to nothing' );





######################################################################
# Regression: Nested logic and nested call and run-time

# Combining nested logic with a mix of call and non-call pointcuts
# results in a situation where call pointcuts need to be retained
# at run-time so that we can limit calls to run-time pointcuts to the
# correct subset of cases to apply the run-time tests to.
SCOPE: {
	package Two;

	sub one { 1 }

	sub two { 2 }
}

my $complex = call qr/^Two::/ & (
	call qr/::one\z/
	| (
		wantscalar & call qr/::two\z/
	)
);
isa_ok( $complex, 'Aspect::Pointcut' );

ok(
	scalar $complex->match_contains('Aspect::Pointcut::Wantarray'),
	'Pointcut contains the Wantarray pointcut',
);

# We should match_all both functions
is_deeply(
	[ $complex->match_all ],
	[ 'Two::one', 'Two::two' ],
	'->match_all works as expected',
);

# Bind the aspect
before {
	$_[0]->return_value(0);
} $complex;

# Both functions should match in scalar context
is( scalar(Two::one()), 0, 'Scalar one matches' );
is( scalar(Two::two()), 0, 'Scalar two matches' );

# Only one should match in list context
is_deeply( [ Two::one() ], [ 0 ], 'List one matches' );
is_deeply( [ Two::two() ], [ 2 ], 'List two does not match' );
