###########################################################################
#
# BasePlugin.pm -- base class for all the import plugins
# A component of the Greenstone digital library software
# from the New Zealand Digital Library Project at the 
# University of Waikato, New Zealand.
#
# Copyright (C) 1999-2005 New Zealand Digital Library Project
#
# 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.
#
###########################################################################

package BasePlugin;

use strict; 
no strict 'subs';
no strict 'refs'; # allow filehandles to be variables and viceversa

use File::Basename;

use multiread;
use encodings;
use unicode;
use doc;
eval "require diagnostics"; # some perl distros (eg mac) don't have this
use ghtml;
use gsprintf 'gsprintf';

use PrintInfo;

BEGIN {
    @BasePlugin::ISA = ( 'PrintInfo' );
}

our $encoding_list =
    [ { 'name' => "ascii",
	'desc' => "{ReadTextFile.input_encoding.ascii}" },
      { 'name' => "utf8",
	'desc' => "{ReadTextFile.input_encoding.utf8}" },
      { 'name' => "unicode",
	'desc' => "{ReadTextFile.input_encoding.unicode}" } ];

my $e = $encodings::encodings;
foreach my $enc (sort {$e->{$a}->{'name'} cmp $e->{$b}->{'name'}} keys (%$e)) 
{
    my $hashEncode =
    {'name' => $enc,
     'desc' => $e->{$enc}->{'name'}};
    
    push(@{$encoding_list},$hashEncode);
}

our $encoding_plus_auto_list = 
    [ { 'name' => "auto",
	'desc' => "{ReadTextFile.input_encoding.auto}" } ];

push(@{$encoding_plus_auto_list},@{$encoding_list});

my $arguments =
    [ { 'name' => "process_exp",
	'desc' => "{BasePlugin.process_exp}",
	'type' => "regexp",
	'deft' => "",
	'reqd' => "no" },
      { 'name' => "block_exp",
	'desc' => "{BasePlugin.block_exp}",
	'type' => "regexp",
	'deft' => "",
	'reqd' => "no" },
      { 'name' => "smart_block",
	'desc' => "{BasePlugin.smart_block}",
	'type' => "flag",
	'reqd' => "no" },
      { 'name' => "associate_ext",
	'desc' => "{BasePlugin.associate_ext}",
	'type' => "string",
	'reqd' => "no" },
      { 'name' => "associate_tail_re",
	'desc' => "{BasePlugin.associate_tail_re}",
	'type' => "string",
	'reqd' => "no" },
      { 'name' => "use_as_doc_identifier",
	'desc' => "{BasePlugin.use_as_doc_identifier}",
	'type' => "string",
	'reqd' => "no" ,
	'deft' => "" } ,
     { 'name' => "no_cover_image",
	'desc' => "{BasePlugin.no_cover_image}",
	'type' => "flag",
	'reqd' => "no" },
      { 'name' => "filename_encoding",
	'desc' => "{BasePlugin.filename_encoding}",
	'type' => "enum",
	'deft' => "auto",
	'list' => $encoding_plus_auto_list,
	'reqd' => "no" }
      
      ];


my $options = { 'name'     => "BasePlugin",
		'desc'     => "{BasePlugin.desc}",
		'abstract' => "yes",
		'inherits' => "no",
		'args'     => $arguments };


sub new {

    my ($class) = shift (@_);
    my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
    push(@$pluginlist, $class);

    push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
    push(@{$hashArgOptLists->{"OptList"}},$options);

    my $self = new PrintInfo($pluginlist, $inputargs, $hashArgOptLists);

    my $plugin_name = (defined $pluginlist->[0]) ? $pluginlist->[0] : $class;
    $self->{'plugin_type'} = $plugin_name;

    $self->{'num_processed'} = 0;
    $self->{'num_not_processed'} = 0;
    $self->{'num_blocked'} = 0;
    $self->{'num_archives'} = 0;
    $self->{'cover_image'} = 1; # cover image is on by default
    $self->{'cover_image'} = 0 if ($self->{'no_cover_image'});
    #$self->{'option_list'} = $hashArgOptLists->{"OptList"};
    
    my $associate_ext = $self->{'associate_ext'};
    if ((defined $associate_ext) && ($associate_ext ne "")) {

	my $associate_tail_re = $self->{'associate_tail_re'};
	if ((defined $associate_tail_re) && ($associate_tail_re ne "")) {
	    my $outhandle = $self->{'outhandle'};
	    print $outhandle "Warning: can only specify 'associate_ext' or 'associate_tail_re'\n";
	    print $outhandle "         defaulting to 'associate_tail_re'\n";
	}
	else {
	    my @exts = split(/,/,$associate_ext);

	    my @exts_bracketed = map { $_ = "(?:\\.$_)" } @exts;
	    my $associate_tail_re = join("|",@exts_bracketed);
	    $self->{'associate_tail_re'} = $associate_tail_re;
	}

	delete $self->{'associate_ext'};
    }

    $self->{'shared_fileroot'} = {};
    $self->{'file_blocks'} = {};


    return bless $self, $class;

}

# initialize BasePlugin options
# if init() is overridden in a sub-class, remember to call BasePlugin::init()
sub init {
    my $self = shift (@_);
    my ($verbosity, $outhandle, $failhandle) = @_;

    # verbosity is passed through from the processor
    $self->{'verbosity'} = $verbosity;

    # as are the outhandle and failhandle
    $self->{'outhandle'} = $outhandle if defined $outhandle;
    $self->{'failhandle'} = $failhandle;

    # set process_exp and block_exp to defaults unless they were
    # explicitly set

    if ((!$self->is_recursive()) and 
	(!defined $self->{'process_exp'}) || ($self->{'process_exp'} eq "")) {

	$self->{'process_exp'} = $self->get_default_process_exp ();
	if ($self->{'process_exp'} eq "") {
	    warn ref($self) . " Warning: Non-recursive plugin has no process_exp\n";
	}
    }

    if ((!defined $self->{'block_exp'}) || ($self->{'block_exp'} eq "")) {
	$self->{'block_exp'} = $self->get_default_block_exp ();
    }

}

sub begin {
    my $self = shift (@_);
    my ($pluginfo, $base_dir, $processor, $maxdocs) = @_;
}

sub end {
    # potentially called at the end of each plugin pass 
    # import.pl only has one plugin pass, but buildcol.pl has multiple ones

    my ($self) = shift (@_);
}

sub deinit {
    # called only once, after all plugin passes have been done

    my ($self) = @_;
}

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

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

# this function should be overridden to return 1
# in recursive plugins
sub is_recursive {
    my $self = shift (@_);

    return 0; 
}

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

    return "";
}

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

    return "";
}

# default implementation is to do nothing. 
sub store_block_files
{
    my $self =shift (@_);
    my ($filename) = @_;
    return;
}

#default implementation is to block a file with same name as this, but extension jpg or JPG, if cover_images is on.
sub block_cover_image
{
    my $self =shift;
    my $filename = shift;

    if ($self->{'cover_image'}) {
	my $coverfile = $filename;
	$coverfile =~ s/\.[^\\\/\.]+$/\.jpg/;
	if (!-e $coverfile) {
	    $coverfile =~ s/jpg$/JPG/;
	} 	
	if (-e $coverfile) {
	    $self->{'file_blocks'}->{$coverfile} = 1;
	} 
    }

    return;
}

sub root_ext_split
{
    my $self = shift (@_);
    my ($filename,$tail_re) = @_;
    
    my ($file_prefix,$file_ext) = ($filename =~ m/^(.*?)($tail_re)$/);

    if ((!defined $file_prefix) || (!defined $file_ext)) {
	($file_prefix,$file_ext) = ($filename =~ m/^(.*)(\..*?)$/);
    }

    return ($file_prefix,$file_ext);
}

sub metadata_read {
    my $self = shift (@_);  
    my ($pluginfo, $base_dir, $file, $metadata, $extrametakeys, $extrametadata, $processor, $maxdocs, $gli) = @_;
    # Keep track of filenames with same root but different extensions
    # Used to support -associate_ext and the more generalised
    # -associate_tail_re

    my $associate_tail_re = $self->{'associate_tail_re'};
    if ((defined $associate_tail_re) && ($associate_tail_re ne "")) {

	my ($file_prefix,$file_ext) 
	    = $self->root_ext_split($file,$associate_tail_re);

	if ((defined $file_prefix) && (defined $file_ext)) {

	    my $shared_fileroot = $self->{'shared_fileroot'};
	    if (!defined $shared_fileroot->{$file_prefix}) {
		my $file_prefix_rec = { 'tie_to'  => undef, 
				        'exts'    => {} };
		$shared_fileroot->{$file_prefix} = $file_prefix_rec;
	    }
	    
	    my $file_prefix_rec = $shared_fileroot->{$file_prefix};

	    my $process_exp = $self->{'process_exp'};

	    if ($file =~ m/$process_exp/) {
		# This is the document the others should be tied to
		$file_prefix_rec->{'tie_to'} = $file_ext;
	    }
	    else {
		if ($file_ext =~ m/$associate_tail_re$/) {
		    $file_prefix_rec->{'exts'}->{$file_ext} = 1;
		}
	    }

	}
    }

    # now check whether we are actually processing this
    my $filename = $file;
    $filename = &util::filename_cat ($base_dir, $file) if $base_dir =~ /\w/;
    if ($self->{'process_exp'} eq "" || $filename !~ /$self->{'process_exp'}/ || !-f $filename) {
	return undef; # can't recognise
    }

    # do smart blocking if appropriate
    if ($self->{'smart_block'}) {
	$self->store_block_files($filename);
    }
    # block the cover image if there is one
    if ($self->{'cover_image'}) {
	$self->block_cover_image($filename);
    }
       
    return 1;
}

sub tie_to_filename
{
    my $self = shift (@_);  
  
    my ($file_ext,$file_prefix_rec) = @_;

    if (defined $file_prefix_rec) {
	my $tie_to = $file_prefix_rec->{'tie_to'};

	if (defined $tie_to) {
	    if ($tie_to eq $file_ext) {
		return 1;
	    }
	}
    }

    return 0;
}

sub tie_to_assoc_file
{
    my $self = shift (@_);    
    my ($file_ext,$file_prefix_rec) = @_;

    if (defined $file_prefix_rec) {
	my $tie_to = $file_prefix_rec->{'tie_to'};
	if (defined $tie_to) {

	    my $exts = $file_prefix_rec->{'exts'};

	    my $has_file_ext = $exts->{$file_ext};

	    if ($has_file_ext) {
		return 1;
	    }
	}
    }

    return 0;
}


sub associate_with
{
    my $self = shift (@_);    
    my ($file, $filename, $metadata) = @_;

    my $associate_tail_re = $self->{'associate_tail_re'};
    return 0 if (!$associate_tail_re);

    # If file, see if matches with "tie_to" doc or is one of the
    # associated filename extensions.

    my ($file_prefix,$file_ext) = $self->root_ext_split($file,$associate_tail_re);

    if ((defined $file_prefix) && (defined $file_ext)) {

	my $file_prefix_rec = $self->{'shared_fileroot'}->{$file_prefix};
	
	if ($self->tie_to_filename($file_ext,$file_prefix_rec)) {

	    # Set up gsdlassocfile_tobe

	    my $exts = $file_prefix_rec->{'exts'};
		
	    if (!defined $metadata->{'gsdlassocfile_tobe'}) {
		$metadata->{'gsdlassocfile_tobe'} = [];
	    }

	    my $assoc_tobe = $metadata->{'gsdlassocfile_tobe'};
			
	    my ($full_prefix) = ($filename =~ m/^(.*)\..*?$/);
	    foreach my $e (keys %$exts) {		
		my $assoc_file = "$full_prefix$e";
		print STDERR "  $self->{'plugin_type'}: Associating $file_prefix$e with $file_prefix_rec->{'tie_to'} version\n";
		my $mime_type = ""; # let system auto detect this
		push(@$assoc_tobe,"$assoc_file:$mime_type:"); 
	    }

	}
	elsif ($self->tie_to_assoc_file($file_ext,$file_prefix_rec)) {


	    # a form of smart block	    
	    return 1;
	}
    }

    return 0;
}

sub get_full_filenames {
    my $self = shift (@_);
    my ($base_dir, $file) = @_;

    my $filename_full_path = $file;
    # add on directory if present
    $filename_full_path = &util::filename_cat ($base_dir, $file) if $base_dir =~ /\w/;
    my $filename_no_path = $file;
    # remove directory if present
    $filename_no_path =~ s/^.*[\/\\]//;
    return ($filename_full_path, $filename_no_path);
}

sub read_block {
    my $self = shift (@_);  
  
    my ($pluginfo, $base_dir, $file, $metadata, $processor, $maxdocs, $total_count, $gli) = @_;


    my ($filename_full_path, $filename_no_path) = $self->get_full_filenames($base_dir, $file);

    if ($self->associate_with($file,$filename_full_path,$metadata)) {
	# a form of smart block
	$self->{'num_blocked'} ++;
	return (0,undef); # blocked
    }

    my $smart_block = $self->{'smart_block'};
    my $smart_block_BN = $self->{'smart_block_BN'};
   
    if ($smart_block || $smart_block_BN) {
	if (defined $self->{'file_blocks'}->{$filename_full_path} && $self->{'file_blocks'}->{$filename_full_path} == 1){
	    $self->{'num_blocked'} ++;
	    return (0,undef); # blocked
	}
    } else {
	if ($self->{'block_exp'} ne "" && $filename_full_path =~ /$self->{'block_exp'}/) {
	    $self->{'num_blocked'} ++;
	    return (0,undef); # blocked
	}
	if ($self->{'cover_image'}) {
	    if (defined $self->{'file_blocks'}->{$filename_full_path} && $self->{'file_blocks'}->{$filename_full_path} == 1){
		$self->{'num_blocked'} ++;
		return (0,undef); # blocked
	    }
	}
    }

    if ($filename_full_path !~ /$self->{'process_exp'}/ || !-f $filename_full_path) {
	return (undef,undef); # can't recognise
    }
    
    ##why are we returning the full filename - do we need this??
    return (1,$filename_full_path);
}


#filename_encoding set by user
sub filename_to_utf8_metadata
{
    my $self = shift (@_);  
    my ($file, $file_encoding) = @_;

    my $outhandle = $self->{'outhandle'};

    my ($filemeta) = $file =~ /([^\\\/]+)$/; # getting the tail of the filepath (skips all string parts containing slashes upto the end)

    my $filename_encoding = $self->{'filename_encoding'};
    if ($filename_encoding eq "auto") {
	# we check the locale first
	if (!defined $self->{'filesystem_encoding'}) {
	    $self->{'filesystem_encoding'} = $self->get_filesystem_encoding();
	    $self->{'filesystem_encoding'} = "undefined" if !defined $self->{'filesystem_encoding'};
	}
	if ($self->{'filesystem_encoding'} ne "undefined") {
	    $filename_encoding = $self->{'filesystem_encoding'};
	} else {
	    # try the encoding of the document, if available
	    if (defined $file_encoding) {
		$filename_encoding = $file_encoding;
	    } else {
		# use utf8
		$filename_encoding = "utf8";
	    }
	}
	
    }

    if ($filename_encoding !~ /(?:ascii|utf8|unicode)/) {
	$filemeta = unicode::unicode2utf8(
	  unicode::convert2unicode($filename_encoding, \$filemeta)
	);
    }
    my $dmsafe_filemeta = &ghtml::dmsafe($filemeta);

    return $dmsafe_filemeta;

}


sub get_filesystem_encoding {

    my $self = shift(@_);

    my $outhandle = $self->{'outhandle'};
    my $filesystem_encoding = undef;

    eval {
	use POSIX qw(locale_h);
	
	# With only one parameter, setlocale retrieves the 
	# current value
	my $current_locale = setlocale(LC_CTYPE);
	
	if ($current_locale =~ m/^.*\.(.*?)$/) {
	    my $char_encoding = lc($1);
	    if ($char_encoding =~ m/^(iso)(8859)(\d{1,2})$/) {
		$char_encoding = "$1\_$2\_$3";
	    }

	    $char_encoding =~ s/-/_/g;
	    $char_encoding =~ s/^utf_8$/utf8/;
	    
	    if ($char_encoding =~ m/^\d+$/) {
		if (defined $encodings::encodings->{"windows_$char_encoding"}) {
		    $char_encoding = "windows_$char_encoding";
		}
		elsif (defined $encodings::encodings->{"dos_$char_encoding"}) {
		    $char_encoding = "dos_$char_encoding";
		}
	    }
	    
	    if (($char_encoding =~ m/(?:ascii|utf8|unicode)/) 
		|| (defined $encodings::encodings->{$char_encoding})) {
		$filesystem_encoding = $char_encoding;
	    }
	    else {
		print $outhandle "Warning: Unsupported character encoding '$char_encoding' from locale '$current_locale'\n";
	    }
	}
	

    };
    if ($@) {
	print $outhandle "$@\n";
	print $outhandle "Warning: Unable to establish locale.  Will assume filesytem is UTF-8\n";
	
    }
    return $filesystem_encoding;
}

# is there ever only one Source? Sometimes this will be called twice, for images etc that are converted.
sub set_Source_metadata {
    my $self = shift (@_);  
    my ($doc_obj, $filename_no_path, $file_encoding) = @_;

    my $top_section = $doc_obj->get_top_section();
    
    # the original encoding filename
    $doc_obj->set_metadata_element($top_section, "Source", $filename_no_path);
    # UTF-8 version of filename
    my $filemeta = $self->filename_to_utf8_metadata($filename_no_path, $file_encoding);
    $doc_obj->set_utf8_metadata_element($top_section, "SourceUTF8", $filemeta);

}
     
sub add_OID {

    my $self = shift (@_);  
    my ($doc_obj) = @_;

    # See if a metadata field is specified as the field
    if ((defined $self->{'use_as_doc_identifier'}) && ($self->{'use_as_doc_identifier'} ne "")) {
	my $metadata_doc_id = $self->{'use_as_doc_identifier'};

	# Consider "tidying" up metadata_doc_id to be something
	# suitable in a URL
	# Could even support a user specified plugin RE for this.

	my $top_section = $doc_obj->get_top_section();
	my $oid = $doc_obj->get_metadata_element($top_section,$metadata_doc_id);
##	print STDERR "**** oid = $oid\n";
    	$doc_obj->set_OID($oid);
    }
    # See if there is a plugin-specific set_OID function...
    elsif (defined ($self->can('set_OID'))) {
	# it will need $doc_obj to set the Identifier metadata...
	$self->set_OID(@_); # pass through any extra arguments supplied
    } else {
	# use the default set_OID() in doc.pm
	$doc_obj->set_OID();
    }
}



# The BasePlugin read_into_doc_obj() function. This function does all the
# right things to make general options work for a given plugin.  It doesn't do anything with the file other than setting reads in
# a file and sets up a slew of metadata all saved in doc_obj, which
# it then returns as part of a tuple (process_status,doc_obj)
#
# Much of this functionality used to reside in read, but it was broken
# down into a supporting routine to make the code more flexible.  
#
# recursive plugins (e.g. RecPlug) and specialized plugins like those
# capable of processing many documents within a single file (e.g.
# GMLPlug) will normally want to implement their own version of
# read_into_doc_obj()
#
# Note that $base_dir might be "" and that $file might 
# include directories

# currently blocking has been done before it gets here - does this affect secondary plugin stuff??
sub read_into_doc_obj {
    my $self = shift (@_);  
    my ($pluginfo, $base_dir, $file, $metadata, $processor, $maxdocs, $total_count, $gli) = @_;

    my $outhandle = $self->{'outhandle'};

    # should we move this to read? What about secondary plugins?
    print STDERR "<Processing n='$file' p='$self->{'plugin_type'}'>\n" if ($gli);
    print $outhandle "$self->{'plugin_type'} processing $file\n"
	    if $self->{'verbosity'} > 1;

    my ($filename_full_path, $filename_no_path) = $self->get_full_filenames($base_dir, $file);
    # create a new document
    my $doc_obj = new doc ($filename_full_path, "indexed_doc");
    my $top_section = $doc_obj->get_top_section();

    # this should look at the plugin option too...
    $doc_obj->set_OIDtype ($processor->{'OIDtype'}, $processor->{'OIDmetadata'});    
    $doc_obj->add_utf8_metadata($top_section, "Plugin", "$self->{'plugin_type'}");
    $doc_obj->add_utf8_metadata($top_section, "FileSize", (-s $filename_full_path));
 
    $self->set_Source_metadata($doc_obj, $filename_no_path);

    # plugin specific stuff - what args do we need here??
    unless (defined ($self->process($pluginfo, $base_dir, $file, $metadata, $doc_obj, $gli))) {
	print STDERR "<ProcessingError n='$file'>\n" if ($gli);
	return -1;
    }
    
    # include any metadata passed in from previous plugins 
    # note that this metadata is associated with the top level section
    my $section = $doc_obj->get_top_section();
    # can we merge these two methods??
    $self->add_associated_files($doc_obj, $filename_full_path);
    $self->extra_metadata ($doc_obj, $section, $metadata);
    $self->auto_extract_metadata($doc_obj);

    # if we haven't found any Title so far, assign one
    # this was shifted to here from inside read()
    $self->title_fallback($doc_obj,$section,$filename_no_path);
    
    $self->add_OID($doc_obj);
    
    return (1,$doc_obj);
}

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

    # add NoText metadata so we can hide this dummy text in format statements
    $doc_obj->add_metadata($section, "NoText", "1");
    $doc_obj->add_text($section, &gsprintf::lookup_string("{BasePlugin.dummy_text}",1));
    
}

# does nothing. Can be overridden by subclass
sub auto_extract_metadata {
    my $self = shift(@_);
    my ($doc_obj) = @_;
}

# adds cover image, associate_file options stuff. Should be called by sub class
# read_into_doc_obj
sub add_associated_files {
    my $self = shift(@_);
    # whatis filename??
    my ($doc_obj, $filename) = @_;
    
    # add in the cover image
    if ($self->{'cover_image'}) {
	$self->associate_cover_image($doc_obj, $filename);
    }
   

}

# The BasePlugin read() function. This function calls read_into_doc_obj()
# to ensure all the right things to make general options work for a
# given plugin are done. It then calls the process() function which
# does all the work specific to a plugin (like the old read functions
# used to do). Most plugins should define their own process() function
# and let this read() function keep control.  
#
# recursive plugins (e.g. RecPlug) and specialized plugins like those
# capable of processing many documents within a single file (e.g.
# GMLPlug) might want to implement their own version of read(), but
# more likely need to implement their own version of read_into_doc_obj()
#
# Return number of files processed, undef if can't recognise, -1 if can't
# process

sub read {
    my $self = shift (@_);  
    my ($pluginfo, $base_dir, $file, $metadata, $processor, $maxdocs, $total_count, $gli) = @_;

    # check that we are not blocked
    my ($block_status,$filename) = $self->read_block(@_);    
    return $block_status if ((!defined $block_status) || ($block_status==0));

    my ($process_status,$doc_obj) = $self->read_into_doc_obj(@_);
   
    if ((defined $process_status) && ($process_status == 1)) {

	# process the document
	$processor->process($doc_obj);

	$self->{'num_processed'} ++;
	undef $doc_obj; 
    }
    # delete any temp files that we may have created
    $self->clean_up_after_doc_obj_processing();

    # if process_status == 1, then the file has been processed.
    return $process_status;

}

# returns undef if file is rejected by the plugin
sub process {
    my $self = shift (@_);
    my ($textref, $pluginfo, $base_dir, $file, $metadata, $doc_obj, $gli) = @_;

    gsprintf(STDERR, "BasePlugin::process {common.must_be_implemented}\n") && die "\n";
    # die "BasePlugin::process function must be implemented in sub-class\n";

    return undef; # never gets here
}

# overwrite this method to delete any temp files that we have created
sub clean_up_after_doc_obj_processing {
    my $self = shift(@_);

}
# write_file -- used by ConvertToPlug, for example in post processing
#
# where should this go, is here the best place??
sub utf8_write_file {
    my $self = shift (@_);
    my ($textref, $filename) = @_;
    
    if (!open (FILE, ">$filename")) {
	gsprintf(STDERR, "ConvertToPlug::write_file {ConvertToPlug.could_not_open_for_writing} ($!)\n", $filename);
	 die "\n";
     }
    print FILE $$textref;
    
    close FILE;
}


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

    my $file_derived_title = $file;
    $file_derived_title =~ s/_/ /g;
    $file_derived_title =~ s/\..*?$//;

    return $file_derived_title;
}


sub title_fallback
{
    my $self = shift (@_);
    my ($doc_obj,$section,$file) = @_;

    if (!defined $doc_obj->get_metadata_element ($section, "Title") or $doc_obj->get_metadata_element($section, "Title") eq "") {

	my $file_derived_title = $self->filename_to_utf8_metadata($self->filename_based_title($file));
	if (!defined $doc_obj->get_metadata_element ($section, "Title")) {
	    $doc_obj->add_utf8_metadata ($section, "Title", $file_derived_title);
	}
	else {
	    $doc_obj->set_utf8_metadata ($section, "Title", $file_derived_title);
	} 
    }
	    
}
  
# add any extra metadata that's been passed around from one
# plugin to another.
# extra_metadata uses add_utf8_metadata so it expects metadata values
# to already be in utf8
sub extra_metadata {
    my $self = shift (@_);
    my ($doc_obj, $cursection, $metadata) = @_;

    my $associate_tail_re = $self->{'associate_tail_re'};

    foreach my $field (keys(%$metadata)) {
	# $metadata->{$field} may be an array reference
	if ($field eq "gsdlassocfile_tobe") {
	    # 'gsdlassocfile_tobe' is artificially introduced metadata
	    # that is used to signal that certain additional files should
	    # be tied to this document.  Useful in situations where a
	    # metadata pass in the plugin pipeline works out some files
	    # need to be associated with a document, but the document hasn't
	    # been formed yet.
	    
	    my $equiv_form = "";
	    foreach my $gaf (@{$metadata->{$field}}) {
		my ($full_filename,$mimetype) = ($gaf =~ m/^(.*):(.*):$/);
		my ($tail_filename) = ($full_filename =~ /^.*[\/\\](.+?)$/);
		my $filename = $full_filename;
			      
		$doc_obj->associate_file($full_filename,$tail_filename,$mimetype);

		# work out extended tail extension (i.e. matching tail re)

		my ($file_prefix,$file_extended_ext) 
		    = $self->root_ext_split($tail_filename,$associate_tail_re);
		my ($pre_doc_ext) = ($file_extended_ext =~ m/^(.*)\..*$/);

		my ($doc_ext) = ($tail_filename =~ m/^.*\.(.*)$/);
		my $start_doclink = "<a href=\"_httpprefix_/collect/[collection]/index/assoc/{Or}{[parent(Top):archivedir],[archivedir]}/$tail_filename\">";
		my $srcicon = "_icon".$doc_ext."_";
		my $end_doclink = "</a>";

		my $assoc_form = "$start_doclink\{If\}{$srcicon,$srcicon,$doc_ext\}$end_doclink";

		if (defined $pre_doc_ext) {
		    # for metadata such as [mp3._edited] [mp3._full] ...
		    $doc_obj->add_utf8_metadata ($cursection, "$doc_ext.$pre_doc_ext", $assoc_form); 
		}

		# for multiple metadata such as [mp3.assoclink]
		$doc_obj->add_utf8_metadata ($cursection, "$doc_ext.assoclink", $assoc_form); 
		
		$equiv_form .= " $assoc_form";	
	    }
	    $doc_obj->add_utf8_metadata ($cursection, "equivlink", $equiv_form); 
	}
	elsif (ref ($metadata->{$field}) eq "ARRAY") {
	    map { 
		$doc_obj->add_utf8_metadata ($cursection, $field, $_); 
	    } @{$metadata->{$field}};
	} else {
	    $doc_obj->add_utf8_metadata ($cursection, $field, $metadata->{$field}); 
	}
    }
}


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

    $stats->{'num_processed'} += $self->{'num_processed'};
    $stats->{'num_not_processed'} += $self->{'num_not_processed'};
    $stats->{'num_archives'} += $self->{'num_archives'};

}

sub associate_cover_image {
    my $self = shift;
    my ($doc_obj, $filename) = @_;

    $filename =~ s/\.[^\\\/\.]+$/\.jpg/;
    if (exists $self->{'covers_missing_cache'}->{$filename}) {
	# don't stat() for existence eg for multiple document input files
	# (eg SplitPlug)
	return;
    }

    my $top_section=$doc_obj->get_top_section();

    if (-e $filename) {
    	$doc_obj->associate_file($filename, "cover.jpg", "image/jpeg");
	$doc_obj->add_utf8_metadata($top_section, "hascover",  1);
    } else {
	my $upper_filename = $filename;
	$upper_filename =~ s/jpg$/JPG/;
	if (-e $upper_filename) {
	    $doc_obj->associate_file($upper_filename, "cover.jpg",
				     "image/jpeg");
	    $doc_obj->add_utf8_metadata($top_section, "hascover",  1);
	} else {
	    # file doesn't exist, so record the fact that it's missing so
	    # we don't stat() again (stat is slow)
	    $self->{'covers_missing_cache'}->{$filename} = 1;
	}
    }

}


# Overridden by exploding plugins (eg. ISISPlug)
sub clean_up_after_exploding
{
    my $self = shift(@_);
}


1;
