# This document processor saves a document in the 
# archives directory of a collection


package mgbuildproc;

use docproc;
use util;


BEGIN {
    @ISA = ('docproc');
}


sub new {
    my ($class, $collection, $source_dir, $build_dir, $verbosity) = @_;
    my $self = new docproc ();

    $self->{'collection'} = $collection;
    $self->{'source_dir'} = $source_dir;
    $self->{'build_dir'} = $build_dir;
    $self->{'verbosity'} = $verbosity;
    $self->{'mode'} = "text";
    $self->{'index'} = "section:text";
    $self->{'output_handle'} = "STDOUT";
    $self->{'num_docs'} = 0;
    $self->{'num_sections'} = 0;
    $self->{'num_bytes'} = 0;

    return bless $self, $class;
}

sub reset {
    my $self = shift (@_);
    
    $self->{'num_docs'} = 0;
    $self->{'num_sections'} = 0;
    $self->{'num_bytes'} = 0;
}

sub get_num_docs {
    my $self = shift (@_);

    return $self->{'num_docs'};
}

sub get_num_sections {
    my $self = shift (@_);

    return $self->{'num_sections'};
}

sub get_num_bytes {
    my $self = shift (@_);

    return $self->{'num_bytes'};
}

sub set_output_handle {
    my $self = shift (@_);
    my ($handle) = @_;

    $self->{'output_handle'} = $handle;
}

sub set_mode {
    my $self = shift (@_);
    my ($mode) = @_;

    $self->{'mode'} = $mode;
}

sub set_index {
    my $self = shift (@_);
    my ($index) = @_;

    $self->{'index'} = $index;
}

sub process {
    my $self = shift (@_);
    my ($doc_obj) = @_;
    my $method = $self->{'mode'};

    $self->$method($doc_obj);
}

sub infodb {
    my $self = shift (@_);
    my ($doc_obj) = @_;
    my $handle = $self->{'output_handle'};

    if ($doc_obj eq 'classifications') {
	# output classifications if all books have been processed
	foreach $key (keys %$saved_classifications) {
	    $saved_classifications->{$key}->{'contains'} = undef if 
		$saved_classifications->{$key}->{'contains'} eq "";
	    $saved_classifications->{$key}->{'parent'} = undef if 
		(!defined $saved_classifications->{$key}->{'parent'}) || 
		    ($saved_classifications->{$key}->{'parent'} eq "");
	    $self->write_to_gdbm ($handle, $key, $saved_classifications->{$key}->{'title'}, 
				  undef, undef, 
				  $saved_classifications->{$key}->{'contains'}, undef, 
				  $saved_classifications->{$key}->{'parent'}, undef);
	}
	return;
    }

    my $doctype = $doc_obj->get_doc_type();

    # only output this document if it is one to be indexed
    return if ($doctype ne "indexed_doc" && 
	       $doctype ne "classification");

    # found classification document
    $saved_classifications = {} if ($doctype eq "classification");
    $sectionmap = {};

    # this is another document
    $self->{'num_docs'} += 1 unless ($doctype eq "classification");

    my $section = $doc_obj->get_top_section();
    while (defined $section) {
	# update a few statistics
	$self->{'num_bytes'} += $doc_obj->get_text_length ($section);
	$self->{'num_sections'} += 1 unless ($doctype eq "classification");

	my $title = $doc_obj->get_metadata_element($section, "Title");
	my $jobnumber = $doc_obj->get_source_filename();

	my $mapped_section = $self->map_section($doctype, $section);
	if ($doctype eq "classification") {
	    $mapped_section = $self->char_classification($mapped_section);
	} else {
	    $mapped_section = "B.$self->{'num_docs'}.$mapped_section";
	}
	$mapped_section =~ s/\.+$//;

	my ($parent, $classification, $creator, $OID);

	if ($section ne $doc_obj->get_top_section()) {
	    $parent = $self->map_section($doctype, $doc_obj->get_parent_section($section));
	    if ($doctype eq "classification") {
		$parent = $self->char_classification($parent);
	    } else {
		$parent = "B.$self->{'num_docs'}.$parent";
	    }
	    $parent =~ s/\.+$//;
	} else {
	    $classification = $self->get_classifications($doc_obj, $section, $mapped_section)
		unless $doctype eq "classification";
	    $creator = $doc_obj->get_metadata_element($section, "Creator");
	    $OID = $doc_obj->get_OID();
	    # this is a hack at getting OID to look like the directory path - I'm sure there's 
	    # a better way to do it but it's late... Stefan
	    my @OIDchars = split //, $OID;
	    $OID = "";
	    my $count = 0;
	    foreach $i (@OIDchars) {
		if ($count == 7) {
		    $OID .= "$i/";
		    $count = 0;
		} else {
		    $OID .= $i;
		    $count ++;
		}
	    }
	    $OID =~ s/\/$//;
	    $OID .= ".dir";
	}

	if ($doc_obj->is_leaf_section($section)) {
	    if ($doctype eq "classification") {
		if (defined $saved_classifications->{$mapped_section}) {
		    print STDERR "mgbuildproc:warning: classification $mapped_section " .
			"declared multiple times\n";
		} else {
		    $saved_classifications->{$mapped_section}  = {'title' => $title, 'contains' => "",
								  'parent' => $parent};
		}
	    } else {
		$self->write_to_gdbm($handle, $mapped_section, $title, $creator, $jobnumber, undef,
				     $self->{'num_sections'}, $parent, $classification, $OID);
	    }
	} else {

	    # add the introductory section if it exists
	    my $contains = "";
	    if ($doc_obj->get_text_length($section) > 0) {
		$contains .= "B.$self->{'num_docs'}." . $self->map_section ($doctype, "$section.0");
	    }

	    # add the rest of the children
	    my @children = @{$doc_obj->get_children($section)};
	    if ($doctype eq "classification") {
		map {$_ = $self->char_classification($_);} @children;
		if (defined $saved_classifications->{$mapped_section}) {
		    print STDERR "mgbuildproc:warning: classification $mapped_section " .
			"declared multiple times\n";
		} else {
		    $saved_classifications->{$mapped_section}  = {'title' => $title, 'contains' => ""};
		    $saved_classifications->{$mapped_section}->{'contains'} = join ":", @children;
		    $saved_classifications->{$mapped_section}->{'parent'} = $parent;
		}
	    } else {
		map {$_ = "B.$self->{'num_docs'}." . $self->map_section($doctype, $_);} @children;
		$contains .= ":" if $contains ne "";
		$contains .= join ":", @children;
		$self->write_to_gdbm ($handle, $mapped_section, $title, $creator,
				      $jobnumber, $contains, $self->{'num_sections'},
				      $parent, $classification, $OID);

		if ($doc_obj->get_text_length($section) > 0) {
		    my $intromapsection = "B.$self->{'num_docs'}." . 
			$self->map_section($doctype, "$section.0");
		    $self->write_to_gdbm ($handle, $intromapsection, "<i>(introductory text)</i>", $creator,
					  $jobnumber, undef, $self->{'num_sections'},
					  $mapped_section, undef, $OID);
		}
	    }
	}
	$section = $doc_obj->get_next_section($section);
    }
    
    # update classification list with those books that 
    # were processed before classification list
    if ($doctype eq "classification" && defined $temp_classifications) {
	foreach $key (keys(%$temp_classifications)) {
	    if (!defined $saved_classifications->{$key}) {
		print STDERR "mgbuildproc:$temp_classifications->{$key} belong to " .
		    "undefined classification $key\n";
	    } 
	    $saved_classifications->{$key}->{'contains'} = $temp_classifications->{$key}->{'contains'};
	}
	$temp_classifications = undef;
    }
}


sub write_to_gdbm {
    my $self = shift (@_);
    my ($handle, $section, $title, $creator, $jobnumber, $contains,
	$docnum, $parent, $classification, $OID) = @_;

    print $handle "[$section]\n";
    print $handle "<t>$title\n" if defined $title;
    print $handle "<a>$creator\n" if defined $creator;
    print $handle "<j>$jobnumber\n" if defined $jobnumber;
    print $handle "<c>$contains\n" if defined $contains;
    print $handle "<d>$docnum\n" if defined $docnum;
    print $handle "<p>$parent\n" if defined $parent;
    print $handle "<x>$classification\n" if defined $classification;
    print $handle "<o>$OID\n" if defined $OID;
    print $handle '-' x 70, "\n";
    
    if (defined $docnum) {
	print $handle "[$docnum]\n";
	print $handle "<x>$section\n";
	print $handle '-' x 70, "\n";
    }
}

sub get_classifications {
    my $self = shift (@_);
    my ($doc_obj, $section, $mapped_section) = @_;

    my ($classificationsref);
    if (defined $saved_classifications) {
	# classification list has been processed
	$classificationsref = $saved_classifications;
    } else {
	# classification list has yet to be processed, save
	# books in temp_classifications until list is processed
	# and they can be moved into saved_classifications
	$temp_classifications = {} unless defined $temp_classifications;
	$classificationsref = $temp_classifications;
    }

    my $classifications = $doc_obj->get_metadata($section, "Subject");

    # need to save which books belong in each classification
    # to output later
    foreach $classification (@$classifications) {

	if (!defined $classificationsref->{$classification}) {
	    $classificationsref->{$classification}->{'parent'} = $doc_obj->get_parent_section($section);
	    $classificationsref->{$classification}->{'contains'} = "";
	}
	$classificationsref->{$classification}->{'contains'} .= ":" unless
	    $classificationsref->{$classification}->{'contains'} eq "";
	$classificationsref->{$classification}->{'contains'} .= $mapped_section;
    }

    return (join ":", @$classifications);
}

sub map_section {
    my $self = shift (@_);
    my ($doctype, $section) = @_;

    # classifications should never need to be mapped
    return $section if $doctype eq "classification";

    return "" unless (defined $section) && ($section ne "");

    $sectionmap = {} unless defined $sectionmap;



    # get the section into a standard format
    $section =~ s/^\.+|\.+$//g;

    # return the mapped section if it has been seen before
    if (defined $sectionmap->{$section}) {
	return $sectionmap->{$section};
    }

    # find out the parent section
    my ($parentsection, $num);
    if ($section =~ /^(.+)\.(\d+)$/) {
	$parentsection = $1;
	$num = $2;
    } elsif ($section =~ /^(\d+)$/) {
	$parentsection = "";
	$num = $1;
    } else {
	print STDERR "mgbuildproc:map_section - misformed section $section\n";
    }
    $parentsection = "" unless defined $parentsection;

    if ($parentsection eq "") {
	$num ++ if $num == 1 and defined $sectionmap->{'0'};
    } else {
	$num ++ if $num == 1 and defined $sectionmap->{"$parentsection.0"};
    }

    # find out the mapped parent section
    my $mappedparentsection = $self->map_section ($doctype, $parentsection);

    # find the next unused child section
    my $previousnum = $num - 1;
    my $previoussection = "";
    if ($parentsection eq "") {
	$previoussection = $previousnum;
	$previoussection = 0 if ($section == 1) && (defined $sectionmap->{'0'});
    } else{
	$previoussection = "$parentsection.$previousnum";
	$previoussection = "$parentsection.0" if ($section =~ /\.1$/) && (defined $sectionmap->{"$parentsection.0"});
    }
    while (($previousnum > 0) && (!defined $sectionmap->{$previoussection})) {
	$previousnum--;
	$previoussection = "$parentsection.$previousnum";
	$previoussection = $previousnum if $parentsection eq "";
    }

    # there has been no children under this parent, this section will be number 1
    if ($previousnum <= 0) {
	if ($mappedparentsection eq "") {
	    $sectionmap->{$section} = "1";
	} else {
	    $sectionmap->{$section} = "$mappedparentsection.1";
	}

    } else {
	# get the previous mapped number
	my $previousmapnum = 0;
	if ($sectionmap->{$previoussection} =~ /(^|\.)?(\d+)$/) {
	    $previousmapnum = $2;
	}
	
	# increment it to get this mapped child
	my $mappednum = $previousmapnum+1;

	if ($mappedparentsection eq "") {
	    $sectionmap->{$section} = $mappednum;
	} else {
	    $sectionmap->{$section} = "$mappedparentsection.$mappednum";
	}
    }

    return $sectionmap->{$section};
}

sub text {
    my $self = shift (@_);
    my ($doc_obj) = @_;
    my $handle = $self->{'output_handle'};

    # only output this document if it is one to be indexed
    return if ($doc_obj->get_doc_type() ne "indexed_doc");

    # this is another document
    $self->{'num_docs'} += 1;

    # get the parameters for the output
    my ($level, $fields) = split (/:/, $self->{'index'});
    $fields =~ s/\ball\b/Title,Creator,text/;
    $fields =~ s/\btopall\b/topTitle,topCreator,toptext/;

    my $doc_section = 0; # just for this document
    my $text = "";
    my $text_extra = "";

    # get the text for this document
    my $section = $doc_obj->get_top_section();
    while (defined $section) {
	# update a few statistics
	$doc_section++;
	$self->{'num_bytes'} += $doc_obj->get_text_length ($section);
	$self->{'num_sections'} += 1;
	
	foreach $field (split (/,/, $fields)) {
	    # only deal with this field if it doesn't start with top or
	    # this is the first section
	    my $real_field = $field;
	    if (!($real_field =~ s/^top//) || ($doc_section == 1)) {
		my $new_text = "";
		if ($real_field eq "text") {
		    $new_text = $doc_obj->get_text ($section);
		    $new_text =~ s/[\cB\cC]//g;
		    $new_text =~ s/(<p\b)/\cC$1/gi;
		    
		} else {
		    $new_text = join ("\cC", @{$doc_obj->get_metadata ($section, $real_field)});
		}
	    
		$text .= "$new_text\cC";
	    }
	}
	
	if ($level eq "document") { $text_extra .= "\cB"; }
	else { $text .= "\cB"; }
	
	$section = $doc_obj->get_next_section($section);
    }

    print $handle "$text$text_extra";
}

# converts leading number in classification back 
# to letter it represents
# i.e 67.2.4 becomes C.2.4
sub char_classification {
    my $self = shift (@_);
    my ($classification) = @_;

    return $classification if $classification eq "";
    my ($c) = $classification =~ /^\.?(\d+)/;
    $c = chr($c);
    $classification =~ s/^\d+/$c/;

    return $classification;
}

# converts leading letter of a classification into its ascii equivalent
# i.e C.2.4 becomes 67.2.4
sub int_classification {
    my $self = shift (@_);
    my ($classification) = @_;
    my $c = ord($classification);
    $classification =~ s/^./$c/;

    return $classification;
}
1;

