require HashObject ;
require bb_misc ;


# HISTORY :
# Creation BB Dec 2 1998


#**************************************************************************#
package AbstractLineFields ;
#**************************************************************************#
	@ISA = qw( HashObject );

	@fields_names = ('fields') ;

	eval( ${HashObject'new_SubClass_String} ) ;

#--------------------------------------------------------------------------#

sub readFromCurrentLine {

	die "Subclass responsibility";
}

#--------------------------------------------------------------------------#

sub writeLine {

	die "Subclass responsibility";
}

#--------------------------------------------------------------------------#

sub size {
	my $self = shift ;
	
	my @fields = @{$self->{'fields'}} ;
	
	if( $[ == 1 )
	{	return $#fields ;}
	else #assume $[ == 0
	{	return $#fields + 1;} 
}
#--------------------------------------------------------------------------#
#answer an integer (position) of a match data
#answer undef if not found

sub indexOf
{
	my $self = shift ;
	my $what = shift ;
	
	my @fields = @{$self->{'fields'}} ;
	
	for( $i = 0 ; $i <= $#fields ; $i++)
	{
		my $f = $fields[$i] ;
		defined $f || next ;
		
		if( $what eq $f){ return $i ;}
		#Bug ! 
		#if( $what == $f){ return $i ;}
	}
	
	return undef ;
}
#--------------------------------------------------------------------------#
#Replace a match data
#answer undef if not found

sub replaceData
{
	my $self = shift ;
	my $what = shift ;
	my $new = shift ;
	
	defined $what || die ;
	
	my $fields = $self->{'fields'} ;
	
	for( $i = 0 ; $i < main'size_of_array_ref($fields) ; $i++)
	{
		my $f = $fields->[$i] ;		
		if( $what eq $f){ $fields->[$i] = $new ; return $i ;}

	}
	
	return undef ;
}
#--------------------------------------------------------------------------#

sub valueAtIndex
{
	my $self = shift ;
	my $index = shift;
	my @fields = @{$self->{'fields'}} ;
	
	defined @fields || die "Assertion failed";
	
	($index <= $#fields && $index>= 0 )|| die "array access error";
	
	return $fields[ $index] ;
}

			

#**************************************************************************#
package AbstractTable ;

# The first line contains the labels
#**************************************************************************#
	@ISA = qw( HashObject );

	@fields_names = (
		'lines',  #	\@array of AbstractLineFields
		'labels', # AbstractLineFields first line
		
		'quick_access_hash', #A hash table to speed up search for one key
		#it contains key => $array_ref , where $array_ref is used to store all the
		#entries that match the key in the table 
		'quick_key'
		) ;

	eval( ${HashObject'new_SubClass_String} ) ;


#--------------------------------------------------------------------------#
sub readFromInput
{

	my $self = shift ;

	$self->readFromInputUsingQuickKey( undef) ;
	


}	
#--------------------------------------------------------------------------#
sub readFromInputUsingQuickKey
{

	die "Subclass responsibility";


}
#--------------------------------------------------------------------------#
#Return the class parser for input each line
sub get_Line_Class
{
	die "Subclass responsibility";
	#Implementation example:
	#return bless {}, CSV_line ;
}

#--------------------------------------------------------------------------#
sub readFromInputFileUsingQuickKey
{
	my $class = shift () ;
	my $filename = shift ;
	defined $filename || die "Assertion failed";
	my $quickKey = shift ;
	
	local *STDIN ;  
	open( STDIN, "$filename" ) || die "Can't open $filename" ;
	
	my $newInstance = $class->readFromInputUsingQuickKey($quickKey) ;
	
	close (STDIN) ;	

	return $newInstance ;	

}

#--------------------------------------------------------------------------#
sub readFromInputUsingQuickKey
{

	my $class = shift ;
	my $self = new($class) ;
	my $quick_key = shift;

	
	my $n_lines = 0 ;
	my $index_of_quick_key;
	
	$_ =<STDIN> ;
	
	defined $_ || return undef ;
	
	my $line_Class = $class->get_Line_Class() ;
	defined $line_Class || die "get_Line_Class() is undefined in $class";
	
	my $first_line =  $line_Class->readFromCurrentLine();
	my $size = $first_line->size();
	$n_lines++;
	
	
	$index_of_quick_key = $self->init_quick_access_hash( $quick_key , $first_line) ;
	
	while(<STDIN>)
	{
		$n_lines ++ ;
		my $new_line = $line_Class->readFromCurrentLine();
		if( !defined $new_line)
		{
			die "Something wrong at line $n_lines";
		}
		
		if( $new_line->size() != $size )
		{
			die sprintf("The number of fields at line %d is %d and not %d\n",$n_lines, $new_line->size(), $size);
		}
		
		push(@lines, $new_line);
		
		#Hash table optimization
		if( defined $index_of_quick_key )
		{
			my $value = $new_line->valueAtIndex($index_of_quick_key) ;
			if( defined $value)
			{
				if( ! defined $self->{'quick_access_hash'}->{$value} )
				{
					my @array = () ;
					$self->{'quick_access_hash'}->{$value} = \@array ;
				}
				push( @{$self->{'quick_access_hash'}->{$value}}, $new_line);
			
			}
			 
		
		}
	}
		
	$self->{'lines'} = \@lines ;
	$self->{'labels'} = $first_line ;
	
	$self ;
	

}	


#--------------------------------------------------------------------------#
sub init_quick_access_hash
{

	my $self = shift ;
	my $quick_key = shift ;
	my $first_line = shift ;
	
	if( defined $quick_key )
	{
		my $index_of_quick_key = $first_line->indexOf( $quick_key);
		if( !defined $index_of_quick_key)
		{
			warn "Fast key $quick_key not found in the fist line of the table. No speed up!";
		}
		$self->{'quick_key'} = $quick_key;
		$self->{'quick_access_hash'} = {};
		
		return $index_of_quick_key
	} else 
	{
		return undef ;
	}	
}	
#--------------------------------------------------------------------------#
sub size {
	my $self = shift ;
	
	my @fields = @{$self->{'lines'}} ;
	
	if( $[ == 1 )
	{	return $#fields ;}
	else #assume $[ == 0
	{	return $#fields + 1;} 
}


#--------------------------------------------------------------------------#
sub number_of_fields {
	my $self = shift ;
	
	($self->{'labels'})->size();
	
}	

#--------------------------------------------------------------------------#

sub dimensions {
	my $self = shift ;

	return ( $self->number_of_fields(), $self->size());
}
#--------------------------------------------------------------------------#
# retrieve one row of the AbstractTable as an array ref
# first argument : identification of the column label to be used for matching
#	-> which columm?
# second argument : identification of the row by searching in the selected column
#	an exact string match
#
# answer undef if not found

sub get_row
{
	my $self = shift ;
	
	my $column_label = shift ;
	my $row_value = shift ;
	
	defined $self || die ;
	defined $column_label || die ;
	defined $row_value || die ;
	
	my $index_label = $self->indexOfLabel($column_label) ;
	
	if( !defined $index_label)
	{
		warn( "label $column_label not found in the AbstractTable");
		return  undef;
	}
	
	if( defined $self->{'quick_key'} && ($self->{'quick_key'} eq $column_label) )
	{ #Fast mode
		$array_ref = $self->{'quick_access_hash'}->{$row_value} ;
		defined $array_ref || return undef ;
		my $line = $array_ref->[$[] ; #return the first element of array
		return $line->{'fields'} ; 
	
	} else #search thoroughly
	{

#Bug in Perl 5.003	: foreach does not work here, $line was undefined
#		foreach $line (@{$self->lines})
		my @temp_array = @{$self->lines} ;
		for( $i = $[ ; $i <= $#temp_array ; $i++)
		{
			$line = $temp_array[ $i ];
			defined $line || die "Assertion failed";

			#exact string match
			if( $line->valueAtIndex($index_label) eq $row_value)
			{
				return $line->{'fields'} ;
			}
		}
	}
	
	return undef ;

}	
#--------------------------------------------------------------------------#
# retrieve one or more row of the AbstractTable as an array of array ref
# first argument : identification of the column label to be used for matching
#	-> which columms?
# second argument : identification of the row by searching in the selected column
#	an exact string match
#
# answer undef if not found

sub get_mutiple_rows
{
	my $self = shift ;
	
	my $column_label = shift ;
	my $row_value = shift ;
	
	
	defined $self || die ;
	defined $column_label || die ;
	defined $row_value || die ;
	
	my @rows = () ;
	my $index_label = $self->indexOfLabel($column_label) ;
	
	if( !defined $index_label)
	{
		warn( "label $column_label not found in the AbstractTable");
		return  \@rows;
	}
	

	if( defined $self->{'quick_key'} && ($self->{'quick_key'} eq $column_label) )
	{ #Fast mode
		my @array = @{$self->{'quick_access_hash'}->{$row_value}} ;
		defined @array || return undef ;
		@rows = map { $_->{'fields'}} @array; 
	}
	else
	{
		#search thoroughly
	
		foreach $line (@{$self->lines})
		{
			defined $line || die "Assertion failed";
		
			#excat string match
			if( $line->valueAtIndex($index_label) eq $row_value)
			{
				push(@rows, $line->{'fields'}) ;
			}
		}
	}	
	
	return \@rows;
}

#--------------------------------------------------------------------------#
# retrieve one row of the AbstractTable as an hash ref
# first argument : identification of the column label to be used for matching
#	-> which columm?
# second argument : identification of the row by searching in the selected column
#	an exact string match
#
# answer undef if not found

sub get_multi_Properties
{	
	my $self = shift ;
	
	my $column_label = shift ;
	my $row_value = shift ;
	
	defined $self || die ;
	defined $column_label || die ;
	defined $row_value || die ;
	
	my $row_array_ref = $self->get_mutiple_rows($column_label, $row_value);
	
	
	defined $row_array_ref || return undef ;
	main'is_array_ref($row_array_ref) || die "assertion failed: type error" ;
	
	my @labels = @{$self->{'labels'}->{'fields'}} ;
	my @return_array = ();
	foreach $row_ref (@$row_array_ref)
	{
		my  @row = @$row_ref;
		defined $row_ref || die "assertion failed" ;
		main'is_array_ref($row_ref) || die "assertion failed: type error" ;
		if($#labels != $#row){
			die "Assertion failed: size mismatch";
		}
	
		#Build the hash AbstractTable
		my %hash = () ;
		for( $i = $[ ; $i <= $#row ; $i++)
		{
			$hash{$labels[$i]} = $row[$i];
		}
		push(@return_array, \%hash);
	}
	
	return \@return_array;
		


}

#--------------------------------------------------------------------------#
# retrieve one or more rows of the AbstractTable as array_ref of  hash ref
# first argument : identification of the column label to be used for matching
#	-> which columm?
# second argument : identification of the row by searching in the selected column
#	an exact string match
#
# answer undef if not found

sub getProperties
{	
	my $self = shift ;
	
	my $column_label = shift ;
	my $row_value = shift ;
	
	defined $self || die ;
	defined $column_label || die ;
	defined $row_value || die ;
	
	my $row_ref = $self->get_row($column_label, $row_value);
	my  @row = @$row_ref;
	
	defined $row_ref || return undef ;
	main'is_array_ref($row_ref) || die "assertion failed: type error" ;
	
	my @labels = @{$self->{'labels'}->{'fields'}} ;
	
	if($#labels != $#row){
		die "Assertion failed: size mismatch";
	}
	
	#Build the hash AbstractTable
	my %hash = () ;
	for( $i = $[ ; $i <= $#row ; $i++)
	{
		$hash{$labels[$i]} = $row[$i];
	}
	
	return \%hash;
		


}


#--------------------------------------------------------------------------#
#answer an integer, which is the position of the argument label in the AbstractTable
sub indexOfLabel
{
	my $self = shift ;
	my $label = shift ;
	
	defined $label || die ;
	defined $self->{'labels'} || die "Undefined field: labels";
	
	return ($self->{'labels'})->indexOf($label);
	
}


#--------------------------------------------------------------------------#
sub renameLabel
{
	my $self = shift ;
	my $label = shift ;
	my $new_label = shift ;
	
	defined $label || die ;
	defined $new_label || die ;
	
	return ($self->{'labels'})->replaceData($label, $new_label);
	
}	
	

#--------------------------------------------------------------------------#


1;

	
