# -*-Perl-*-
#
#
#######################################################################
# This software has been created by Genome Research Limited (GRL).    # 
# GRL hereby grants permission to use, copy, modify and distribute    # 
# this software and its documentation for non-commercial purposes     # 
# without fee at the user's own risk on the basis set out below.      #
# GRL neither undertakes nor accepts any duty whether contractual or  # 
# otherwise in connection with the software, its use or the use of    # 
# any derivative, and makes no representations or warranties, express #
# or implied, concerning the software, its suitability, fitness for   #
# a particular purpose or non-infringement.                           #
# In no event shall the authors of the software or GRL be responsible # 
# or liable for any loss or damage whatsoever arising in any way      # 
# directly or indirectly out of the use of this software or its       # 
# derivatives, even if advised of the possibility of such damage.     #
# Our software can be freely distributed under the conditions set out # 
# above, and must contain this copyright notice.                      #
#######################################################################

package gapAssemble;
require Exporter;

@ISA = qw(Exporter);
@EXPORT = qw(gapAssemble);


################################
#
# gapAssemble -
#
# Enter a file of filenames into
# project version zero at hardcoded
# params. Log to logfile
#
################################

sub gapAssemble {

    my ($directory, $project, $fofn, $logfile)=@_;

    ################################
    # 
    # Default values
    # 
    ################################

    my $version = 0;
    my $working_vers = 'X';
    my $previous_vers = 1;

    my $error_message = '';
    my $warn_message = '';
    my $output_message = '';
    my $echo = 1;
    ($project)=~tr/a-z/A-Z/;

    ################################
    # 
    # Define full pathname for log file
    # 
    ################################

    if ($logfile !~ /^\//) {
	$logfile = "$directory/$logfile"; 
    } 

    ################################
    # 
    # Tell the user the name of the log file
    # 
    ################################
    print "gapAssemble: Logfile \"$logfile\"\n";

    ################################
    # 
    # Log what we do to specified text file
    # 
    ################################

    unless (&logEntry("&gapAssemble ($directory, $project, $fofn, $logfile)", $logfile, $echo)) {
	warn "WARNING: Unable to make log file: \"$logfile\"\n";
	$echo = 4; # echo to STDOUT only
    }


    ################################
    # 
    # Check integrity of file of file names
    # NOTE: Will remove full pathname as gap
    # can't handle names over 40 chars.
    # 
    ################################

    if (&sanityFofn($directory, \$fofn, \$error_message)) {
	&logEntry("Sanity check of fofn OK", $logfile, $echo);
    } else {
	&logEntry("$error_message", $logfile, $echo);
	return 0;
    }

    ################################
    # 
    # Check if gap database files exist in this directory
    # If not, create them
    # 
    ################################

    unless (&existsGap($directory, $project, $version)) {
	
	&logEntry("Gap database for $project not found -- Creating it...\n", $logfile, $echo);

	unless ( &createGap ($directory, $project, \$output_message, \$error_message) ) {
	    &logEntry("$error_message", $logfile, $echo);
	    return 0;
	}
	&logEntry("Created Gap database\n$output_message", $logfile, 2); 
	$output_message='';
    } 


    ################################
    # 
    # Check integrity of database files (some redundancy from the above)
    # 
    ################################

    if (&sanityGap($directory, $project, $version, \$error_message)) {
	&logEntry("Sanity check of gap database OK", $logfile, $echo);
    } else {
	&logEntry("$error_message", $logfile, $echo);
	return 0;
    }

    ################################
    # 
    # Remove old working version of gap database (if found)
    # 
    ################################
    if (&removeGap($directory, $project, $working_vers, \$error_message)) {
	&logEntry("Removed old working version OK", $logfile, $echo);
    } else {
	&logEntry("$error_message", $logfile, $echo);
	return 0;
    }
    
    ################################
    # 
    # Make working version copy of original.
    # 
    ################################
    if (&copyGap($directory, $project, $version, $working_vers, \$error_message)) {
	&logEntry("Copied original version of database to working version OK", $logfile, $echo);
    } else {
	&logEntry("$error_message", $logfile, $echo);
	return 0;
    }

    ################################
    # 
    # Go enter that file of filenames into working version
    # 
    ################################
    my $rtn_cde = 
	&assembleFofn($directory, $project, $working_vers, $fofn, \$error_message, \$warn_message, \$output_message);

    &logEntry("$output_message", $logfile, 2); # Put general gap output (regardless of error) into log file only

    if ( $rtn_cde == 0 ) { # Errors and possibly warnings found
	&logEntry("$error_message", $logfile, $echo);
	if ($warn_message ne '') {
	    &logEntry("$warn_message", $logfile, $echo);
	}
	return 0; # Don't continue
    }
    elsif ($rtn_cde == 1 ) { #  warnings found
	&logEntry("$warn_message", $logfile, $echo);
    } else { #Everthing was fine
	&logEntry("Assembled file of filenames OK", $logfile, $echo);
    }

    ################################
    # 
    # If assembly sucessful, make copy of original version for backup,
    # then copy working version over the original.
    # 
    ################################

    if (&moveGap($directory, $project, $version, $previous_vers, \$error_message)) {
	&logEntry("Backed up original version to version $previous_vers", $logfile, $echo);
    } else {
	&logEntry("$error_message", $logfile, $echo);
	return 0;
    }


    if (&moveGap($directory, $project, $working_vers, $version, \$error_message)) {
	&logEntry("Moved working version over original version OK", $logfile, $echo);
    } else {
	&logEntry("$error_message", $logfile, $echo);
	return 0;
    }

    ################################
    #     
    # Success
    # 
    ################################

    1;

}

###########################
# 
# logEntry ($text, $logfile, $echo)
#
# log event/error messages
# save to file if required
# Display text according to code
#
#  0 - No output
#  1 - To file and STDOUT
#  2 - Just to file 
#  4 - Just to STDOUT
#
###########################
sub logEntry {

    my ($text, $logfile, $echo)=@_;

    # Check for non null text entry
    return if $text eq '';
    chomp($text);

    # Check for OK file name
    if ($logfile eq '') {
	warn "No filename specified: Echo to stdout only";
	$echo = 4;
    }

    # Check display code
    if ($echo !~ /^\d$/) {
	warn "No displaty(echo) code supplied";
	$echo = 1;
    }

    # Return if no output set
    if ($echo == 0) {
	return;
    }

    # Open log file
    if ( $echo == 1 || $echo == 2 ) {

	if ( -e "$logfile" ) {
	    open (LOG, ">>$logfile")
		|| warn "&logEntry in gapAssemble couldn't create log file"
		    && return 0;
	} else {
	    open (LOG, ">$logfile")
		|| warn "&logEntry in gapAssemble couldn't create log file"
		    && return 0;
	}

	print LOG scalar(localtime), " $text\n";
	close (LOG);
    }
	

    # Print to STDOUT
    if ($echo == 1 || $echo == 4 ) {
	print STDOUT scalar(localtime), " $text\n";
    }

    1;

}

#
# Check for existance of the file of filenames.
# If fofn contains a path name remove the path.
#
#
sub sanityFofn {

    my ($directory, $fofn_ref, $error_ref)=@_;

    my $fofn;
    if ($$fofn_ref !~ /^\//) {
	$fofn = "$directory/$fofn"; # For checking only.
    } else {
	$fofn=$$fofn_ref;	# Use path for checking here.
	($$fofn_ref)=~ s/.*\///; # Remove the path for gap purposes.
    }


    unless  (-e "$fofn" ) {
	$$error_ref = "&sanityFofn: File: \"$directory/$fofn\" not found\n";
	return 0;
    }

    if  (-z "$fofn" ) {
	$$error_ref = "&sanityFofn: File: \"$directory/$fofn\" not found\n";
	return 0;
    }

    
    1;

}


sub existsGap {

    my ($directory, $project, $version)=@_;

    foreach ("$directory/$project.$version",
	     "$directory/$project.$version.aux") {

	stat($_);

	if ( ! -e _ ) {
	    return 0;
	}
    }
    
    return 1;

}


sub sanityGap {

    my ($directory, $project, $version, $error_ref)=@_;

    unless ( -d "$directory" ) {
	$$error_ref = "&sanityGap in gapAssemble.pm: Directory: \"$directory\': not found";
	return 0;
    }

    
    foreach ("$directory/$project.$version",
	     "$directory/$project.$version.aux") {

	stat($_);

	unless ( -e _ ) {
	    $$error_ref = "&sanityGap: File: \"$_\" not found\n";
	    return 0;
	}

	if ( -z _ ) {
	    $$error_ref = "&sanityGap: File: \"$_\" zero length\n";
	    return 0;
	}

	unless ( -w _ ) {
	    $$error_ref = "&sanityGap: File: \"$_\" not writable\n";
	    return 0;
	}
       
    }
    
    if ( -e "$directory/$project.$version.BUSY") {
	$$error_ref = "&sanityGap: Project: \"$project\" Version: \"$version\" is BUSY\n";
	return 0;
    }

    1;

}


sub copyGap {

    my ($directory, $project, $old_version, $new_version, $error_ref)=@_;

    `/bin/cp "$directory/$project.$old_version.aux" "$directory/$project.$new_version.aux"`;
    if ($?!=0) {
	$$error_ref = "&copyGap: /bin/cp spoke: $!\n";
	return 0;
    }

    `/bin/cp "$directory/$project.$old_version" "$directory/$project.$new_version"`; # 
    if ($?!=0) {
	$$error_ref = "&copyGap: /bin/cp spoke: $!\n";
	return 0;
    }

    1;

}


sub moveGap {

    my ($directory, $project, $old_version, $new_version, $error_ref)=@_;

    `/bin/mv -f "$directory/$project.$old_version.aux" "$directory/$project.$new_version.aux"`;    
    if ($?!=0) {
	$$error_ref = "&copyGap: /bin/mv -f spoke: $!\n";
	return 0;
    }

    `/bin/mv -f "$directory/$project.$old_version" "$directory/$project.$new_version"`;

    if ($?!=0) {
	$$error_ref = "&copyGap: /bin/mv -f spoke: $!\n";
	return 0;
    }

    1;

}


sub removeGap {

    my ($directory, $project, $version, $error_ref)=@_;

    foreach (qw("$directory/$project.$version.aux" 
		"$directory/$project.$version")) { 		       

	if ( -e "$_" ) {
	    unless (unlink("$_")) {
		$$error_ref = "&removeGap: Coudn't remove file: \"$_\": $!\n";
		return 0;
	    }
	}
    }

    1;
}


sub createGap {

    my ($directory, $project, $output_ref, $error_ref) = @_;

    # Tell the output what the subroutine received
    $$output_ref .= "Directory: \"$directory\"  Project: \"$project\"\n";

    # Check if directory exists, if not, return 0 with error message
    if ( ! -d "$directory") {
	$$error_ref .= "Directory: \"$directory\" not found\n";
	return 0;
    }

    # translate project name to uppercase
    ($project) =~ tr/a-z/A-Z/;
    ($project) =~ s/^\s*(\S+)/$1/;
    
    # extrapolate full gap db file names from directory path
    @gap_files = ("$directory/$project.0",
		  "$directory/$project.0.aux");
    
    # check in case these files exist already, if so return error
    foreach (@gap_files) {
	if ( -e "$_" ) {
	    $$error_ref .= "Gap file: \"$_\" already exists\n";
	    return 0;
	}
    }

    # Check we have write access to this directory, else error
    if ( ! -w "$directory") {
	$$error_ref .= "Do not have write access to directory: \"$directory\"\n";
	return 0;
    }

    # Define gap dialogue string for creating db
    my $gap_dialog= qq {3
n          # Open existing database
$project
y          # Database is for DNA

!
};

    ###################################
    # 
    # Change dir to project if not already there
    # 
    ###################################

    my $pop = `pwd`;

    if ($pop ne $directory) {
        unless (chdir ("$directory")) {
            $$error_ref = "Couldn't chdir to \"$directory\": $!\n";
            return 0;
        }
    }

    ###################################
    # 
    # Open gap and pump in the commands
    # 
    ###################################

    unless ( open (GAP, "echo '$gap_dialog' | gap |")) {
        $$error_ref = "Couldn't open gap: $!\n";
        return 0;
    }

    ###################################
    # 
    # Record what came out
    # 
    ###################################

    while(<GAP>) {

        $$output_ref .= $_;
    }
    
    close(GAP);

    ###################################
    # 
    # check gap db files for non zero length else return error
    # 
    ###################################

    foreach (@gap_files) {
	if ( -z "$_" ) {
	    $$error_ref .= "Gap file: \"$_\" zero length\n";
	    return 0;
	}
    }
    
    # return no error
    return 1;

}


sub assembleFofn {

    my ($directory, $project, $version, $fofn, $error_ref, $warn_ref, $output_ref)=@_;

    # 
    # Hard coded gap parameters
    # 

    my $min_init_match = 20;
    my $max_read_pads = 25;
    my $max_contig_pads = 25;
    my $max_mismatch = 8;
    my $fails_file = "/tmp/_fails_$$";
    my $error_count = 0;
    my $warn_count = 0;

    # 
    # Put these in the output
    # 

    $$output_ref .= qq{
GAP PARAMETERS:

min_init_match $min_init_match
max_read_pads $max_read_pads
max_contig_pads $max_contig_pads
max_mismatch $max_mismatch
fails_file $fails_file
error_count $error_count
warn_count $warn_count


};

    # 
    # Define gap interaction string
    # 

    my $gap_dialog = 
qq{3
y
$project
$version

20
y   # Permit Entry
1   # Hide all alignments
y   # Use file of file names 
$fofn
$fails_file
1   # Perform normal shotgun assembly
y   # Permit joins
2   # Enter all readings
$min_init_match
$max_read_pads
$max_contig_pads
$max_mismatch

!
};

    ###################################
    # 
    # Change dir to project if not already there
    # 
    ###################################

    my $pop = `pwd`;

    if ($pop ne $directory) {
	unless (chdir ("$directory")) {
	    $$error_ref = "Couldn't chdir to \"$directory\": $!\n";
	    return 0;
	}
    }

    ###################################
    # 
    # Open gap and pump in the commands
    # 
    ###################################

    unless ( open (GAP, "echo '$gap_dialog' | gap |")) {
	$$error_ref = "Couldn't open gap: $!\n";
	return 0;
    }


    ###################################
    # 
    # Check what came out
    # 
    ###################################

    my $error;

    while(<GAP>) {
	
	$$output_ref .= $_;

	foreach $error ("not consistent",
			"Error encountered opening database files") {

	    if ( /$error/i ) {
		$$error_ref .= "Gap Error: $_";
		$error_count++;
	    }
	}
    }

    close(GAP);

 
    ###################################
    # 
    # Check Gap's exit status
    # 
    ###################################

    if ( $? != 0 ) {
	$$error_ref .= "Gap exited with non zero return code (it crashed?)";
    }

    ###################################
    # 
    # Open failures file if it has
    # been written to and interpret
    # the contents
    # 
    ###################################

    unless ( -z "$fails_file" ) {
    
	unless (open (FAILS, "$fails_file")) {
	    $$warn_ref .= "Couldn't open failure file: $!";
	    $warn_count++;
	}
	
	while (<FAILS>) {
	 
	    chomp;
	    my ($read, $code) = split('\s+', $_);
	    my $reason = &interpretGapErrorCode($code);
	    $$warn_ref .= "Error for reading: $read : \"$reason\"\n";
	    $warn_count++;

	}
	
	close(FAILS);

    }

    ###################################
    # 
    # Remove the now used failures
    # file
    # 
    ###################################
    
    unlink("$fails_file");

    if ($error_count > 0) {
	return 0;
    } 
    elsif ($warn_count > 0) {
	return 1;
    } 
    else {
	return 2;
    }

}

sub interpretGapErrorCode {

    my ($code) = @_;

    return "unknown (Invalid error code $code)" unless $code =~ /\d+/;

    if ($code == 0) {
	return 'file not found';
    } 
    elsif ($code == 1) {
	return 'read too short';
    }
    elsif ($code == 2 || $code == 5) {
	return 'failed to align and not entered';
    }
    elsif ($code == 3) {
	return 'failed on entry';
    }
    elsif ($code == 4) {
	return 'failed to align but entered';
    }
    elsif ($code eq 'X') {
	return 'no match found during masked assembly';
    }
    else {
	return 'unknown (unidentified error code)';
    }

}

sub debug { print @_; }


1;
