package CDB_Perl::Read;

use Carp qw(carp croak);

require CDB_Perl;
@ISA = qw(CDB_Perl);

use strict;
#use warnings;

sub new{
	my ($pack, $fname, $cache) = @_;

	if(!$pack or ref($pack)){
		croak "Invalid constructor parameter";
	}
	
	#by default cache is on
	if(!defined($cache)){
		$cache = 1;
	}
	if(!-e $fname){
		croak "$fname doesn't exist";
	}
	if(!-r $fname){
		croak "$fname isn't readable";
	}
	my $size = -s $fname;
	if($size < 2048){
		croak "invalid file ($size < 2048 bytes)";
	}
	my $self = bless {},$pack;
	$self->file_open($fname, '<');
	$self->{'cache'} = $cache;

	if($cache){
		$self->{'tables'} = {};
	}
	return $self;
}

sub get_value{
	if(wantarray){
		return &get_values(@_);
	}
	my ($self, $key) = @_;

	if(!defined($key)){
		croak "key must be defined";
	}

	my($h,$h0,$h1) = $self->hash($key);
	
	my $iter = {
		key  => $key,
		hash => $h,
		tnum => $h0,
		pos  => $h1,
	};

	$self->{'iter'} = $iter;
	return $self->get_next();
}

#compatibility with CDB_File
*multi_get = \&get_values;

sub get_values{
	my $self = shift;
	my $val = $self->get_value(@_);
	my @rsp = ();

	if(defined($val)){
		@rsp = ($val);
		while(defined($val = $self->get_next)){
			push @rsp,$val;
		}
	}
	return @rsp;
}

sub get_next{
	my $self = shift;

	my $iter = $self->{'iter'};
	if(not defined $iter){
		croak "Can't call get_next without having called get_value";
	}

	my $table = $self->get_table($iter->{'tnum'});
	my $len = @$table/2;
	if(!$len){
		return;
	}

	#now iterate
	for(;;){
		$iter->{'pos'} %= $len;
		my($hash,$pos) = (
			$table->[2*$iter->{'pos'}] ,
			$table->[2*++$iter->{pos} -1]
		);

		if($pos == 0){
			return;
		}
		if($hash == $iter->{'hash'}){
			my ($key, $val) = $self->entry($pos);
			if ($key eq $iter->{'key'}){
				return $val;
			}
		}
	}
}

sub get_table{
	my ($self, $n) = @_;

	if(!defined($n)){
		die "table number not defined";
	}

	my $cache = $self->{'cache'};

	if($cache && (exists($self->{'tables'}->{$n}))){
		return $self->{'tables'}->{$n};
	}

	#position in the header
	$self->seek(8*$n);
	my($pos,$len) = $self->read_long(2);

	$self->seek($pos);
	my @table = $self->read_long(2*$len);

	if($cache){
		$self->{'tables'}->{$n} = \@table;
	}

	return \@table;
}

sub entry{
	my ($self, $pos) = @_;

	if($pos < 2048){
		die "pos $pos is invalid";
	}

	$self->seek($pos);
	my ($klen, $vlen) = $self->read_long(2);
	return ((map{$self->read($_)}($klen,$vlen)), $klen, $vlen);
}

sub read{
	my ($self, $len) = @_;

	if(!defined($len)){
		die "Can't read an undefined number of characters.";
	}

	my $data;
	read($self->{'file'},$data,$len) or die "Error reading file";
	#pos not updated
	return $data;
}

sub read_long{
	my ($self, $len) = @_;
	
	if($len == 0){
		return ();
	}

	return unpack("V$len",$self->read($len*4));
}

###############################
# Tied hash interface follows #
###############################

sub TIEHASH{
	my $pack = shift;
	return $pack->new(@_);
}

sub FETCH{
	my ($self, $key) = @_;

	my $lkey = $self->{'tie'}->{'lastkey'};
	my $lval = $self->{'tie'}->{'lastvalue'};
	if(defined($lkey) && $key eq $lkey and defined($lval)){
		return $lval;
	}else{
		my $value = $self->get_value($key);
		return $value;
	}
}

sub STORE{
	croak "Can't store data on a readonly CDB";
}

sub EXISTS{
	my ($self, $key) = @_;

	return defined($self->get_value($key));
}

sub FIRSTKEY{
	my $self = shift;
	$self->{'tie'}->{'iterate_pos'} = 2048;
	#this could break on 'weird' CDB files
	$self->seek(0);
	$self->{'tie'}->{'max_pos'} = ($self->read_long(1))[0];

	delete($self->{'tie'}->{'lastkey'});
	delete($self->{'tie'}->{'lastvalue'});

	return $self->NEXTKEY;
}

sub NEXTKEY{
	my $self = shift;
	#don't need the previous key

	my $pos = \$self->{'tie'}->{'iterate_pos'};
	return unless $$pos < $self->{'tie'}->{'max_pos'};

	my ($key,$val, $klen, $vlen) = $self->entry($$pos);
	$$pos += $klen + $vlen + 8;

	$self->{'tie'}->{'lastkey'} = $key;
	$self->{'tie'}->{'lastvalue'} = $val;

	return $key;
}

sub DELETE{
	croak "Can't delete data on a readonly CDB";
}
*CLEAR = \&DELETE;

sub DESTROY{
	my $self = shift;
	close($self->{'file'}) or croak "Error closing CDB file.\n$!";
}

1;
