#! /usr/bin/perl -w

###########################################################################
#
# phindgen.pl -- produce Phind index files for a GSDL collection.
#
# Copyright (C) 2000 Gordon W. Paynter
#
# A component of the Greenstone digital library software
# from the New Zealand Digital Library Project at the 
# University of Waikato, New Zealand.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
###########################################################################


# This program generates phind phrase browsing indexes for a Greenstone
# digital library collection.  
#
# The GML files that have been imported to the archives directory are 
# read, and then the phind indexes are created in the phindex directory.
#
# This version od Phind uses suffix arrays to extract phrases.


BEGIN {
    die "GSDLHOME not set\n" unless defined $ENV{'GSDLHOME'};
    die "GSDLOS not set\n" unless defined $ENV{'GSDLOS'};
    unshift (@INC, "$ENV{'GSDLHOME'}/perllib");
    unshift (@INC, "$ENV{'GSDLHOME'}/perllib/plugins");
}


use strict;
use colcfg;
use util;
use parsargv;
use plugin;
use phproc;


sub print_usage {
    print STDERR "\n  usage: $0 [options] collection-name\n\n";
    print STDERR "  options:\n";
    print STDERR "   -verbosity number      0=none, 3=lots\n";
    print STDERR "   -archivedir directory  Where the converted material ends up\n";
    print STDERR "   -phindexdir directory  Where to place the phind indexes\n";
    print STDERR "   -languages names       The languages to consider in the browser\n";
    print STDERR "   -maxdocs number        Maximum number of documents to process\n";
    print STDERR "   -untidy                Leave working files in phindex directory\n";
    print STDERR "\n";
}


# Define delimiter symbols - this should be abstracted out someplace
my $colstart = "COLLECTIONSTART";
my $colend   = "COLLECTIONEND";
my $doclimit = "DOCUMENTLIMIT";
my $senlimit = "SENTENCELIMIT";
my @delimiters = ($colstart, $colend, $doclimit, $senlimit);

&main ();


sub main {
    
    my ($verbosity, $archivedir, $phindexdir, 
	$phind, $language, $maxdocs, $untidy,
	$collection, $configfilename, $collectcfg);
    
    # Parse command-line arguments and get the collection name
    if (!parsargv::parse(\@ARGV, 
			 'verbosity/\d+/1', \$verbosity,
			 'archivedir/.*/', \$archivedir,
			 'phindexdir/.*/', \$phindexdir,
			 'languages/\w+/english', \$language,
			 'maxdocs/^\-?\d+/-1', \$maxdocs,
			 'untidy', \$untidy )) {
	&print_usage();
	die "\n";
    }
    $collection = &util::use_collection(@ARGV);
    if ($collection eq "") {
	&print_usage();
	die "\n";
    }

    # Read the collection configuration file
    $configfilename = &util::filename_cat ($ENV{'GSDLCOLLECTDIR'}, "etc", "collect.cfg");
    if (-e $configfilename) {
	$collectcfg = &colcfg::read_collect_cfg ($configfilename);
	if (defined $collectcfg->{'archivedir'} && $archivedir eq "") {
	    $archivedir = $collectcfg->{'archivedir'};
	}
	if (defined $collectcfg->{'phindexdir'} && $phindexdir eq "") {
	    $phindexdir = $collectcfg->{'phindexdir'};
	}
	if (defined $collectcfg->{'phind'}) {
	    $phind = $collectcfg->{'phind'};
	}
    } else {
	die "Couldn't find the configuration file $configfilename\n";
    }

    # Calculate default archive and phindex directories
    if ($archivedir eq "") {
	$archivedir = &util::filename_cat($ENV{'GSDLCOLLECTDIR'}, "archives");
    }
    if ($phindexdir eq "") {
	$phindexdir = &util::filename_cat($ENV{'GSDLCOLLECTDIR'}, "phindex");
    }

    # Make sure theuser has in fact requested phind indexes
    if (!defined($phind)) {
	print "No phind information in $configfilename\n";
	exit;
    }


    # Construct phind indexes
    my $mode = 1;
    my $symbol_limit = 40000000;
    my ($command, $status);
    
    # Make a clean phindex directory
    if (-e "$phindexdir") {
	&util::rm_r("$phindexdir");
    }
    &util::mk_dir("$phindexdir");

    # Read the archives directory and build the clauses file
    print "\nReading archive directory\n" if $verbosity;
    &build_clauses($archivedir, $phindexdir, $language, $verbosity, $maxdocs);

    # Generate the vocabulary, symbol statistics, and numbers file
    # from the clauses file
    print "\nExtracting vocabulary and statistics\n" if $verbosity;
    &extract_vocabulary($phindexdir, $language, $verbosity);

    # Use the suffix program to generate the phindex/phrases file
    $command = "suffix $phindexdir $symbol_limit $mode";
    print "\nExecuting: $command\n" if $verbosity;
    $status = system($command);
    if ($status != 0) {
	print STDERR "phindgen.pl - Error executing $command: $!\n";
	exit($status);
    }

    # Create the phrase file and put phrase numbers in phindex/phrases
    print "\nSorting and Renumbering phrases for input to mgpp\n" if $verbosity;
    &renumber_phrases("$phindexdir", $verbosity);
    
    # Create the mg phrase database
    my $mgpp = &util::filename_cat($ENV{'GSDLHOME'}, "src", "mgpp");
    my $mg_passes = &util::filename_cat($mgpp, "text", "mg_passes");
    my $mg_compression_dict = &util::filename_cat($mgpp, "text", "mg_compression_dict");
    my $mg_input = &util::filename_cat($phindexdir, "mg-p.txt");

    $command = "$mg_passes -d $phindexdir -f phrase -T1 $mg_input";
    print "\nExecuting: $command\n" if $verbosity;
    $status = system($command);
    if ($status != 0) {
	print STDERR "phindgen.pl - Error executing $command: $!\n";
	exit($status);
    }

    $command = "$mg_compression_dict -d $phindexdir -f phrase";
    print "\nExecuting: $command\n" if $verbosity;
    $status = system($command);
    if ($status != 0) {
	print STDERR "phindgen.pl - Error executing $command: $!\n";
	exit($status);
    }

    $command = "$mg_passes -d $phindexdir -f phrase -T2 $mg_input";
    print "\nExecuting: $command\n" if $verbosity;
    $status = system($command);
    if ($status != 0) {
	print STDERR "phindgen.pl - Error executing $command: $!\n";
	exit($status);
    }

    # Tidy up stray files
    if (!$untidy) {
	print "\nCleaning up\n" if $verbosity;
	&util::rm("$phindexdir/clauses", "$phindexdir/clauses.numbers", 
		  "$phindexdir/clauses.vocab", "$phindexdir/clauses.stats", 
		  "$phindexdir/phrases", "$phindexdir/mg-p.txt");
	my $outfile = 1;
	while (-e "$phindexdir/outPhrase.$outfile") {
	    &util::rm("$phindexdir/outPhrase.$outfile");
	    $outfile++;
	}
    }
}







# Get the text for the collection.  
# Save the document names and identifiers as we go.

sub build_clauses {
    my ($archive_dir, $phindex_dir, $language, $verbosity, $maxdocs) = @_;

    # create a "pluginfo" for ArcPlug and RecPlug
    my $plugins = [["GMLPlug"], ["ArcPlug"]];
    my $pluginfo = &plugin::load_plugins ($plugins, $verbosity);
    die "No plugins were loaded." if (scalar(@$pluginfo) == 0);
    
    
    # create a phind document processor object to process the documents
    my $processor = new phproc ($archive_dir, $phindex_dir, $language,
				$doclimit, $verbosity, "STDOUT");

    # process the archives directory
    &plugin::read ($pluginfo, $archive_dir, "", {}, $processor, $maxdocs);
}



# Generate the vocabulary, symbol statistics, and numbers file from the
# clauses file.  This is legacy code, so is a bit messy and probably wont
# run under windows.

sub extract_vocabulary {
    my ($phindex_dir, $language, $verbosity) = @_;

    my ($w, $l, $line, $word);

    my ($first_delimiter, $last_delimiter,
	$first_stopword, $last_stopword,
	$first_extractword, $last_extractword,
	$first_contentword, $last_contentword,
	$phrasedelimiter);

    my ($use_thesaurus, %thesaurus, $first_thesaurusword, $last_thesaurusword);


    my %symbol;
    my (%freq);

    print "Calculating vocabulary\n" if ($verbosity > 1);

    # Read and store the stopwords
    my $words = `find $ENV{'GSDLHOME'}/etc/phind/$language -name "*.sw" | xargs cat`;
    my %stopwords;
    foreach my $w (split(/\s+/, $words)) {
	$l = lc($w);
	$stopwords{$l} = $w;
    }
    
    # Read and store the thesaurus terms
    $use_thesaurus = 0;
    my $lex_file = &util::filename_cat("$ENV{'GSDLHOME'}", "etc", "phind", 
				       "$language", "agrovoc.lex");
    if (-e "$lex_file") {
	open(TH, "<$lex_file");
	while(<TH>) {
	    s/^\d+ //;
	    s/\(.*\)//;
	    foreach my $w (split(/\s+/, $_)) {
		$thesaurus{lc($w)} = $w;
	    }
	}
	close TH;
	$use_thesaurus = 1;
    }

    # Read words in the text and count occurences
    open(TXT, "<$phindex_dir/clauses");
    my @words;
    
    while(<TXT>) {
	$line = $_;
	next unless ($line =~ /./);
	
	@words = split(/\s+/, $line);
	foreach $w (@words) {
	    $l = lc($w);
	    $w = $l if ((defined $stopwords{$l}) || (defined $thesaurus{$l}));
	    $freq{$w}++;
	}
	$freq{$senlimit}++;
    }
    
    # Calculate the "best" form of each word
    my (%bestform, %totalfreq, %bestfreq);

    foreach $w (sort (keys %freq)) {
	$l = lc($w);
	
	# totalfreq is the number of times a term appears in any form
	$totalfreq{$l} += $freq{$w};
	
	if (defined $stopwords{$l}) {
	    $bestform{$l} = $stopwords{$l};
	    
	} elsif (defined $thesaurus{$l}) {
	    $bestform{$l} = $thesaurus{$l};
	    
	} elsif (!$bestform{$l} || ($freq{$w} > $bestfreq{$l})) {
	    $bestfreq{$l} = $freq{$w};
	    $bestform{$l} = $w;
	}
    }
    
    undef %freq;
    undef %bestfreq;
    

    # Assign symbol numbers to tokens
    my $nextsymbol = 1;
    my (@vocab);
    
    # Delimiters
    $first_delimiter = 1;
    
    foreach $word (@delimiters) {

	$word = lc($word);
	$bestform{$word} = uc($word);
	$vocab[$nextsymbol] = $word;
	$symbol{$word} = $nextsymbol;
	$nextsymbol++;
    }
    $last_delimiter = $nextsymbol - 1;
    
    # Stopwords
    $first_stopword = $nextsymbol;
    
    foreach my $word (sort keys %stopwords) {
	
	# don't incluse stopword unless it occurs in the text
	$word = lc($word);
	next unless ($totalfreq{$word});
	next if ($symbol{$word});
	
	$vocab[$nextsymbol] = $word;
	$symbol{$word} = $nextsymbol;
	$nextsymbol++;
    }
    $last_stopword = $nextsymbol - 1;
    $first_contentword = $nextsymbol;
    
    # Thesaurus terms
    if ($use_thesaurus) {
	$first_thesaurusword = $nextsymbol;
    
	foreach my $word (sort keys %thesaurus) {
	    
	    $word = lc($word);
	    next if ($symbol{$word});
	    $bestform{$word} = $thesaurus{$word};
	    
	    $vocab[$nextsymbol] = $word;
	    $symbol{$word} = $nextsymbol;
	    $nextsymbol++;
	    
	}
	$last_thesaurusword = $nextsymbol - 1;
    }

    # Other content words
    $first_extractword = $nextsymbol;
    
    foreach my $word (sort (keys %bestform)) {
	
	next if ($symbol{$word});
	
	$vocab[$nextsymbol] = $word;
	$symbol{$word} = $nextsymbol;
	$nextsymbol++;
    }
    $last_extractword = $nextsymbol - 1;
    $last_contentword = $nextsymbol - 1;
    

    # Outut the words
    print "Saving vocabulary in $phindex_dir/clauses.vocab\n" if ($verbosity > 1);
    open(VOC, ">$phindex_dir/clauses.vocab");

    for (my $i = 1; $i < $nextsymbol; $i++) {
	$w = $vocab[$i];

	print VOC "$bestform{$w}\n";
	$totalfreq{$w} = 0 unless ($totalfreq{$w});
    }
    close VOC;


    # Output statistics about the vocablary
    print "Saving statistics in $phindex_dir/clauses.stats\n" if ($verbosity > 1);
    &util::rm("$phindex_dir/clauses.stats") if (-e "$phindex_dir/clauses.stats");
    open(STAT, ">$phindex_dir/clauses.stats")
	|| die "Cannot open $phindex_dir/clauses.stats: $!";

    print STAT "first_delimiter $first_delimiter\n";
    print STAT "last_delimiter $last_delimiter\n";
    print STAT "first_stopword $first_stopword\n";
    print STAT "last_stopword $last_stopword\n";
    if ($use_thesaurus) {
	print STAT "first_thesaurusword $first_thesaurusword\n";
	print STAT "last_thesaurusword $last_thesaurusword\n";
    }
    print STAT "first_extractword $first_extractword\n";
    print STAT "last_extractword $last_extractword\n";
    print STAT "first_contentword $first_contentword\n";
    print STAT "last_contentword $last_contentword\n";
    print STAT "first_symbol $first_delimiter\n";
    print STAT "last_symbol $last_contentword\n";
    print STAT "first_word $first_stopword\n";
    print STAT "last_word $last_contentword\n";
    close STAT;

    undef @vocab;


    # Save text as symbol numbers
    print "Saving text as numbers in $phindex_dir/clauses.numbers\n" if ($verbosity > 1);
    
    open(TXT, "<$phindex_dir/clauses");
    open(NUM, ">$phindex_dir/clauses.numbers");
    
    $phrasedelimiter = $symbol{lc($senlimit)};
    print NUM "$symbol{lc($colstart)}\n";
    
    # set up the special symbols that delimit documents and sentences
    while(<TXT>) {
	
	# split sentence into a list of tokens
	$line = $_;
	next unless ($line =~ /./);
	@words = split(/\s+/, $line);
	
	# output one token at a time
	foreach $word (@words) {
	    $word = lc($word);
	    print NUM "$symbol{$word}\n";
	}
	
	# output phrase delimiter
	print NUM "$phrasedelimiter\n";
    }
    
    print NUM "$symbol{lc($colend)}\n";

}


# Prepare the phrases file to be input to mgpp.
# This means renumbering the phrases in order of decreasing frequency.


# This is legacy code, and a little ugly, and may be unix-specific
# (particularly the sort command).

sub renumber_phrases {
    my ($phindex_dir, $verbosity) = @_;

    # Sort the phrases into order of increasing frequency
    print "Sorting phrases into freq order\n" if ($verbosity);
    system("sort -rnt ':' +2 -o $phindex_dir/phrases $phindex_dir/phrases");

    my @symbol;

    # Read the vocabulary
    print "Reading the vocabulary\n" if ($verbosity);
    open(V, "<$phindex_dir/clauses.vocab")
	|| die "Cannot open $phindex_dir/clauses.vocab: $!";
    
    my $i = 1;
    while(<V>) {
	chomp;
	$symbol[$i++] = $_;
    }

    # Create file for phrase data
    #
    # The phrases file looks something like this
    # 159396-1:s5175:4:1:116149-2:3:d2240,2;d2253;d2254
    # 159409-1:s5263:6:1:159410-2:6:d2122;d2128;d2129;d2130;d2215;d2380
    # 159415-1:s5267:9:1:159418-2:8:d3,2;d632;d633;d668;d1934;d2010;d2281;d2374
    # 159426-1:s5273:5:2:159429-2,115168-17:5:d252;d815;d938;d939;d2361


    print "Calculate phrase numbers\n" if ($verbosity);
    
    open(IN, "<$phindex_dir/phrases");

    my %phrasenumber;
    my $nextphrase = 1;
    
    my ($line, $num);
    
    while(<IN>) {
	
	# read the line
	chomp;
	$line = $_;
	
	# we're only interested in ther first field
	$line =~ s/:.*//;
	
	# get a phrase number for this line
	$num = $nextphrase;
	$phrasenumber{$line} = $num;
	$nextphrase++;
    }
    
    # Extract the phrase data
    print "Create phrase file and frequency file\n" if ($verbosity);
    
    open(IN, "<$phindex_dir/phrases");
    open(DATA, ">$phindex_dir/mg-p.txt");
    
    my ($key, $tf, $countexp, $expansions, $countdocs, $documents, $text);
    my @fields;
    my @documents;
    my (@newexp, $k, $n);
    
    my $linenumber = 0;

    while(<IN>) {
	
	# read the line
	chomp;
	$line = $_;
	@fields = split(/:/, $line);
	
	# output the MG document tag
	print DATA "<Document>";
	
	# get a phrase number for this line
	$key = shift @fields;
	die unless (defined($phrasenumber{$key}));
	$num = $phrasenumber{$key};
	print DATA "$num:";
	
	# get the text of the phrase
	$text = shift @fields;
	$text =~ s/s(\d+)/$symbol[$1]/g;
	print DATA "$text:";
	
	$linenumber++;
	if ($linenumber % 1000 == 0) {
	    print "line $linenumber:\t$num\t$key\t($text)\n"  if ($verbosity > 1);
	}
	print "$num: $key\t($text)\n" if ($verbosity > 1);
	
	# get the phrase frequency
	$tf = shift @fields;
	print DATA "$tf:";
	
	# get the number of expansions
	$countexp = shift @fields;
	print DATA "$countexp:";
	
	# get the expansions and convert them into phrase numbers
	$expansions = shift @fields;
	@newexp = ();
	foreach $k (split(/,/, $expansions)) {
	    die "ERROR - no phrase number for: $k" unless (defined($phrasenumber{$k}));
	    $n = $phrasenumber{$k};
	    push @newexp, $n;
	}
	print DATA join(",", (sort numerically @newexp)), ":";
	
	# get the number of documents
	$countdocs = shift @fields;
	print DATA "$countdocs:";
	
	# get the documents
	$documents = shift @fields;
	$documents =~ s/d//g;
	@documents = split(/;/, $documents);
	
	print DATA join(";", (sort by_frequency @documents));
	
	print DATA "\n";
	
    }
}

# sort routines used to renumber phrases

sub numerically { $a <=> $b }

sub by_frequency { 
    my $fa = 1;
    if ($a =~ /,/) {
	$fa = $a;
	$fa =~ s/\d+,//;
    }
    my $fb = 1;
    if ($b =~ /,/) {
	$fb = $b;
	$fb =~ s/\d+,//;
    }

    return ($fb <=> $fa);
}

