package TDS::Search;
# $Id: Search.pm,v 1.13 2000/12/03 13:37:54 tom Exp $
################################################################

=head1 NAME

TDS::Search - 検索担当

=head1 SYNOPSIS

 use TDS::Search;

 my $search = new TDS::Search;
 my $col = $search->Search;
 print $rend->AsHTML($col);

=cut

use strict;
use vars qw(@ISA
	    $Debug
	    $UseSignatureMethod $SignatureDir
	    $AllowAllSearch);
@ISA = qw(ObjectTemplate);

use ObjectTemplate;
use CGI::QueryString;
use CGI::Tools;
use SimpleDB::Hash;
use SigSearch;

use TDS::System;
use TDS::Status;
use TDS::Collection;
use TDS::DirInfo;

=head1 STATIC VARIABLES

 $UseSignatureMethod  シグネチャ法を使うか
 $SignatureDir        シグネチャ用ディレクトリ
 $AllowAllSearch      著者以外に全検索を許すか

=cut

$UseSignatureMethod = 0 unless defined $UseSignatureMethod;
$SignatureDir = qq(sig) unless defined $SignatureDir;
$AllowAllSearch = 0 unless defined $AllowAllSearch;
$Debug = 0;

=head1 MEMBER VARIABLES

 keyword       検索語
 object        検索対象('this', 'recent', 'year', 'all')

 sig_search    SigSearch object
 
 col           TDS::Colection オブジェクト
 status        TDS::Status オブジェクト

=cut

attributes qw(keyword object
	      col status sig_search);


=head1 MEMBER FUNCTIONS

=cut

################################################################
sub initialize($)
{
    my $self = shift;

    $self->status($TDS::Status);
    $self->col(new TDS::Collection);
    
    #$self->keyword(Escape(param('keyword'))) unless $self->keyword;
    $self->keyword($TDS::Status->keyword->escaped) unless $self->keyword;
    $self->object(param('search_object') || 'this');
    $self->sig_search(new SigSearch);
    $self->SUPER::initialize;
}

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

=head2 $s->Search;

検索する。
col オブジェクトを返す。

=cut

sub Search
{
    my $self = shift;

    my @files;
    my $use_sig = 0;
    if ($UseSignatureMethod &&          # シグネチャ法を使う場合
	!param('search_unuse_signature_method')){
	if ($self->ReadSignature){       # シグネチャファイルが読み込めれば
	    @files = $self->Find;        # 検索対象ファイルを絞る
	    $use_sig = 1;
	} else {
	    $use_sig = 0;
	}
    }
    unless ($use_sig){                 # シグネチャファイルが使えなければ
	@files = $self->PickupFiles;     # 検索対象ファイルを全列挙
    }
    if (param('search_sort') eq 'old'){
	@files = reverse @files;
    }

    $self->col->read_files(@files);    # 読み込む
    return $self->col;
}

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

=head2 $s->ReadSignature;

シグネチャファイルを読み込む。
対象全部読めれば真、
何か読めないファイルがあれば偽を返す。

=cut

sub ReadSignature
{
    my $self = shift;
    
    my $flg = 1;
    if ($self->object eq 'this'){                # 指定月
	my ($year, $month) = split(/\//, param('year_month'));
	$year ||= $self->status->start_time->year;
	$month ||= $self->status->start_time->month;
	my $part = $self->status->day_part;
	
	my $sig_file = sprintf("%s/%04d%02d.sig",
			       $SignatureDir, $year, $month);

	unless ($part){          	# 旬指定じゃなきゃそのまま
	    $flg = $self->sig_search->ReadSigFile($sig_file);
	    print "$sig_file: $flg<br>"
		if $self->status->debug;
	} else {                        # 旬指定だとちょっと手を加える
	    my %hash;
	    unless (-f $sig_file){
		$flg = 0;
	    } else {
		tie %hash, 'SimpleDB::Hash', $sig_file, 0, 1;
		print $hash{'dummy'};    # これやんないと tie 変数が読み込まれないよう
		my @table;
		for (1..9){  $table[$_] = $part eq 'a'; }
		for (10..19){$table[$_] = $part eq 'b'; }
		for (20..31){$table[$_] = $part eq 'c'; }
		
		for (keys %hash){
		    my $file = $_;
		    my ($day) = m!/(\d{2})\.tdf$!;
		    print "[$day,$table[$day]]  "
			if $Debug;
		    unless ($table[$day]){
			delete $hash{$file};
		    }
		}
		push(@{$self->sig_search->objects}, \%hash);
	    }
	}
    } else {                # 指定月以外
	my @ym = $self->col->GetMonthDirs;
	my @dirs;
	if ($self->object eq 'all'){                # すべて
	    @dirs = @ym;
	} elsif ($self->object eq 'year'){          # 最近一年
	    my $num = min($#ym, 12);
	    @dirs = @ym[0..$num];
	} elsif ($self->object eq 'recent'){        # 最近三ヶ月
	    my $num = min($#ym, 3);
	    @dirs = @ym[0..$num];
	} else {
	    die "illegal searc_object: ", $self->object;
	}
	# pickup したディレクトリのシグネチャファイルを探す
	for (@dirs){
	    my ($year, $month) = @$_;
	    my $sig_file = sprintf("%s/%04d%02d.sig",
				   $SignatureDir,
				   $year, $month);
	    my $f = $self->sig_search->ReadSigFile($sig_file);
	    unless ($f){
		print "no signature file: $sig_file\n"
		    if $Debug;
	    }
	    $flg &&= $f;
	}
    }
    return $flg;
}

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

=head2 $s->Find;

検索語にマッチする可能性のあるファイルを見つける。

=cut

sub Find
{
    my $self = shift;

    my @files;
    for (reverse sort $self->sig_search->Find($self->keyword)){
	my ($y, $m, $d) = m!(\d{4})/(\d{2})/(\d{2})\.tdf$!;
	push(@files, [$_, $y, $m, $d]);
	print "$_, "
	    if $Debug;
    }
    return @files;
}

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

=head2 $s->PickupFiles;

検索対象のファイルを抽出する。
シグネチャ法が使えない場合利用される。

=cut

sub PickupFiles ($)
{
    my $self = shift;

    my @files;
    my $object = $self->object;
    if ($object eq 'this'){                # 指定月
	@files = reverse $self->col->pickup_files($self->status->year,
						  $self->status->month);

	if (0){    # no use
	    my $day_part = $self->status->day_part;
	    
	    if ($day_part eq ''){           # 月別
		@files = $self->col->pickup_files($self->status->year,
						  $self->status->month);
	    } elsif ($day_part =~ /[abc]/){ # 旬別
		my ($min_day, $max_day) =
		    ($day_part eq 'a') ? (1, 9) :
			($day_part eq 'b') ? (10, 19) :
			    (20, 31);
		@files = $self->col->pickup_files($self->status->year,
						  $self->status->month,
						  $min_day, $max_day);
	    }
	}
    } elsif ($object eq 'recent' || $object eq 'year'){
	my $date = new DateTime::Date;
	$date->Set($self->status->year, $self->status->month);
	my %hash = ('recent'=>3, 'year'=>12);
	my $num = $hash{$object};
	for (1..$num){
	    push(@files, reverse $self->col->pickup_files($date->year,
							  $date->month));
	    $date -= '1M';
	}
    } elsif ($object eq 'all'){                # すべて
	die "not allowed searching all diary to non-author"
	    if  !$self->status->is_author && !$AllowAllSearch;

	@files = $self->col->pickup_recent($self->status->start_time->year,
					   $self->status->start_time->month, 
					   $self->status->start_time->day);
    } else {
	die "illegal searc_object: ", $self->object;
    }
    return @files;
}

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

=head2 $s->UpdateSignatureFiles;

シグネチャファイルを更新する

=cut

sub UpdateSignatureFiles($;$)
{
    my ($self, $current_only) = @_;

    my $diary_dir = GetDiaryDir();
    my @dirs;
    if ($current_only){
#	@dirs = ([$self->status->year, $self->status->month]);
	@dirs = ([$self->status->start_time->year,
		  $self->status->start_time->month]);
    } else {
	@dirs = $self->col->GetMonthDirs;
    }
    for (@dirs){
	my ($year, $month) = @$_;
	my $sig_file = sprintf("%s/%04d%02d.sig",
			       $SignatureDir,
			       $year, $month);
	my @files;
	my $dir = sprintf("%s/%04d/%02d", $diary_dir, $year, $month);
	unless (opendir(DIR, $dir)){
	    warn "open failed dir: $dir";
	} else {
	    while ($_ = readdir(DIR)){
		next unless /^(\d{2})\.tdf$/;
		push(@files, sprintf("%s/%04d/%02d/%02d.tdf",
				     $diary_dir, $year, $month, $1));
	    }
	    closedir DIR;
	    $self->sig_search->UpdateSigFile($sig_file, @files);
	}
    }
}
################################################################
sub min
{
    my ($a, $b) = @_;
    return ($a<$b) ? $a : $b;
}

1;

