# ------------------------------------------------------------------------------
#  Copyright © 2003 by Matt Luker.  All rights reserved.
# 
#  Revision:
# 
#  $Header$
# 
# ------------------------------------------------------------------------------

# SmartHash.pm - Hash with default values.
# 
# SmartHash objects can also be given a callback method parameter to call when
# values are changed.  This allows wrapping objects to implement "is dirty?"
# mechanisms.
#
# Change call back methods will be passed the object reference, the key name,
# the old value, and the new value.  Callback methods are called AFTER the value
# has been changed.
#
# @author  Matt Luker
# @version $Revision: 1327 $

# SmartHash.pm - Hash with default values.
# 
# Copyright (C) 2003, Matt Luker
# 
# This library is free software; you can redistribute it and/or modify
# it under the same terms as Perl itself. 

# If you have any questions about this software,
# or need to report a bug, please contact me.
# 
# Matt Luker
# Port Angeles, WA
# kostya@redstarhackers.com
# 
# TTGOG

package RSH::SmartHash;

use 5.008;
use strict;
use warnings;

require Tie::Hash;

our @ISA = qw(Tie::Hash);

use RSH::Exception;

# ******************** PUBLIC Class Methods ********************

sub merge_hashes {
	my @hash_refs = @_;

	if (scalar(@hash_refs) == 0) { die new RSH::CodeException message => 'Please supply a hash reference.'; }

	for (my $i = 1; $i < scalar(@hash_refs); $i++) {
		if (ref($hash_refs[$i]) ne 'HASH') { next; }
		foreach my $key (keys %{$hash_refs[$i]}) {
			if (defined($key) && defined($hash_refs[$i]->{$key})) {
				$hash_refs[0]->{$key} = $hash_refs[$i]->{$key};
			}
		}
	}

	return $hash_refs[0];
}
	

# ******************** CONSTRUCTOR Methods ********************

sub new {
	my $class = shift;
	my %params = @_;

	my $default_vals = $params{default};
	my $vals = $params{values};
	my $change_callback = $params{change_callback};
	my $dirty = $params{dirty};

	my $self = {};
	$self->{default} = $default_vals;
	$self->{hash} = $vals;
	if ( (defined($change_callback)) &&
		 (ref($change_callback ne 'CODE')) ) {
		$change_callback = undef;
	}

	$self->{change_callback} = $change_callback;

	if (not defined($dirty)) {
		$dirty = 0;
	}

	$self->{dirty} = $dirty;
		
	bless $self, $class;
	return $self;
}

sub TIEHASH {
	return (new @_);
}

# ******************** PUBLIC Instance Methods ********************

# ******************** Hash Tie Methods ********************

sub STORE {
	my $self = shift;
	my $key = shift;
	my $val = shift;

	my $old_val = $self->{hash}{$key};
	$self->{hash}{$key} = $val;
	if ( defined($old_val) && 
		 defined($val) && 
		 (ref($old_val) eq ref($val)) && 
		 defined(($old_val ne $val)) &&
		 ($old_val ne $val) ) {

		$self->{dirty} = 1;
		if (defined($self->{change_callback})) {
			&{$self->{change_callback}}($self, $key, $old_val, $val);
		}
	} elsif ( (not defined($old_val)) && (not defined($val) ) ) {
		# NOTHING
	} else {
		# one is defined and one isn't, which is different--so ...
		$self->{dirty} = 1;
		if (defined($self->{change_callback})) {
			&{$self->{change_callback}}($self, $key, $old_val, $val);
		}
	}
}

sub FETCH {
	my $self = shift;
	my $key = shift;

	if (defined($self->{hash}{$key})) { return $self->{hash}{$key}; }
	else { return $self->{default}{$key}; }

}

sub FIRSTKEY {
	my $self = shift;

	my $a = keys %{$self->{hash}};
	each %{$self->{hash}};
}

sub NEXTKEY {
	my $self = shift;
	my $last_key = shift;
	each %{$self->{hash}};
}

sub EXISTS {
	my $self = shift;
	my $key = shift;

	if (not exists($self->{hash}{$key})) { return exists($self->{default}{$key}); }
	else { return exists($self->{default}{$key}); }
}

sub DELETE {
	my $self = shift;
	my $key = shift;

	delete $self->{hash}{$key};
}

sub CLEAR {
	my $self = shift;

	$self->{hash} = {};
}

# ******************** Regular Instance Methods ********************

sub default_hash {
	my $self = shift;

	return $self->{default};
}

# is_dirty
#
# Read-only accessor for the object's dirty flag.  The dirty flag is set
# whenever a value is changed for the object's hash values.
#
sub is_dirty {
	my $self = shift;

	return $self->{dirty};
}

# dirty
#
# Read-write accessor for the dirty state of this object.
#
# params:
#  val - new dirty state
#
sub dirty {
	my $self = shift;
	my $val = shift;

	if (defined($val)) { $self->{dirty} = ($val && 1); }

	return $self->{dirty};
}

# merge
#
# Merges the values of a hash reference into this object.
#
sub merge {
	my $self = shift;
	
	merge_hashes($self, @_);
}

# rollback_value
#
# Rollback the value.   Works like the Tie STORE, but does not call the 
# change callback method (prevents an endless loop).
#
sub rollback_value {
	my $self = shift;
	my $key = shift;
	my $old_val = shift;

	$self->{hash}{$key} = $old_val;
}

# #################### SmartHash.pm ENDS ####################
1;
# ------------------------------------------------------------------------------
# 
#  $Log$
#  Revision 1.4  2004/04/09 06:18:26  kostya
#  Added quote escaping capabilities.
#
#  Revision 1.3  2003/10/15 01:07:00  kostya
#  documentation and license updates--everything is Artistic.
#
#  Revision 1.2  2003/10/14 22:49:32  kostya
#  Added the merge functions for combining settings.
#
#  Revision 1.1.1.1  2003/10/13 01:38:04  kostya
#  First import
#
# 
# ------------------------------------------------------------------------------

__END__