#!/usr/local/bin/perl

use Cache;
use Benchmark;
use vars qw($Size %cache %count_cache);
use strict;

$Size = 5000;
$| = 1;

sub report {
    my($desc, $count, $sub) = @_;
    my $timed = timestr(timeit($count, $sub));
    $timed =~ /([\d\.]+\s+cpu)/i;
    printf("%-65.65s %s\n", "[ timing ] $desc", $1);
}

sub test {
    my($desc, $eval) = @_;
    my $result = eval { &$eval } ? "OK" : "ERROR - $@";
    print "$result ... $desc\n";
}

tie %cache, 'Tie::Cache', { 
			   Debug => 0, 
			   MaxCount => $Size, 
			   MaxSize => 1000, 
			   MaxBytes => 5000000,
#			   WriteSync => 0,
#			   Debug => 2,
			  };

tie %count_cache, 'Tie::Cache', $Size;


my %normal;

print "++++ Benchmarking operations on Tie::Cache of size $Size\n\n";
my $i = 0;
report("insert of $Size elements into normal %hash", $Size,
       sub { $normal{++$i} = $i }
      );
$i = 0;
report("insert of $Size elements into MaxCount Tie::Cache", $Size,
       sub { $count_cache{++$i} = $i }
       );

$i = 0;
report("insert of $Size elements into MaxBytes Tie::Cache", $Size,
       sub { $cache{++$i} = $i }
       );


my $rv;
$i = 0;
report("reading $Size elements from normal %hash", 
       $Size, sub { $rv = $normal{++$i} } );
$i = 0;
report("reading $Size elements from MaxCount Tie::Cache", 
       $Size, sub { $rv = $count_cache{++$i} } );
$i = 0;
report("reading $Size elements from MaxBytes Tie::Cache", 
       $Size, sub { $rv = $cache{++$i} } );


$i = 0;
report("deleting $Size elements from normal %hash",
       $Size, sub { $rv = delete $normal{++$i} } );
$i = 0;
report("deleting $Size elements from MaxCount Tie::Cache",
       $Size, sub { $rv = delete $count_cache{++$i} }
       );
report("deleting $Size elements from MaxBytes Tie::Cache",
       $Size, sub { $rv = delete $cache{++$i} }
       );

my $over = $Size * 2;
$i = 0;
%cache = ();
report(
       "$over inserts overflowing MaxBytes Tie::Cache", 
       $over,
       sub { $cache{++$i} = $i; }
       );

$i = 0;
report(
       "$over reads from overflowed MaxBytes Tie::Cache",
       $over,
       sub { $cache{++$i} }
       );

report(
       "$over undef inserts, not affecting MaxBytes Tie::Cache",
       $over,
       sub { $cache{rand()} = undef; }
      );

report(
       "$over undef reads, not affecting MaxBytes Tie::Cache",
       $over,
       sub { $cache{rand()}; }
      );

print "\n++++ Testing for correctness\n\n";
my @keys = keys %cache;
test("number of keys in %cache = $Size",
     sub { @keys == $Size }
    );
test("first key in %cache = ".($Size + 1),
     sub { $keys[0] == $Size + 1 }
    );
test("last key in %cache = ".($Size + $Size),
     sub { $keys[$#keys] == $Size + $Size }
    );
test("first key value in %cache = ".($Size + 1),
     sub { $cache{$keys[0]} == $Size + 1 }
    );

delete $cache{$keys[0]};
test("deleting key $keys[0]; no value defined for deleted key",
     sub { ! defined $cache{$keys[0]} }
    );
test("existance of deleted key = ! exists",
     sub { ! exists $cache{$Size+1} }
    );
@keys = keys %cache;
test("first key in %cache after delete = ".($Size + 2),
     sub { $keys[0] == $Size + 2 }
    );
test("keys in cache after delete = ".($Size-1),
     sub { keys %cache == $Size - 1 }
     );

print "\n++++ Stats for %cache\n\n";
my $obj = tied(%cache);
print join("\n", map { "$_:\t$obj->{$_}" } 'count', 'hit', 'miss', 'bytes');
print "\n";

# personalize the Tie::Cache object, by inheriting from it
package My::Cache;
use vars qw(@ISA);
@ISA = qw(Tie::Cache);

my($read_count, $write_count) = (0,0);
# override the read() and write() member functions
# these tell the cache what to do with a cache miss or flush
sub read { 
    my($self, $key) = @_; 
#    print "cache miss for $key, read() data\n";
    $read_count++;
    rand() * $key; 
}
sub write { 
    my($self, $key, $value) = @_;
    $write_count++;
#    print "flushing [$key, $value] from cache, write() data\n";
}

print "\n++++ Testing TRUE CACHE ++++\n\n";

my $cache_size   = 100;
my %cache;

tie %cache, 'My::Cache', {
    MaxBytes => $cache_size * 1000,
    MaxCount => $cache_size,
    Debug => 0,
    WriteSync => 0,
    };

# load the cache with new data, each through its contents,
# and then reload in reverse order.
&main::test("read count == 0 pre reads", sub { $read_count == 0 });
my $count = 0;
for(1..$cache_size) { 
    my $value = $cache{$_};
}
&main::test("read count == $cache_size post reads", sub { $read_count == $cache_size });

for(1..$cache_size) {
    my $new_value = int(rand() * 10);
    $cache{$_} = $new_value;
}

&main::test("write count == 0 pre flush()", sub { $write_count == 0 });
tied(%cache)->flush();
&main::test("write count == $cache_size post flush()", sub { $write_count == $cache_size });

%cache = ();

&main::test("write count == $cache_size post CLEAR()", sub { $write_count == $cache_size });

undef %cache;

print "\n";

exit;

