#!/usr/bin/env perl

=head1 NAME

=head1 SYNOPSIS

  runGapResolution.pl [options] <aceFile> <454Scaffolds.txt> <454NewblerMetrics>
    <454AllContigs.fna> <454AllContigs.qual>

  Options:
  -od <path>    Output directory (optional; default is current dir)
  -np <number>  Specify number of parallel processes to use (optional; default is non-parallelized mode)
  -p <name>     Used for naming primers (optional)
  -pd <path>    Absolute full path to phd_dir for Sanger data (optional)
  -c <file>     Config file (optional; default is either specified using environment variable GAPRES_CONFIG or in installation path)
  -h            Detailed message (optional)

=head1 DESCRIPTION

Head wrapper to execute the Gap Resolution sub system.

=head1 DEPENDENCIES

The following scripts (configurable in config file) must exist in the same
path as runGapResolution.pl unless the path to the script is defined in the config
file:

  * createGapResProject.pl
  * idContigRepeats.pl
  * getSubProjReads.pl
  * newblerAssemSubProject.pl
  * validateSubProject.pl
  * createSubProjectPrimers.pl
  * createAnchorTagsForAce.pl

A default config file named gapRes.config residing in
<installPath>/config is used to specify the following parameters:

  script.createGapResProject=createGapResProject.pl
  script.idContigRepeats=idContigRepeats.pl
  script.getSubProjReads=getSubProjReads.pl
  script.newblerAssemSubProject=newblerAssemSubProject.pl
  script.validateSubProject=validateSubProject.pl
  script.createSubProjectFakes=createSubProjectFakes.pl
  script.createSubProjectPrimers=createSubProjectPrimers.pl
  script.createAnchorTagsForAce=createAnchorTagsForAce.pl
  script.createAnchorTagsForSubprojectAce=createAnchorTagsForSubprojectAce.pl
  runGapResolution.addAnchorTagsToMainAceFile=1
  runGapResolution.addAnchorTagsToSubprojectAceFile=1
  
  The specify individual software components to run, modify the following
  parameters in the config file, setting 1 to execute, 0 to skip.
  
  execute.createGapResProject.pl=1
  execute.idContigRepeats.pl=1
  execute.getSubProjectReads.pl=1
  execute.newblerAssemSubProject.pl=1
  execute.validateSubProject.pl=1
  execute.createSubProjectPrimers.pl=1 
  execute.createAnchorTagsForAce.pl=1
  execute.createAnchorTagsForSubprojectAce.pl=1
  
To specify your own config file, set the environment variable GAPRES_CONFIG to
the path and name of the file.

=head1 VERSION

$Revision$

$Date$

=head1 AUTHOR(S)

Stephan Trong

=head1 HISTORY

=over

=item *

S.Trong 2009/01/07 creation

=item *

S.Trong 2009/08/09
  1. Added option to execute individual s/w components and creation of warnings file.
  2. Added -c flag to specify config file.
  3. Added calling of createSubProjectFakes.pl, createAnchorTagsforAce.pl,
     createAnchorTagsForSubprojectAce.pl.
  4. Warnings file created if failure occurs.

=item *

S.Trong 2009/12/28 - added option to parallelize processes.

=back

=cut

use strict;
use warnings;
use Pod::Usage;
use Cwd;
use Cwd qw(abs_path);
use Carp;
use Carp qw(cluck);
use Getopt::Long;
use File::Path;
use File::Copy;
use File::Basename;
use IO::File;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use PGF::Utilities::Properties;
use PGF::Utilities::RunProcess qw(runProcess);
use PGF::Utilities::Logger;
use PGF::Utilities::JobManager;
use PGF::Utilities::FileUtility qw(getRealPath);
use PGF::GapResolution::Warnings;
use vars qw( $optHelp $optOutputDir $optPhdDir $optConfigFile
    $optMaxParallelProcess $optPrimerNaming );

#============================================================================#
# INPUT VALIDATION
#============================================================================#
my $programExecution = abs_path(dirname($0))."/".basename($0)." @ARGV";

if( !GetOptions(
        "od=s"=>\$optOutputDir,
        "pd=s"=>\$optPhdDir,
        "c=s"=>\$optConfigFile,
        "np=n"=>\$optMaxParallelProcess,
        "p=s"=>\$optPrimerNaming,
        "h"=>\$optHelp,
    )
) {
    printhelp(1);
}

printhelp(2) if $optHelp;

if ( @ARGV != 5 ) {
    my $errMsg = "Required input parameters are missing in command line.";
    print STDERR "$errMsg\n";
    printhelp(1);
}

# If -c option to define your own config file is specified, then
# set environment variable to this file.
#
if ($optConfigFile ) {
    if ( -s $optConfigFile) {
        $ENV{GAPRES_CONFIG} = getRealPath($optConfigFile);
    } else {
        print STDERR "Cannot find your specified config file $optConfigFile!\n";
        exit 1;
    }
}

if ( $optMaxParallelProcess && $optMaxParallelProcess < 2 ) {
    print STDERR "The value for -p (max parallel process) must be greater than one.\n";
    exit 1;
}

#============================================================================#
# INITIALIZE VARIABLES
#============================================================================#

my $DEBUG = 0;
my $configFile = defined $ENV{GAPRES_CONFIG} ?
    $ENV{GAPRES_CONFIG} : "$RealBin/../config/gapRes.config";
   $configFile = abs_path($configFile);
my $OBJ_PROPS = PGF::Utilities::Properties->new(-configFile=>$configFile);
   $OBJ_PROPS->setExceptionIfEntryNotFound(1); # confess if entry in config file
                                               # is not found.
my $OBJ_LOGGER = PGF::Utilities::Logger->new();
my $outputDir = $optOutputDir ? $optOutputDir : getcwd;
   $outputDir = abs_path($outputDir);
my $OBJ_WARNINGS = PGF::GapResolution::Warnings->new(
    path=>$outputDir, logger=>$OBJ_LOGGER);

my $aceFile = $ARGV[0];
my $scaffoldFile = $ARGV[1];
my $metricsFile = $ARGV[2];
my $contigsFasta = $ARGV[3];
my $contigsQual = $ARGV[4];

#============================================================================#
# VALIDATE INPUTS
#============================================================================#
    
# Check for valid inputs.
#
if ( !-s $aceFile ) {
    print STDERR "The input ace file $aceFile does not exist or is zero size.\n";
    exit 1;
}
    
if ( !-s $scaffoldFile ) {
    print STDERR "The input scaffold file $scaffoldFile does not exist or is zero size.\n";
    exit 1;
}

if ( !-s $metricsFile ) {
    print STDERR "The input 454 metrics file $metricsFile does not exist or is zero size.\n";
    exit 1;
}

if ( !-s $contigsFasta ) {
    print STDERR "The input contigs fasta file $contigsFasta does not exist or is zero size.\n";
    exit 1;
}

if ( !-s $contigsQual ) {
    print STDERR "The input contigs qual file $contigsQual does not exist or is zero size.\n";
    exit 1;
}

if ($optPhdDir && ! -e $optPhdDir){
    print STDERR "phdDir $optPhdDir  does not exist\n";
}

#============================================================================#
# INITIALIZATION
#============================================================================#

# cd into output dir if different than current directory,.
#
if ( $outputDir ne getcwd ) {
    
    mkpath($outputDir) if ( !-e $outputDir );
    $outputDir = getcwd."/$outputDir" if $outputDir !~ /\//;
    
    # set input files to real paths.
    $aceFile = getRealPath($aceFile);
    $scaffoldFile = getRealPath($scaffoldFile);
    $metricsFile = getRealPath($metricsFile);
    $contigsFasta = getRealPath($contigsFasta);
    $contigsQual = getRealPath($contigsQual);
    
}

my $logfile = "$outputDir/".basename($0).".log";

# Set path for logging.
#
setFileForLogging($logfile);

# Log execution into log file.
#
logExecution($programExecution, $configFile);

# log cd dir if output dir is different than current dir.
#
if ( $outputDir ne getcwd ) {
    logOutput("cd $outputDir",1);
    chdir $outputDir if $optOutputDir;
}

my $gapDirsFile = '';

#============================================================================#
# MAIN
#============================================================================#

print "\n";

# Create gap project directories (also known as sub project directories)
#
runCreateGapResProject($aceFile, $scaffoldFile, $metricsFile)
    if runThisComponent("createGapResProject.pl");

# Identify contig repeats
#
runIdContigRepeats($aceFile, $scaffoldFile)
    if runThisComponent("idContigRepeats.pl");

if ( $optMaxParallelProcess ) {
    runComponentsInParallel($optMaxParallelProcess, $aceFile, 
        $scaffoldFile, $contigsFasta, $contigsQual, $outputDir);
    
} else {

    # Capture reads for each gap dir.
    #
    runGetSubProjectReads($contigsFasta, $contigsQual)
        if runThisComponent("getSubProjectReads.pl");
    
    # Reassemble each gap dir.
    #
    runNewblerAssemSubproject()
        if runThisComponent("newblerAssemSubProject.pl");
    
    # Perform gap dir validation.
    #
    runValidateSubProject()
        if runThisComponent("validateSubProject.pl");
        
    # Create fake reads for closed gaps.
    #
    runCreateSubProjectFakes()
        if runThisComponent("createSubProjectFakes.pl");
    
    # Create primer definition file for designing PCR primers.
    #
    runCreateSubProjectPrimerInfo()
        if runThisComponent("createSubProjectPrimerInfo.pl");
    
    # Create primers for each gap dir
    #
    runCreateSubProjectPrimers()
        if runThisComponent("createSubProjectPrimers.pl");
        
    # Rename primers in primers.txt file
    #
    if ( $optPrimerNaming ) { 
        renamePrimers($optPrimerNaming);
    }
    
    # Tag main ace file with all anchor sequence locations from gap dirs.
    #
    if ( $OBJ_PROPS->getProperty("runGapResolution.addAnchorTagsToMainAceFile") &&
        runThisComponent("createAnchorTagsForAce.pl") ) {
        addAnchorTagsForMainAce($aceFile, $scaffoldFile);
    }
    if ( $OBJ_PROPS->getProperty("runGapResolution.addAnchorTagsToSubprojectAceFile") &&
        runThisComponent("createAnchorTagsForSubprojectAce.pl") ) {
        addAnchorTagsForSubProjectAce();
    }

}

# If warning messages present, then save in .warnings.out file.
#
$OBJ_WARNINGS->createFile() if $OBJ_WARNINGS->getNumberOfWarnings;

exit 0;

#============================================================================#
# SUBROUTINES
#============================================================================#
sub runComponentsInParallel {
    
    my $maxParallelProcess = shift;
    my $aceFile = shift;
    my $scaffoldFile = shift;
    my $contigsFasta = shift;
    my $contigsQual = shift;
    my $outputDir = shift;
    
    my $assemDir = $OBJ_PROPS->getProperty("createGapResProject.assemInfoDirName");
    my $gapDirsFile = $OBJ_PROPS->getProperty("createGapResProject.subProjectFoFName");
       $gapDirsFile = "$assemDir/$gapDirsFile" unless $gapDirsFile =~ /\//;
    my $readInfoFile = "$assemDir/".$OBJ_PROPS->getProperty("createGapResProject.readInfoFileName");
    my $validationFile = $outputDir."/".$OBJ_PROPS->getProperty("validateSubProject.validationSummaryFile");
    my $script = getScript("script.gapResParallel");
    my $tmpValidationFile = "validation.local";
    my $tmpLogDir = "$outputDir/logdir.tmp";
    my $tmpReadInfoFileDir = "$outputDir/contigReadInfoFiles.tmp.$$";
    my $contigReadInfoFileOfFiles = "$tmpReadInfoFileDir/contigReadFileOfFiles.fof";
    my @jobs = ();
    
    if ( !-s $gapDirsFile ) {
        logError("The $gapDirsFile does not exist or is zero size.",1);
    }
    
    mkdir( $tmpLogDir, 0777 ) if !-e $tmpLogDir;
    mkdir( $tmpReadInfoFileDir, 0777 ) if !-e $tmpReadInfoFileDir;
    
    # Populate $tmpReadInfoFileDir with individual contig read info files.
    #
    createContigReadInfoFiles($readInfoFile, $tmpReadInfoFileDir,
        $contigReadInfoFileOfFiles);
    
    my $args = "";
       $args .= " -od $optOutputDir" if $optOutputDir;
       $args .= " -pd $optPhdDir" if $optPhdDir;
       $args .= " -pn $optPrimerNaming" if $optPrimerNaming;
       $args .= " -c $ENV{GAPRES_CONFIG}" if $optConfigFile;
       $args .= " -ld $tmpLogDir";
       $args .= " -cf $contigReadInfoFileOfFiles";
       $args .= " $aceFile $scaffoldFile $contigsFasta $contigsQual";
       $args =~ s/^ //;
    
    unless( open IFILE, $gapDirsFile ) {
        my $errMsg = "ERROR: failed to open file $gapDirsFile.\n";
        logError($errMsg, 1);
    }
    chomp( my @gapDirs = <IFILE> );
    close IFILE;
    
    @gapDirs = grep /\w+/, @gapDirs;
    
    if ( !@gapDirs ) {
        my $msg = "WARNING: no valid gap dirs found.";
        logOutput($msg);
        return;
    }
    
    # Create jobs to run in parallel.
    #
    foreach my $gapDir (@gapDirs) {
        my $localValidationFile = "$gapDir/$tmpValidationFile";
        push @jobs, "$script $args $gapDir $localValidationFile";
    }
    
    if ( @jobs ) {
        my $msg = "Running the following commands in parallel ($maxParallelProcess processors) ...\n\n";
           $msg .= join "\n\n", @jobs;
           $msg .= "\n";
        logOutput($msg);
    }

    # Run jobs in parallel.
    #
    my $objJobManager = PGF::Utilities::JobManager->new();
    $objJobManager->setMaxNumberOfParallelProcesses($maxParallelProcess);
    $objJobManager->turnOnDebugging;
    
    $objJobManager->parallel(@jobs);
    
    # Create validation summary file.  This file is generated by concatenating
    # the local validation files within each of the sub project directories.
    #
    if ( runThisComponent("validateSubProject.pl") ) {
        unless( open OFILE, ">$validationFile" ) {
            my $errMsg = "ERROR: failed to create file $validationFile.\n";
            logError($errMsg, 1);
        }
        foreach my $gapDir (@gapDirs) {
            my $localValidationFile = "$gapDir/$tmpValidationFile";
            if ( -s $localValidationFile ) {
                if ( open IFILE, $localValidationFile ) {
                    while (<IFILE>) {
                        print OFILE $_;
                    }
                    close IFILE;
                }
            }
        }
        close OFILE;
        
        logOutputFileCreation($validationFile);
    }

    # Merge tmp log files to create original log files in $outputDir.
    #
    mergeLogFiles($tmpLogDir, $outputDir);

    # Remove tmp log dir.
    #
    rmtree $tmpLogDir if -e $tmpLogDir;
    
    # Remove tmp contig read info file dir.
    #
    rmtree $tmpReadInfoFileDir if -e $tmpReadInfoFileDir;
    
}

#============================================================================#
sub mergeLogFiles {
    
    my $logDir = shift;
    my $outputDir = shift;
    
    my %logFH = ();
    my @logFiles = ();
    my @warningsFiles = ();
    my @anchorTags = ();
    my $dh;

    my $warningsFile = $OBJ_PROPS->getProperty("errorHandling.warningsFile");
    my $outputTagFile = "$outputDir/".
        $OBJ_PROPS->getProperty("createAnchorTagsForAce.outputFile");
    
    # Get all log files in tmp log dir.
    # 
    unless (opendir $dh, $logDir) {
        logOutput("Warning: failed to read $logDir: $!",1);
	closedir $dh;
	return;
    }

    # Open real log files for append.
    #
    while (my $file = readdir($dh) ) {
        if ( $file =~ /^(.+)\.log\./ ) {
	    my $fileName = basename($1); # get s/w component names
	    if ( !$logFH{$fileName} ) {
	        $logFH{$fileName} = new IO::File ">>$outputDir/$fileName.log";
  	    }
            push @logFiles, { file=>"$logDir/$file",
                              name=>$fileName,
                            };
        } elsif ( $file =~ /^(.+)\.warnings\./ ) {
	    my $fileName = basename($1); # get s/w component names
            push @warningsFiles, { file=>"$logDir/$file", name=>$fileName };
        } elsif ( $file =~ /^anchorTags\./ ) {
            push @anchorTags, "$logDir/$file";
        }
    }
    closedir $dh;

    # Merge tmp log files to real log files.
    #
    foreach my $refLogFile (@logFiles) {
        my $file = $refLogFile->{file};
        my $fileName = $refLogFile->{name};
	unless (open IFILE, $file) {
	    logError("Failed to open logfile $file for merging\n");
	    next;
        }
	while (my $line = <IFILE>) {
	    my $fh = $logFH{$fileName};
            print $fh $line;
        }
	close IFILE;
    }

    # Close real log files.
    #
    foreach my $logFileName ( keys %logFH ) {
        my $fh = $logFH{$logFileName};
	$fh->close;
    }
    
    # If warnings file exist in tmp logdir, then add entries to real
    # warnings file defined in gapRes.config.
    #
    if ( @warningsFiles ) {
        if (open OFILE, ">>$warningsFile") {
	    foreach my $refWarningsFile (@warningsFiles) {
                my $file = $refWarningsFile->{file};
                my $fileName = $refWarningsFile->{name};
                unless (open IFILE, $file) {
                    logError("Failed to open logfile $file for merging\n");
                    next;
                }
                while (my $line = <IFILE>) {
                    print OFILE $line;
                }
                close IFILE;
            }
            close OFILE;
        } else {
            logError("Failed to open logfile $warningsFile for merging\n");
        }
    }
    
    # Merge anchorTags.
    #
    if ( @anchorTags ) {
        if (open OFILE, ">$outputTagFile") {
	    foreach my $file (@anchorTags) {
                unless (open IFILE, $file) {
                    logError("Failed to open logfile $file for merging\n");
                    next;
                }
                while (my $line = <IFILE>) {
                    print OFILE $line;
                }
                close IFILE;
            }
            close OFILE;
        } else {
            logError("Failed to open logfile $outputTagFile for merging\n");
        }
    }
    
}

#============================================================================#
sub runThisComponent {
    my $componentName = shift;
    
    #
    # Check if component should be executed based on what's defined in the
    # config file with the prefix execute
    #
    
    my $runComponent = 0;
    my $findComponent = $OBJ_PROPS->getProperty("execute.$componentName");
       $findComponent =~ s/\s+$//;
    
    # If component is not defined in config file or if component is defined
    # and is set to 1, then return 1 (yes, run the component).
    #
    if ( !length $findComponent ||
        ( length $findComponent && $findComponent ) ) {
        $runComponent = 1;
    }
    return $runComponent;
}
    
#============================================================================#
sub runCreateGapResProject {
    
    my $aceFile = shift;
    my $scaffoldFile = shift;
    my $metricsFile = shift;
    
    # Run createGapResProject.pl
    #
    my $script = getScript("script.createGapResProject");
    my $params = "$aceFile $scaffoldFile $metricsFile";
    my $cmd = "$script $params";
    my %processInfo = runCommand($cmd);
    
    # Check for successful completion.
    #
    checkProcess(%processInfo);
    
    # Check results.
    #
    my $assemDir = $OBJ_PROPS->getProperty("createGapResProject.assemInfoDirName");
    my $gapDirsFile = $OBJ_PROPS->getProperty("createGapResProject.subProjectFoFName");
    my $libInfoFile = $OBJ_PROPS->getProperty("createGapResProject.libInfoFileName");
    my $readInfoFile = $OBJ_PROPS->getProperty("createGapResProject.readInfoFileName");
    my $contigOrientationFile = 
        $OBJ_PROPS->getProperty("createGapResProject.contigOrientationFileName");
        
    # If file names specified in config file doesn't contain paths, then
    # prepend assemInfo path to it.
    #
    $gapDirsFile = "$assemDir/$gapDirsFile" unless $gapDirsFile =~ /\//;
    $libInfoFile = "$assemDir/$libInfoFile" unless $libInfoFile =~ /\//;
    $readInfoFile = "$assemDir/$readInfoFile" unless $readInfoFile =~ /\//;
    $contigOrientationFile = "$assemDir/$contigOrientationFile"
        unless $contigOrientationFile =~ /\//;
    
    logError("ERROR: failed to create $assemDir.\n",1) if !-d $assemDir;
    logOutputFileCreation($gapDirsFile);    
    logOutputFileCreation($libInfoFile);    
    logOutputFileCreation($readInfoFile);    
    logOutputFileCreation($contigOrientationFile);
    
}
    
#============================================================================#
sub runIdContigRepeats {
    
    my $aceFile = shift;
    my $scaffoldFile = shift;
    
    # Run idcontigRepeats.pl
    #
    my $assemDir = $OBJ_PROPS->getProperty("createGapResProject.assemInfoDirName");
    my $gapDirsFile = $OBJ_PROPS->getProperty("createGapResProject.subProjectFoFName");
    my $libInfoFile = $OBJ_PROPS->getProperty("createGapResProject.libInfoFileName");
        
    # If file names specified in config file doesn't contain paths, then
    # prepend assemInfo path to it.
    #
    $gapDirsFile = "$assemDir/$gapDirsFile" unless $gapDirsFile =~ /\//;    
    $libInfoFile = "$assemDir/$libInfoFile" unless $libInfoFile =~ /\//;    
        
    my $script = getScript("script.idContigRepeats");
    my $params = "-ace $aceFile ".
                 "-subdirs $gapDirsFile ".
                 "-scaff $scaffoldFile ".
                 "-lib $libInfoFile ".
                 "-od .";
    my $cmd = "$script $params";
    my %processInfo = runCommand($cmd);
    
    # Check for successful completion.
    #
    checkProcess(%processInfo);
    
    # Check results ...
    #
    
    # check sub project directory creation.
    #
    unless( open FH, $gapDirsFile ) {
        logError("ERROR: failed to open $gapDirsFile: $!\n", 1);
    }
    chomp( my @gapDirs = <FH> );
    close FH;
    @gapDirs = grep /\w/, @gapDirs;
    
    foreach my $gapDir (@gapDirs) {
        if ( !-d $gapDir ) {
            logError("ERROR: failed to create gap dir $gapDir.");
        }
        # check for existence of scaffinfo.txt file
        #
        my $scaffInfoFile = $gapDir.'/'.
            $OBJ_PROPS->getProperty("idContigRepeats.scaffFileName");
        if ( !-s $scaffInfoFile ) {
            logError("ERROR: failed to create scaff info file $scaffInfoFile.");
        }
    }
    
}
    
#============================================================================#
sub runGetSubProjectReads {
    
    my $contigsFasta = shift;
    my $contigsQual = shift;
    
    my $assemDir = $OBJ_PROPS->getProperty("createGapResProject.assemInfoDirName");
    my $readInfoFile = $OBJ_PROPS->getProperty("createGapResProject.readInfoFileName");
    my $gapDirsFile = $OBJ_PROPS->getProperty("createGapResProject.subProjectFoFName");
    my $libInfoFile = $OBJ_PROPS->getProperty("createGapResProject.libInfoFileName");
               
    # If file names specified in config file doesn't contain paths, then
    # prepend assemInfo path to it.
    #
    $readInfoFile = "$assemDir/$readInfoFile" unless $readInfoFile =~ /\//;    
    $gapDirsFile = "$assemDir/$gapDirsFile" unless $gapDirsFile =~ /\//;    
    $libInfoFile = "$assemDir/$libInfoFile" unless $libInfoFile =~ /\//;    
    
    # Run createGapResProject.pl
    #
    my $script = getScript("script.getSubProjReads");
    my $params = "$readInfoFile $gapDirsFile $libInfoFile $contigsFasta $contigsQual";
    my $cmd = "$script $params";
    my %processInfo = runCommand($cmd);
    
    # Check for successful completion.
    #
    checkProcess(%processInfo);
    
}

#============================================================================#
sub runNewblerAssemSubproject {
    
    my $assemDir = $OBJ_PROPS->getProperty("createGapResProject.assemInfoDirName");
    my $gapDirsFile = $OBJ_PROPS->getProperty("createGapResProject.subProjectFoFName");
    my $sffInfoFile = $OBJ_PROPS->getProperty("createGapResProject.sffInfoFileName");
               
    # If file names specified in config file doesn't contain paths, then
    # prepend assemInfo path to it.
    #
    $gapDirsFile = "$assemDir/$gapDirsFile" unless $gapDirsFile =~ /\//;    
    $sffInfoFile = "$assemDir/$sffInfoFile" unless $sffInfoFile =~ /\//;    
    
    # Run createGapResProject.pl
    #
    my $script = getScript("script.newblerAssemSubProject");
    my $params = $optPhdDir ? "-p $optPhdDir " : '' ;
    $params .= "$gapDirsFile $sffInfoFile";
    my $cmd = "$script $params";
    my %processInfo = runCommand($cmd);
    
    # Check for successful completion.
    #
    checkProcess(%processInfo);
    
}

#============================================================================#
sub runValidateSubProject {
    
    my $assemDir = $OBJ_PROPS->getProperty("createGapResProject.assemInfoDirName");
    my $gapDirsFile = $OBJ_PROPS->getProperty("createGapResProject.subProjectFoFName");
    my $libInfoFile = $OBJ_PROPS->getProperty("createGapResProject.libInfoFileName");
    my $sffInfoFile = $OBJ_PROPS->getProperty("createGapResProject.sffInfoFileName");
    my $validationFile = $OBJ_PROPS->getProperty("validateSubProject.validationSummaryFile");
    
    # If file names specified in config file doesn't contain paths, then
    # prepend assemInfo path to it.
    #
    $gapDirsFile = "$assemDir/$gapDirsFile" unless $gapDirsFile =~ /\//;    
    $libInfoFile = "$assemDir/$libInfoFile" unless $libInfoFile =~ /\//;
    $sffInfoFile = "$assemDir/$sffInfoFile" unless $sffInfoFile =~ /\//;
    
    # Run createGapResProject.pl
    #
    my $script = getScript("script.validateSubProject");
    my $params = "$gapDirsFile $libInfoFile $sffInfoFile";
    my $cmd = "$script $params";
    my %processInfo = runCommand($cmd);
    
    # Check for successful completion.
    #
    checkProcess(%processInfo);
    
    logOutputFileCreation($validationFile);
    
}

#============================================================================#
sub runCreateSubProjectFakes {
    
    my $validationFile = $OBJ_PROPS->getProperty("validateSubProject.validationSummaryFile");
    
    # Run createSubProjectFakes.pl
    #
    my $script = getScript("script.createSubProjectFakes");
    my $params = "$validationFile";
    my $cmd = "$script $params";
    my %processInfo = runCommand($cmd);
    
    # Check for successful completion.
    #
    checkProcess(%processInfo);
    
}

#============================================================================#
sub runCreateSubProjectPrimerInfo {
    
    my $validationFile = $OBJ_PROPS->getProperty("validateSubProject.validationSummaryFile");
    
    # Run createSubProjectFakes.pl
    #
    my $script = getScript("script.createSubProjectPrimerInfo");
    my $params = "$validationFile";
    my $cmd = "$script $params";
    my %processInfo = runCommand($cmd);
    
    # Check for successful completion.
    #
    checkProcess(%processInfo);
    
}

#============================================================================#
sub runCreateSubProjectPrimers {
    
    my $assemDir = $OBJ_PROPS->getProperty("createGapResProject.assemInfoDirName");
    my $gapDirsFile = $OBJ_PROPS->getProperty("createGapResProject.subProjectFoFName");
    my $primersFile = $OBJ_PROPS->getProperty("createSubProjectPrimers.allProjectPrimersFile");
    
    # If file names specified in config file doesn't contain paths, then
    # prepend assemInfo path to it.
    #
    $gapDirsFile = "$assemDir/$gapDirsFile" unless $gapDirsFile =~ /\//;
    
    # Run createGapResProject.pl
    #
    my $script = getScript("script.createSubProjectPrimers");
    my $params = "-if $gapDirsFile";
    my $cmd = "$script $params";
    my %processInfo = runCommand($cmd);
    
    # Check for successful completion.
    #
    checkProcess(%processInfo);
    
    logOutputFileCreation($primersFile);
    
}

#============================================================================#
sub renamePrimers {
    
    my $primerName = shift;

    my $primersFile = $OBJ_PROPS->getProperty("createSubProjectPrimers.allProjectPrimersFile");
    
    my $script = getScript("script.renamePrimers");
    my $params = "$primerName $primersFile";
    
    my $cmd = "$script $params";
    my %processInfo = runCommand($cmd);
    
    # Check for successful completion.
    #
    checkProcess(%processInfo);

}
    
#============================================================================#
sub addAnchorTagsForMainAce {
    
    my $aceFile = shift;
    my $scaffoldFile = shift;
    
    my $assemDir = $OBJ_PROPS->getProperty("createGapResProject.assemInfoDirName");
    my $gapDirsFile = $OBJ_PROPS->getProperty("createGapResProject.subProjectFoFName");
    my $outputTagFile =
        $OBJ_PROPS->getProperty("createAnchorTagsForAce.outputFile");
    my $anchorTagType = 
        $OBJ_PROPS->getProperty("createAnchorTagsForAce.tagType");
    my $anchorLength = 
        $OBJ_PROPS->getProperty("idRepeatBoundary.uniqueAnchorLength");
    my $boundaryFileExtension = 
        $OBJ_PROPS->getProperty("idContigRepeats.boundaryFileExtension");
    my $allowTransferOfTags = 
        $OBJ_PROPS->getProperty("createAnchorTagsForAce.allowTransferOfTags");
    my $scaffInfoFileName = 
        $OBJ_PROPS->getProperty("idContigRepeats.scaffFileName");
        
    # If file names specified in config file doesn't contain paths, then
    # prepend assemInfo path to it.
    #
    $gapDirsFile = "$assemDir/$gapDirsFile" unless $gapDirsFile =~ /\//;
    $outputTagFile = abs_path($outputTagFile);
        
    # Run addAnchorTagsForAce.pl
    #
    my $script = getScript("script.createAnchorTagsForAce");
    my $params = "-a $aceFile $scaffoldFile $gapDirsFile $outputTagFile";
       
    my $cmd = "$script $params";
    my %processInfo = runCommand($cmd);
    
    # Check for successful completion.
    #
    checkProcess(%processInfo);
    
    logOutputFileCreation($outputTagFile);
     
}

#============================================================================#
sub addAnchorTagsForSubProjectAce {
    
    my $assemDir = $OBJ_PROPS->getProperty("createGapResProject.assemInfoDirName");
    my $gapDirsFile = $OBJ_PROPS->getProperty("createGapResProject.subProjectFoFName");
    
    # If file names specified in config file doesn't contain paths, then
    # prepend assemInfo path to it.
    #
    $gapDirsFile = "$assemDir/$gapDirsFile" unless $gapDirsFile =~ /\//;
    
    # Run addAnchorTagsToAce.pl
    #
    my $script = getScript("script.createAnchorTagsForSubprojectAce");
    my $params = "$gapDirsFile";
       
    my $cmd = "$script $params";
    my %processInfo = runCommand($cmd);
    
    # Check for successful completion.
    #
    checkProcess(%processInfo);
    
}

#============================================================================#
sub createContigReadInfoFiles {

    my $readInfoFile = shift;
    my $outputFileDir = shift;
    my $contigReadFileOfFiles = shift;
    
    my %contigFH = ();
    my %contigFileNames = ();
    my %openedFiles = ();
    my $numFileHandles = 0;
    my $maxNumFileHandles = 1020;
    my $lastContig = '';
    
    unless (open FH, $readInfoFile) {
        logError("ERROR: failed to open file $readInfoFile: $!",1);
    }
    
    # NOTE: using IO::File to create multiple file handles has a limit of
    # 1020 max file handles created!
    #
    while (my $line = <FH>) {
        next if $line !~ /\w/;
        my @values = split /\t/, $line;
        next if @values < 5;
        my $contig = $values[4];
        if ( defined $contigFH{$contig} ) {
            my $fh = $contigFH{$contig};
            print $fh $line;
        } else {
            $numFileHandles++;
            # if num of file handles is greater than max limit, then
            # close previous file handle.  We'll need to open the previous
            # file handle the next time it is needed.
            #
            if ( $numFileHandles > $maxNumFileHandles ) {
                my $fh = $contigFH{$lastContig};
                $fh->close;
                delete $contigFH{$lastContig};
                $numFileHandles--;
            }
            
            # If file has already been opened, but has been closed due
            # to max limit, then open file handle with append. Otherwise
            # open file handle to write from beginning of file.
            #
            my $openMode = defined $openedFiles{$contig} ? ">>" : ">";
            
            my $file = "$outputFileDir/$contig.readinfo.txt";
            my $fh = new IO::File "$openMode $file";
            $contigFH{$contig} = $fh;
            $contigFileNames{$contig} = $file;
            $openedFiles{$contig}++;
            $lastContig = $contig;
            print $fh $line;
        }
    }
    
    foreach my $contig ( keys %contigFH ) {
        my $fh = $contigFH{$contig};
        $fh->close;
    }
    
    close FH;

    unless (open OFH, ">$contigReadFileOfFiles" ) {
        logError("ERROR: failed to open file $contigReadFileOfFiles: $!",1);
    }
    foreach my $contig ( sort {$a cmp $b} keys %contigFileNames ) {
        print OFH "$contig\t$contigFileNames{$contig}\n";
    }
    close OFH;

}

#============================================================================#
sub setFileForLogging {
    
    my $logFile = shift;
    
    $OBJ_LOGGER->setLogOutFileAppend($logFile);
    $OBJ_LOGGER->setLogErrorFileAppend($logFile);
    
}

#============================================================================#
sub getScript {
    
    my $scriptType = shift;
    
    my $script = $OBJ_PROPS->getProperty("$scriptType");
       $script = "$FindBin::RealBin/$script" if
            $script !~ /\//; # add path to script if not specified in config file
            
    # Check if script exists.
    #
    if ( !-e $script ) {
        my $errMsg = "ERROR: cannot find script $script defined in config file as $scriptType.\n";
        print $errMsg;
        logError($errMsg, 1);
    }
    
    return $script;
    
}

#============================================================================#
sub checkProcess {
    
    my %processInfo = @_;
    
    if ( $processInfo{exitCode} ) {
        my $errMsg = '';
        if ( defined $processInfo{logStdoutMessage} &&
            $processInfo{logStdoutMessage} ) {
            $errMsg .= $processInfo{stdoutMessage};
        }
        $errMsg .= $processInfo{stderrMessage};
        logError($errMsg,1);
    }
    
}

#============================================================================#
sub runCommand {
   
    my $cmd = shift;
    
    # Execute command, capture stderr, stdout, exitcode
    #
    my $errMessage = "CMD: $cmd\n";
    
    print "Running $cmd ...\n\n";
    logOutput($cmd);
    
    my %processInfo = runProcess($cmd,
        {-checkExecutable=>0,
        }
    );
    
    return %processInfo;
    
}

#============================================================================#
sub logExecution {
    
    my $programExecution = shift;
    my $configFile = shift;
    
    my $msg = "Command: ".$programExecution."\n".
              "Config File: $configFile\n".
              "Current directory: ".getcwd;
    logOutput($msg);
}

#============================================================================#
sub logError {
    my $message = shift;
    my $confess = shift || 0;
    
    $OBJ_LOGGER->logError($message);
    $OBJ_WARNINGS->add($message);
    
    if ( $confess ) {
        $OBJ_WARNINGS->createFile() if $OBJ_WARNINGS->getNumberOfWarnings;
        confess $message;
    }
}
    
#============================================================================#
sub logOutput {
    my $message = shift;
    my $printMsg = shift || 0;
    
    $OBJ_LOGGER->logOut($message);
    print "$message\n" if $printMsg;
}
    
#============================================================================#
sub logOutputFileCreation {
    
    my $outputFile = shift;
    
    $outputFile = abs_path($outputFile);
    
    my $fileExists = -s $outputFile ? 1:0;
    my $msg = $fileExists ? "Created $outputFile" :
        "ERROR: failed to create $outputFile";
    logOutput($msg,1);
    
    return $fileExists;
    
}

#============================================================================#
sub printhelp {
    my $verbose = shift || 1;
    pod2usage(-verbose=>$verbose);
    exit 1;
}

#============================================================================#
