#!/opt/anaconda1anaconda2anaconda3/bin/perl 

use warnings;
use strict "vars";
use strict "refs";

use FindBin;
use lib "$FindBin::RealBin/../lib";
use lib "$FindBin::RealBin/../perl/lib";
use vars qw($RANK $LOG $EXE $TMP $LOCK @CMD_ARGS $VERSION);
use File::Which;

BEGIN{
   $VERSION = '2.31';
   @CMD_ARGS = @ARGV;

   #find iprscan
   $EXE = File::Which::which("iprscan");
   if (! $EXE) {
       die "ERROR: You must have iprscan installed and in you path to use iprscan_wrap\n";
   }

   #build temp directory
   my $n = umask();
   umask(0000);
   $TMP = "/tmp/iprscanTMP";
   mkdir($TMP, 07777) if(! -d $TMP);
   mkdir("$TMP/tmp", 07777) if(! -d "$TMP/tmp");
   umask($n);

   #what to do on ^C
   $SIG{'INT'} = sub {
      print STDERR "\n\niprscan_wrap aborted by user!!\n\n";
      exit (2);
   };

   #supress warnings from storable module
   $SIG{'__WARN__'} = sub {
      warn $_[0] if ( $_[0] !~ /Not a CODE reference/ &&
		      $_[0] !~ /Can\'t store item CODE/ &&
		      $_[0] !~ /Find\:\:skip_pattern|File\/Find\.pm/
		    );
   };

   #output to log file of seq that caused rank to die
   $SIG{'__DIE__'} =
   sub {
      if (defined ($LOG) && defined $_[0]) {
	 my $die_count = $LOG->get_die_count();
	 $die_count++;

	 $LOG->add_entry("DIED","RANK",$RANK);
	 $LOG->add_entry("DIED","COUNT",$die_count);
      }

      die $_[0];
   };
}

use Cwd;
use FileHandle;
use File::Copy;
use File::Path;
use Getopt::Long qw(:config pass_through);
use File::Temp qw(tempfile tempdir);
use Iterator::Fasta;
use Fasta;
use iprscan::runlog;
use ds_utility;
use Error qw(:try);
use Error::Simple;
use Perl::Unsafe::Signals;
use Process::IPRchunk;
use Process::MpiTiers;
use Proc::Signal;

select((select(STDOUT), $|=1)[0]); #make STDOUT buffer flush immediately

#no usage, I just print the usage statement from iprscan
#my $usage = ''

#-------------------------------------------------------------------------------
#------------------------------------ MAIN -------------------------------------
#-------------------------------------------------------------------------------

#---global variables
my $rank = 0;
my $size = 1;
$RANK = $rank;
my @appl;
my %OPT;
$OPT{retry} = 2;
$OPT{datastore} = 1;
$OPT{always_try} = 1;
$OPT{min_contig} = 5;
$OPT{seqtype} = 'p'; #default

#---Process options on the command line
try{
    GetOptions("i=s"       => \$OPT{infile},
               "o=s"       => \$OPT{outfile},
               "appl=s"    => \@appl,
               "nocrc"     => \$OPT{nocrc},
               "seqtype=s" => \$OPT{seqtype},
               "trtable=i" => \$OPT{trtable},
               "goterms"   => \$OPT{goterms},
               "iprlookup" => \$OPT{iprlookup},
               "format=s"  => \$OPT{format},
               "verbose"   => \$OPT{verbose},
               "retry"     => \$OPT{retry}, #hidden option for retrying
               "chpc"      => \$OPT{chpc}, #hidden option for local dbs
               "TMP"       => \$TMP,   #hidden option o specify TMP
               "cli"       => \$OPT{cli}, #just used to strip off the option
               "version" => sub{print "$VERSION\n"; exit(0)},
               );

    $main::quiet = 1 unless($OPT{verbose}); #suppress status messages

    #collect app list from iprscan if not suplied by user
    if(! @appl){
        my $conf = Cwd::abs_path($EXE);
        $conf =~ s/[^\/]+$//;
        $conf .= "../conf/iprscan.conf";
        die "ERROR: Cannot find iprscan.conf\n" if(! -e $conf);

        open(my $IN, "< $conf");
        my @data = <$IN>;
        close($IN);

        my ($apps) = grep {/^applications\=/} @data;
        $apps =~ s/^applications\=//;
        chomp($apps);
        @appl = split(',', $apps);
        die "ERROR: Cannot determine default applications\n" if(! @appl);
    }

    #apply apps to OPT
    $OPT{appl} = join(",", @appl);
    $OPT{_appl} = \@appl;

    #get current working directory
    $OPT{CWD} = Cwd::cwd();

    #build out_base and out_name for datastore
    $OPT{out_name} = $OPT{infile} || 'data';
    $OPT{out_name} =~ /([^\/]+)$/;
    $OPT{out_name} = $1;
    $OPT{out_name} =~ s/(.+)\.[^\.]+$/$1/;
    $OPT{out_base} = Cwd::cwd()."/$OPT{out_name}.iprscan.output";

    #build localized iprscan database for each node
    if($OPT{chpc} && ! -e "$TMP/iprscan/data/ok"){
        my $lock = new File::NFSLock("$TMP/.iprscan_lock", 'EX', 1200, 1205);

        #build local db in /tmp
        if(! -e "$TMP/iprscan/data/ok" && $lock){
            #remove old failed directories
            if(-d "$TMP/iprscan"){
                move("$TMP/iprscan", "$TMP/old");
		File::Path::rmtree("$TMP/old");
            }
            if(-d "$TMP/test"){
                move("$TMP/test", "$TMP/old");
		File::Path::rmtree("$TMP/old");
            }

            my $free = `df /tmp | grep -v \"Filesystem\" | awk \'{print \$4}\'`;
            if($free > 16000000){
                my @tar_db = split (":", $ENV{'TAR_DB'});
                @tar_db = grep {-d $_} @tar_db;

                if (@tar_db){
                    my $db = $tar_db[int(rand(@tar_db))];

                    my @files = ('latest_match.tar.gz',
                                 'latest_pthr.tar.gz',
                                 'latest_nopthr.tar.gz');

                    if(-e "$db/$files[0]" &&
                       -e "$db/$files[1]" &&
                       -e "$db/$files[1]"
                       ){

                        my $fail;
                        foreach my $file (@files){
                            last if($fail);
                            mkdir("$TMP/test") unless(-d "$TMP/test");
                            copy("$db/$file", "$TMP/test");
                            system("cd $TMP/test\n".
                                   "tar -zxvmf $file"
                                   );

                            $fail = $?;

                            unlink("$TMP/test/$file");
                        }

                        if($fail){
			    File::Path::rmtree("$TMP/test");
			  }
                        else{
                            move("$TMP/test/iprscan", "$TMP/iprscan");
			    File::Path::rmtree("$TMP/test");
                            system("index_data.pl -p $TMP/iprscan/data -inx -bin");
                            system("touch $TMP/iprscan/data/ok");
                        }
                    }
                }
            }
        }

        $lock->unlock if($lock);
    }
}
catch Error::Simple with{
    my $E = shift;

    print STDERR $E->{-text};
    die "\n\niprscan_wrap failed parsing command line options!!\n\n";
};

#varibles that are persistent outside of try
my $iterator;
my $DS_CTL;
my @failed;
my @interrupted;

try{
    #---test command line  options here
    #test input files existance
    if($OPT{infile} && ! -e $OPT{infile}){
	die "ERROR: The input file \'$OPT{infile}\' does not exist.\n";
    }
    
    #let iprscan test all other options
    my (undef, $tfile) = tempfile(); #empty dummy test file
    my $exe = "$EXE -cli";
    my $command = "$exe " . join(' ', @ARGV);
    $command .= " -nocrc" if($OPT{nocrc});
    $command .= " -seqtype $OPT{seqtype}" if(defined $OPT{seqtype});
    $command .= " -trtable $OPT{trtable}" if(defined $OPT{trtable});
    $command .= " -goterms" if($OPT{goterms});
    $command .= " -iprlookup" if($OPT{iprlookup});
    $command .= " -format $OPT{format}" if(defined $OPT{format});
    $command .= " -verbose" if($OPT{verbose});
    $command .= " -appl " . join(" -appl ", @{$OPT{_appl}}) if(@{$OPT{_appl}});
    $command .= " -i $tfile" if($OPT{infile}); #test options on dummy file                                                                                                                                                                                       
    open(my $PAR, "$command 2>&1 |");
    my @err = <$PAR>;
    close($PAR);
    unlink($tfile);
    
    #report errors from iprscan
    if(! grep {/Error\: Unable to read sequence/} @err){
	foreach (@err){
	    $_ =~ s/\/.*iprscan\s+\-cli/iprscan_wrap/;
	}
	
	die join('', @err);
    }
    
    #make output directory
    mkdir $OPT{out_base} if(! -d $OPT{out_base});
    my $tmplock = new File::NFSLock($OPT{out_base}."/.init_lock", 'EX', 40, 40);
    if($LOCK = new File::NFSLock($OPT{out_base}."/.gi_lock", 'SH', 40, 40)){
	$LOCK->maintain(30);
	$OPT{_shared_id} = $LOCK->shared_id();
	$OPT{_owners} = $LOCK->owners();
	$OPT{_multi_chpc}++ if($LOCK->owners() > 1);
	unlink($OPT{outfile}) if($OPT{_multi_chpc} && $OPT{outfile} && -e $OPT{outfile} ); #clear preexisting outfile
    }
    else{
	die "ERROR: The directory is locked.  Perhaps by another instance of iprscan_wrap.\n\n";
    }
    $tmplock->unlock() if($tmplock);
    
    $DS_CTL = new ds_utility(\%OPT);
    $iterator = new Iterator::Fasta($OPT{infile});
    $iterator->step($OPT{_owners});
    $iterator->skip_file($DS_CTL->{log});
}
catch Error::Simple with{
    my $E = shift;
        print STDERR $E->{-text};
    my $code = 2;
    $code = $E->{-value} if (defined($E->{-value}));
    
    exit($code);
};
    
my $tier;
my $f_count = @failed;
my $i_count = @interrupted;
while (my $fasta = $iterator->nextFasta() || shift @failed || shift @interrupted){
    $tier = Process::MpiTiers->new({fasta => $fasta,
				    CTL_OPT => \%OPT,
				    DS_CTL  => $DS_CTL,
				    params    => \@ARGV,
				    iprscan   => "$EXE -cli"},
				   '0',
				   'Process::IPRchunk'
				   );
    
    next if($tier->terminated);
		
    #take a short break before processing failed contigs
    #this handles heavy processor usage when failure is related
    #to maker process overlap
    sleep 1 if($f_count != @failed);

    $tier->run_all(0);

    push(@failed, $tier->fasta) if ($tier->failed);
    push(@interrupted, $tier->fasta) if ($tier->interrupt && $i_count == @interrupted);

    $f_count = @failed; #reset failure count
    $i_count = @interrupted; #reset interrupt count  
}

print STDERR "\n\niprscan_wrap is now finished!!!\n\n" unless($main::qq);

#clean up all chilren processes
Proc::Signal::reap_children_by_name(9, 'maintain.pl');

exit(0);
#-----------------------------------------------------------------------------
#----------------------------------- SUBS ------------------------------------
#-----------------------------------------------------------------------------
