The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package My;
use strict;
use Tie::Hash;
use Test::Simple tests => 107;

#use lib "../";
use constant CLASS => "Devel::Carnivore::Tie::Hash";

my %factories = (
	new_attribute        => 'attribute on my %hash',
	new_functional       => 'watch',
	new_blessed          => 'watch on a blessed scalar',
	new_scalar_attribute => 'attribute on my $scalar',
);


my @test_cases = ("Perl", "Larry", {}, []);

foreach my $method_name (keys %factories) { # 8 tests + 16 tests

	my $factory = $factories{$method_name};

	my $obj = Factory->execute($method_name);
	# testing whether the objects are tied to the right class
	ok(tied %$obj, "Object is tied via $factory.");
	ok( (tied %$obj) -> isa(CLASS ), "Object is tied to right class via $factory");
	
	my $filename = ".test.test";
	
	open my $out, ">$filename" or die "unable to open file: $filename due to $!. I was going to write
to a file called '.test.test'. Please make sure I have the neccessary permissions.";
	$Devel::Carnivore::OUT = $out;
	
	my $correct_line_number_cool = 0;
	my $correct_line_number_neat = 0;
	
	foreach my $test_case (@test_cases) { # 4 * 4
		$obj->cool($test_case); $correct_line_number_cool = get_line_number(); # on the same line as change occurs
		$obj->change_somewhere_else($test_case); $correct_line_number_neat = get_line_number(); 
		if(ref $test_case) {
			ok(ref $obj->cool eq ref $test_case, "changing a hash value to a ref still works via $factory");
		} else {
			ok($obj->cool eq $test_case, "changing a hash value still works via $factory");
		}
	}
	
	# testing unwatch
	unwatch $obj;	
	ok(! tied %$obj, "unwatching");
	
	open my $in, $filename or die "unable to open $filename due to $!";
	
	my $last = "";
	my $i    = 0;
	
	my $current_filename = quotemeta get_filename();
	
	# tests for the correct line number are badly needed
	while(<$in>) { # 4 * 4 * 3
		next if /^#/; # filter out comments in the test file
		if(/cool/) { # lines where cool changed
			my $test_case = quotemeta $test_cases[$i];
			ok(/$current_filename line \d+$/, "output tells the right filename via $factory");
			ok(/(\d+)$/, "line number found");
			ok($1 == $correct_line_number_cool, "output tells the right line number via $factory: $1 == $correct_line_number_cool");
			ok(/"cool" changed from "$last" to "$test_case"/, "output tells the right key name and values: $i");
			$last = $test_case;
			$i++;
		}
		elsif(/neat/) { # lines where neat changed 
			ok(/(\d+)$/, "line number found");
			ok($1 == $correct_line_number_neat, "output tells the right line number with 1 extra indirection: $1 == $correct_line_number_neat");
		} else {
			die "there shouldn't be any other lines"	
		}
		
		#warn "line = $_; NEW : $last to $test_case";
		
	}
}

for (0..6) { # 7 tests
	ok(test_fail($_), "things that shouldn't work, no. $_");
}

sub test_fail {
	my $index = shift;	
	local $@;

	my $method = "fail$index";	

	eval { Factory->execute($method) };

	# warn "$index failed: $@" if $@;

	return 1 if $@;
}

sub get_filename {
	my($package,$filename) = caller;
	return $filename;
}

sub get_line_number {
	my($package,$filename,$line) = caller;
	return $line;
}

package Factory;
use Devel::Carnivore;
use strict;

sub execute {
	no strict "refs";
	my($class,$method_name) = @_;
	
	$class->$method_name
}


sub new_attribute {
	my %self : Watch(1) = ();		

	bless \%self, shift;
}

sub new_functional {
	my $self  = {};		

	watch $self, 2;

	bless $self
}

sub new_blessed {
	my $self  = {};	

	bless $self;

	watch $self, 3;

	return $self;
}



sub new_scalar_attribute {
	my $self : Watch(4) = {};		
	bless $self
}


sub fail0 { my $s : Watch = "" }              # watch a string via attribute
sub fail1 { my $s = []; watch $s }            # watch an arrayref via function call
sub fail2 { my $b; my $s = \$b; watch $s }    # watch a scalar ref
sub fail3 { my $s : Watch = [] }              # watch an arrayref via attribute
sub fail4 { my $s : Watch = {}; $s = "fail" } # turn watched var into a string
sub fail5 { my $s : Watch = {}; $s = [] }     # turn watched var into some other reference

sub fail6 { # attempting to watch a var which is already tied
	my %var = (bla => "blub");
	tie %var, "Tie::StdHash";
	watch \%var;
	$var{foo} = "bar";
}

sub change_somewhere_else {
	my($self, $new_value) = @_;
	
	$self->neat($new_value); my $line_number = My::get_line_number();
	
	return $line_number;
}

sub cool {
	my $self      = shift;
	$self->{cool} = shift if @_;
	$self->{cool}
}

sub neat {
	my $self      = shift;
	$self->{neat} = shift if @_;
	$self->{neat}	
}