#----------------------------------------------------------------------
package Monitor;
#----------------------------------------------------------------------
#
# This package monitors accesses to variables like this:
#   monitor (\$x, "x");
# Now, if you access $x, a trace message will be printed
#

require Exporter;
@ISA = ("Exporter");
@EXPORT = qw(monitor unmonitor);

use strict;


sub monitor {
   my ($r_var, $name) = @_;
   my ($type) = ref($r_var);
   if ($type =~ /SCALAR/) {
       return tie $$r_var, 'Monitor::Scalar', $r_var, $name;
   } elsif ($type =~ /ARRAY/) {
       return tie @$r_var, 'Monitor::Array', $r_var, $name;
   } elsif ($type =~ /HASH/) {
       return tie %$r_var, 'Monitor::Hash', $r_var, $name;
   } else {
       print STDERR "require ref. to scalar, array or hash" unless $type;
   }
}
sub unmonitor {
   my ($r_var) = @_;
   my ($type) = ref($r_var);
   my $obj;
   if ($type =~ /SCALAR/) {
       Monitor::Scalar->unmonitor($r_var);
   } elsif ($type =~ /ARRAY/) {
       Monitor::Array->unmonitor($r_var);
   } elsif ($type =~ /HASH/) {
       Monitor::Hash->unmonitor($r_var);
   } else {
       print STDERR "require ref. to scalar, array or hash" unless $type;
   }
}

#------------------------------------------------------------------------
# Internal functions

package Monitor::Scalar;
sub TIESCALAR {
   my ($pkg, $rval, $name) = @_;
   my $obj = [$name, $$rval];
   bless $obj, $pkg;
   return $obj;
}

sub FETCH {
   my ($obj) = @_;
   my $val = $obj->[1];

   print STDERR 'MMMMM: Read    $', $obj->[0], " ... $val \n";
   return $val;
}
sub STORE {
   my ($obj, $val) = @_;
   my @lCallInfo;
   my $iRow;

   while (@lCallInfo = (caller($iRow++))[1..3])
   {
      my $iCol = 0;

      # the stack-fields
      foreach (@lCallInfo)
      {
        print STDERR $_ . " ";
      }
      print STDERR "\n";
   }

   print STDERR 'MMMMM: Wrote   $', $obj->[0], " ... $val \n";
   $obj->[1] = $val;
   return $val;
}

sub unmonitor {
   my ($pkg, $r_var) = @_;
   my $val;
   {
      my $obj = tied $$r_var;
      $val = $obj->[1];
      $obj->[0] = "_UNMONITORED_";
   }
   untie $$r_var;
   $$r_var = $val;
}

sub DESTROY {
   my ($obj) = @_;
   if ($obj->[0] ne '_UNMONITORED_') {
      print STDERR 'Died    $\n', $obj->[0];
   }
}

package Monitor::Array;

sub TIEARRAY {
   my ($pkg, $rarray, $name) = @_;
   my $obj = [$name, [@$rarray]];
   bless $obj, $pkg;
   return $obj;
}

sub FETCH {
   my ($obj, $index) = @_;
   my $val = $obj->[1]->[$index];
   print STDERR 'Read    $', $obj->[0], "[$index] ... $val\n";
   return $val;
}

sub STORE {
   my ($obj, $index, $val) = @_;
   print STDERR 'Wrote   $', $obj->[0], "[$index] ... $val\n";
   $obj->[1]->[$index] = $val;
   return $val;
}


sub DESTROY {
   my ($obj) = @_;
   if ($obj->[0] ne '_UNMONITORED_') {
      print STDERR 'Died    %', $obj->[0];
   }
}

sub unmonitor {
   my ($pkg, $r_var) = @_;
   my $r_array;
   {
      my $obj = tied @$r_var;
      $r_array = $obj->[1];
      $obj->[0] = "_UNMONITORED_";
   }
   untie @$r_var;
   @$r_var = @$r_array;

}

package Monitor::Hash;
sub TIEHASH {
   my ($pkg, $rhash, $name) = @_;
   my $obj = [$name, {%$rhash}];
   return (bless $obj, $pkg);
}


sub FIRSTKEY {
   my ($obj) = @_;
   my $val = scalar keys %{$obj->[1]}; each %{$obj->[1]};
   return $val;
}

sub NEXTKEY {
   my ($obj) = @_;
   my $val = each %{$obj->[1]};
   return $val;
}

sub CLEAR {
   my ($obj) = @_;
   print STDERR 'MMMMM: Cleared %', $obj->[0], "\n";
}

sub FETCH {
   my ($obj, $index) = @_;
   my $val = $obj->[1]->{$index};
   print STDERR 'MMMMM: Read    $', $obj->[0], "{$index} ... $val\n";
   return $val;
}

sub STORE {
   my ($obj, $index, $val) = @_;
   print STDERR 'MMMMM: Wrote   $', $obj->[0], "{$index} ... $val\n";
   $obj->[1]->{$index} = $val;
   return $val;
}

sub DELETE {
   my ($obj) = @_;
   if ($obj->[0] ne '_UNMONITORED_') {
      print STDERR 'MMMMM: Died    %', $obj->[0];
   }
}
sub unmonitor {
   my ($pkg, $r_var) = @_;
   my $r_hash;
   {
      my $obj = tied %$r_var;
      $r_hash = $obj->[1];
      $obj->[0] = "_UNMONITORED_";
   }
   untie %$r_var;
   %$r_var = %$r_hash;
}

if (!caller) {
   #TEST DRIVER : To test this package, invoke as "perl Monitor.pm"

   package main;

   print "TESTING Scalar Monitor\n\n";
   my $x;  my $a;
   Monitor::monitor (\$x, "x");
   $x = 10;
   $a = $x;
   Monitor::unmonitor(\$x);
   print "\$x ===> $x\n";

   print "\nTESTING Array Monitor\n\n";
   my @x = (1, 2, 3);
   Monitor::monitor (\@x, "x");
   $x [3] = 100;
   Monitor::unmonitor(\@x);
   print "\@x ===> @x\n";

   print "\nTESTING Hash Monitor\n\n";
   my %x ;
   Monitor::monitor (\%x, "hash");
   $x{"India"} = "N. Delhi";
   $x{"USA"} = "Washington";
   Monitor::unmonitor(\%x);
   $, = " ";
   print "\%x ===> ", %x, "\n";
}

1;
