package Hash;

# Hash.pm
# An example demonstrating how a hash works

# This hash is not designed for production use and is not
# optimized. The Perl builtin hash should be used instead.

# Number of slots should preferably be prime.

use strict;

sub new {
	my $arg0 = shift;
	my $cls = ref($arg0) || $arg0;
	my $this = bless {}, $cls;
	$this->initialize(@_);
	return $this;
}

# Store property values
sub initialize {
	my $this = shift;
	my %params = @_; 
	foreach (keys %params) {
		$this->{$_} = $params{$_};
	}
	
	# Hash defaults
	!exists $this->{'NUMSLOTS'} and $this->{'NUMSLOTS'} = 17;
}

# Add a (key, value) pair to the hash
# i.e. $hash{$key} = $value;
sub add {
	my $this = shift;
	my ($key, $value) = @_;

	# First call the hash function to find where it should go
	my $slot = $this->hashFunction($key);

	if (my $cur = $this->search($key)) {
		# The specified key exists, replace current
		# value with the new one
		$cur->{'value'} = $value;
	} else {
		# Prepend to the chain
		my $next = $this->{'slots'}[$slot];
		$this->{'slots'}[$slot] = {
			'key' => $key,
			'value' => $value,
			'next' => $next,
			'prev' => undef,	# must be head
		};
		$next->{'prev'} = $this->{'slots'}[$slot];
	}
}

# Remove a (key, value) pair from the hash
# i.e. delete $hash{$key}
sub remove {
	my $this = shift;
	my $key = shift;

	# First call the hash function to find where it should go
	my $slot = $this->hashFunction($key);
	if (my $toDelete = $this->search($key)) {
		my $prev = $toDelete->{'prev'};
		my $newNext = $toDelete->{'next'};
		if (defined $prev) {
			$prev->{'next'} = $newNext;
		} else {
			$this->{'slots'}[$slot] = $newNext;
		}
		$newNext->{'prev'} = $prev;
	}
}

# Search for a specific key in the hash; Internal use only
sub search {
	my $this = shift;
	my $key = shift;

	# First call the hash function to find where it should go
	my $slot = $this->hashFunction($key);
	
	my $count = 1;
	my $totalNum = $this->getAllKeys();
	
	my $ptr = $this->{'slots'}[$slot];
	while (defined $ptr) {
		if ($ptr->{'key'} eq $key) {
			$this->{'DEBUG'} and print STDERR "Number of comparisons: $count/$totalNum.\n";
			return $ptr;
		} else {	# advance
			$ptr = $ptr->{'next'};
			++$count;
		}
	}
	$this->{'DEBUG'} and print STDERR "Number of comparisons: $count/$totalNum.\n";
	return undef;
}

# Get value for a specific key
# i.e. $hash{$key}
sub get {
	my $this = shift;
	my $key = shift;

	# First call the hash function to find where it should go
	my $slot = $this->hashFunction($key);
	if (my $obj = $this->search($key)) {
		return $obj->{'value'};
	} else {
		return undef;
	}
}

# Tests if a specific key exists,
# because get() cannot handle the case when the value is undef 
# i.e. exists $hash{$key}
sub exists {
	my $this = shift;
	my $key = shift;

	return ($this->search($key)?1:undef);
}

# ======================================================================
# These three functions let you get a list of keys, values or both

# keys %hash
sub getAllKeys {
	traverse($_[0], 'key');
}

# values %hash
sub getAllValues {
	traverse($_[0], 'value');
}

# @list = %hash
sub getAllKeyValuePairs {
	traverse($_[0], 'value', 'key');
}

# Traversing the hash returning values of certain fields
# Internal Use only
sub traverse {
	my $this = shift;
	my @fields = @_;
	my @retval = ();
	
	foreach my $slot (0..$this->{'NUMSLOTS'}-1) {
		my $ptr = $this->{'slots'}[$slot];
		my @tmpList = ();
		while (defined $ptr) {
			push @tmpList, @$ptr{@fields};
			$ptr = $ptr->{'next'};
		}
		push @retval, reverse(@tmpList);
	}
	return @retval;
}

# Hash function
# Generates mapping (Key -> hash slot index)
sub hashFunction {
	my $this = shift;
	my $key = shift; 
	my $tmp = 0;
	for(my $i = 0; $i<length($key); $i++) {
		$tmp += (ord(substr($key, $i, 1)) * ($i*2+11));
	}
	return $tmp % $this->{'NUMSLOTS'};
}

# Total number of slots in this hash
sub getTotalSlots {
	my $this = shift;
	return $this->{'NUMSLOTS'};
}

# Total number of OCCUPIED slots in this hash
sub getOccupiedSlots {
	my $this = shift;
	my $count = 0;
	for (0..$this->{'NUMSLOTS'}-1) {
		$this->{'slots'}[$_] and ++$count;
	}
	return $count;
}

# Return a similar string as 
# scalar %hash
sub getUtilization {
	my $this = shift;
	return $this->getOccupiedSlots() . '/' . $this->getTotalSlots();
}

1;
