package SigSearch;
# $Id: SigSearch.pm,v 1.1.1.1 2000/01/25 17:00:55 tom Exp $
################################################################

=head1 NAME

SigSearch - シグネチャ法による簡易検索

=head1 SYNOPSIS

 use SigSearch;

 yet..

=cut

use strict;
use vars qw(@ISA
	    $SIGBITS $SIGBYTES);

use ObjectTemplate;
@ISA = qw(ObjectTemplate);

use SimpleDB::Hash;

=head1 STATIC VARIABLES

 $SIGBYTES      シグネチャのバイト長
 $SIGBITS       同ビット換算

=cut

$SIGBYTES = 1024;
$SIGBITS = $SIGBYTES * 6;


=head1 MEMBER VARIABLES

 @objects         対象ファイルシグネチャ情報配列

=cut

attributes qw(objects);


=head1 MEMBER FUNCTIONS

=cut

################################################################
sub initialize ($)
{
    my $self = shift;
    
    $self->objects([]);
    $self->SUPER::initialize;
}
################################################################

=head2 $s->CreateSigFile(@files);

指定したファイル群からシグネチャファイルを作成する

=cut

sub CreateSigFile($$@)
{
    my ($self, $sig_file, @files) = @_;

    open(SIGS, ">$sig_file") || die $sig_file;
    for (@files){
	printf(SIGS "%s\t%s\n", $_, file_sig($_));
    }
    close SIGS;
}


=head2 $s->UpdateSigFile(@files);

指定したファイル群からシグネチャファイルを更新する

=cut

sub UpdateSigFile($$@)
{
    my ($self, $sig_file, @files) = @_;

    my $sig_file_lm = (stat($sig_file))[9];

    my %hash;
    tie %hash, 'SimpleDB::Hash', $sig_file;
    print $hash{'dummy'};     # これやんないと変になる

    print "update: $sig_file:";
    for (@files){
	my $lm = (stat($_))[9];
	if ($lm > $sig_file_lm){    # sig_file より新しければ更新
	    print "$_, ";
	    $hash{$_} = file_sig($_);
	}
    }
    print "\n";

}

################################################################

=head2 $s->ReadSigFile;

sig_file からシグネチャ情報を読み込む

読み込みに成功すると真を返す

=cut

sub ReadSigFile($$)
{
    my ($self, $sig_file) = @_;

    return 0 unless -f $sig_file;
    
    my %db;
    tie %db, 'SimpleDB::Hash', $sig_file, 1;
#    print $db{"d:/tom/diary/1999/10/31.tdf"};
    print $db{'dummy'};    # これやんないと tie 変数が読み込まれないよう
    push(@{$self->objects}, \%db);
    return 1;
}


=head2 $s->ReadSigFiles(@sig_files);

@sig_files からシグネチャ情報を読み込む

=cut

sub ReadSigFiles($@)
{
    my ($self, @sig_files) = @_;
    my $flg;
    for (@sig_files){
	$flg &&= $self->ReadSigFile($_);
    }
    return $flg;
}

=head2 $s->Find($keyword);

対象ファイル群から $keyword に合致するファイル群を返す

=cut

sub Find($$)
{
    my ($self, $keyword) = @_;

    my $key_sig = calc_sig($keyword);
#    print "key_sig: \n$key_sig\n";
    my @find_files;
    
    for (@{$self->objects}){
	my $hash = $_;
	my ($file, $sig);
#	while (($file, $sig) = each(%$hash)){
	for (sort keys %$hash){
#	    print "$file, $sig<br>";
	    if (match_sig($hash->{$_}, $key_sig)){
		push(@find_files, $_);
	    }
	}
    }
    return @find_files;
}

################################################################
# static functions
# ハッシュ値を計算して返す
sub hashpjw
{
    my $str = shift;

    my ($h, $g);
    
    for (0..length($str)-1){
	my $c = unpack("c", substr($str, $_, 1));
	$h = ($h<<4) + $c;
	if ($g = ($h & 0xf0000000)){
	    $h = $h ^ ($g >> 24);
	    $h = $h ^ $g;
	}
    }
    return $h;
}

# シグネチャを計算して返す
sub calc_sig
{
    my $str = shift;
    
    my @sig;
    my $len = length($str);
    # 初期化
    for (0..$SIGBYTES-1){
	$sig[$_] = 0x40;
    }
    # シグネチャ計算
    for(0..$len-3){
	my $v = hashpjw(substr($str, $_, 3)) % $SIGBITS;
	$sig[int($v/6)] |= (1 << $v%6);
    }
    # EUC エスケープの変換
    my $rtn;
    for (0..$SIGBYTES-1){
	if ($sig[$_] == 0x7f){
	    $sig[$_] = 0x3f;
	}
	$rtn .= chr($sig[$_]);
    }
    # 文字列として返す
    return $rtn;
}
################################################################
# 与えられたファイルのシグネチャを返す
sub file_sig ($)
{
    my $fname = shift;

    open(F, $fname) || die $fname;
    my $line = join('', <F>);
    close F;
    return calc_sig($line);
}
################################################################
# 二つのシグネチャを比較する
sub match_sig ($$)
{
    my ($textsig, $patsig) = @_;

    for (0..$SIGBYTES-1){
	my $pat_char = unpack("c", substr($patsig, $_, 1)) & 0x3f;
	my $text_char = unpack("c", substr($textsig, $_, 1));
	if (($text_char & $pat_char) != $pat_char){
	    return 0;
	}
    }
    return 1;
}
    
1;

