<commit_before>package Tephra::TIR::TIRStats;

use 5.014;
use Moose;
use MooseX::Types::Path::Class;
use Statistics::Descriptive;
use Sort::Naturally;
use File::Spec;
use File::Find;
use File::Basename;
use File::Path      qw(make_path remove_tree);
use File::Copy      qw(move copy);
use List::MoreUtils qw(indexes any);
use Time::HiRes     qw(gettimeofday);
use Log::Any        qw($log);
use Cwd             qw(getcwd abs_path);
use Bio::DB::HTS::Kseq;
use Bio::AlignIO;
use Bio::TreeIO;
use Parallel::ForkManager;
use Carp 'croak';
use Try::Tiny;
#use Data::Dump::Color;
use namespace::autoclean;

with 'Tephra::Role::GFF',
     'Tephra::Role::Util',
     'Tephra::Role::Run::PAML';

=head1 NAME

Tephra::TIR::TIRStats - Calculate the age distribution of TIR transposons

=head1 VERSION

Version 0.05.1

=cut

our $VERSION = '0.05.1';
$VERSION = eval $VERSION;

has genome => (
    is       => 'ro',
    isa      => 'Path::Class::File',
    required => 1,
    coerce   => 1,
);

has outfile => (
    is       => 'ro',
    isa      => 'Path::Class::Dir',
    required => 1,
    coerce   => 1,
);

has dir => (
    is       => 'ro',
    isa      => 'Path::Class::Dir',
    required => 0,
    coerce   => 1,
);

has threads => (
    is        => 'ro',
    isa       => 'Int',
    predicate => 'has_threads',
    lazy      => 1,
    default   => 1,
);

has gff => (
    is       => 'ro',
    isa      => 'Path::Class::File',
    required => 0,
    coerce   => 1,
);

has clean => (
    is       => 'ro',
    isa      => 'Bool',
    required => 0,
    default  => 1,
);

has all => (
    is       => 'ro',
    isa      => 'Bool',
    required => 0,
    default  => 1,
);

#
# methods
#
sub calculate_tir_ages {
    my $self = shift;
    my $threads = $self->threads;
    my $outfile = $self->outfile->absolute->resolve;

    my $args = $self->collect_feature_args;
    #dd $args; ## debug
    
    my $resdir = File::Spec->catdir($args->{resdir}, 'divergence_time_stats');
    
    unless ( -d $resdir ) {
	make_path( $resdir, {verbose => 0, mode => 0771,} );
    }
    
    my $t0 = gettimeofday();
    my $tirts = 0;
    my $logfile = File::Spec->catfile( abs_path($resdir), 'all_aln_reports.log' );
    open my $logfh, '>>', $logfile or die "\nERROR: Could not open file: $logfile\n";
    
    my $pm = Parallel::ForkManager->new($threads);
    local $SIG{INT} = sub {
        $log->warn("Caught SIGINT; Waiting for child processes to finish.");
        $pm->wait_all_children;
        exit 1;
    };

    $pm->run_on_finish( sub { my ($pid, $exit_code, $ident, $exit_signal, $core_dump, $data_ref) = @_;
			      my $t1 = gettimeofday();
			      my $elapsed = $t1 - $t0;
			      my $time = sprintf("%.2f",$elapsed/60);
			      say $logfh basename($ident),
			          " just finished with PID $pid and exit code: $exit_code in $time minutes";
			} );

    for my $type (keys %$args) {
	if ($type eq 'tirs') {
	    for my $db (@{$args->{$type}{seqs}}) {
		$tirts++;
		$pm->start($db) and next;
		$SIG{INT} = sub { $pm->finish };
		$self->process_align_args($db, $resdir);
		
		$pm->finish(0, \$db);
	    }
	}
    }
    $pm->wait_all_children;

    my @agefiles;
    find( sub { push @agefiles, $File::Find::name if -f and /divergence.txt$/ and -s }, $resdir );

    open my $out, '>', $outfile or die "\nERROR: Could not open file: $outfile\n";
    say $out join "\t", "TIR-ID", "Divergence", "Age", "Ts:Tv";

    for my $file (@agefiles) {
	$self->collate($file, $out);
    }
    close $out;

    if ($self->clean) {
	my @alnfiles;
	my $wanted  = sub { push @alnfiles, $File::Find::name 
				if -f && /tirs.fasta$|\.aln$|\.log$|\.phy$|\.dnd$|\.ctl$|\-divergence.txt$/ };
	my $process = sub { grep ! -d, @_ };
	find({ wanted => $wanted, preprocess => $process }, $resdir);
	unlink @alnfiles;

	remove_tree( $args->{resdir}, { safe => 1 } );
    }

    my $t2 = gettimeofday();
    my $total_elapsed = $t2 - $t0;
    my $final_time = sprintf("%.2f",$total_elapsed/60);

    say $logfh "\n========> Finished calculating insertion time for $tirts TIRs in $final_time minutes";
    close $logfh;

    return;
}

sub collect_feature_args {
    my $self = shift;
    my $dir = $self->dir->absolute->resolve;

    my (@tirs, %aln_args);
    if ($self->all) {
	my ($files, $wdir) = $self->extract_tir_features;
        $aln_args{tirs} = { seqs => $files };
        $aln_args{resdir} = $wdir;
    }
    else {
	my $wanted  = sub { push @tirs, $File::Find::name if -f && /exemplar_tirs.fasta$/ };
	my $process = sub { grep ! -d, @_ };
	find({ wanted => $wanted, preprocess => $process }, $dir);

	if (@tirs > 0) {
	    $aln_args{tirs} = { seqs => \@tirs };
	    $aln_args{resdir} = $dir;
	}
	else {
	    unless (-e $self->gff) {
		croak "\nERROR: No exemplar files were found in the input directory ".
		    "and the input GFF file does not appear to exist. Exiting.\n";
	    }

	    warn "\nWARNING: No exemplar files were found in the input directory. TIR age will be ".
		"calculated from TIR elements in the input GFF.\n";

	    my ($files, $wdir) = $self->extract_tir_features;
	    $aln_args{tirs} = { seqs => $files };
	    $aln_args{resdir} = $wdir;
	}
    }

    return \%aln_args;
}

sub extract_tir_features {<commit_msg>add check if directory argument was given<commit_after>package Tephra::TIR::TIRStats;

use 5.014;
use Moose;
use MooseX::Types::Path::Class;
use Statistics::Descriptive;
use Sort::Naturally;
use File::Spec;
use File::Find;
use File::Basename;
use File::Path      qw(make_path remove_tree);
use File::Copy      qw(move copy);
use List::MoreUtils qw(indexes any);
use Time::HiRes     qw(gettimeofday);
use Log::Any        qw($log);
use Cwd             qw(getcwd abs_path);
use Bio::DB::HTS::Kseq;
use Bio::AlignIO;
use Bio::TreeIO;
use Parallel::ForkManager;
use Carp 'croak';
use Try::Tiny;
#use Data::Dump::Color;
use namespace::autoclean;

with 'Tephra::Role::GFF',
     'Tephra::Role::Util',
     'Tephra::Role::Run::PAML';

=head1 NAME

Tephra::TIR::TIRStats - Calculate the age distribution of TIR transposons

=head1 VERSION

Version 0.05.1

=cut

our $VERSION = '0.05.1';
$VERSION = eval $VERSION;

has genome => (
    is       => 'ro',
    isa      => 'Path::Class::File',
    required => 1,
    coerce   => 1,
);

has outfile => (
    is       => 'ro',
    isa      => 'Path::Class::Dir',
    required => 1,
    coerce   => 1,
);

has dir => (
    is       => 'ro',
    isa      => 'Path::Class::Dir',
    required => 0,
    coerce   => 1,
);

has threads => (
    is        => 'ro',
    isa       => 'Int',
    predicate => 'has_threads',
    lazy      => 1,
    default   => 1,
);

has gff => (
    is       => 'ro',
    isa      => 'Path::Class::File',
    required => 0,
    coerce   => 1,
);

has clean => (
    is       => 'ro',
    isa      => 'Bool',
    required => 0,
    default  => 1,
);

has all => (
    is       => 'ro',
    isa      => 'Bool',
    required => 0,
    default  => 1,
);

#
# methods
#
sub calculate_tir_ages {
    my $self = shift;
    my $threads = $self->threads;
    my $outfile = $self->outfile; 

    my $args = $self->collect_feature_args;
    #dd $args; ## debug
    
    my $resdir = File::Spec->catdir($args->{resdir}, 'divergence_time_stats');
    
    unless ( -d $resdir ) {
	make_path( $resdir, {verbose => 0, mode => 0771,} );
    }
    
    my $t0 = gettimeofday();
    my $tirts = 0;
    my $logfile = File::Spec->catfile( abs_path($resdir), 'all_aln_reports.log' );
    open my $logfh, '>>', $logfile or die "\nERROR: Could not open file: $logfile\n";
    
    my $pm = Parallel::ForkManager->new($threads);
    local $SIG{INT} = sub {
        $log->warn("Caught SIGINT; Waiting for child processes to finish.");
        $pm->wait_all_children;
        exit 1;
    };

    $pm->run_on_finish( sub { my ($pid, $exit_code, $ident, $exit_signal, $core_dump, $data_ref) = @_;
			      my $t1 = gettimeofday();
			      my $elapsed = $t1 - $t0;
			      my $time = sprintf("%.2f",$elapsed/60);
			      say $logfh basename($ident),
			          " just finished with PID $pid and exit code: $exit_code in $time minutes";
			} );

    for my $type (keys %$args) {
	if ($type eq 'tirs') {
	    for my $db (@{$args->{$type}{seqs}}) {
		$tirts++;
		$pm->start($db) and next;
		$SIG{INT} = sub { $pm->finish };
		$self->process_align_args($db, $resdir);
		
		$pm->finish(0, \$db);
	    }
	}
    }
    $pm->wait_all_children;

    my @agefiles;
    find( sub { push @agefiles, $File::Find::name if -f and /divergence.txt$/ and -s }, $resdir );

    open my $out, '>', $outfile or die "\nERROR: Could not open file: $outfile\n";
    say $out join "\t", "TIR-ID", "Divergence", "Age", "Ts:Tv";

    for my $file (@agefiles) {
	$self->collate($file, $out);
    }
    close $out;

    if ($self->clean) {
	my @alnfiles;
	my $wanted  = sub { push @alnfiles, $File::Find::name 
				if -f && /tirs.fasta$|\.aln$|\.log$|\.phy$|\.dnd$|\.ctl$|\-divergence.txt$/ };
	my $process = sub { grep ! -d, @_ };
	find({ wanted => $wanted, preprocess => $process }, $resdir);
	unlink @alnfiles;

	remove_tree( $args->{resdir}, { safe => 1 } );
    }

    my $t2 = gettimeofday();
    my $total_elapsed = $t2 - $t0;
    my $final_time = sprintf("%.2f",$total_elapsed/60);

    say $logfh "\n========> Finished calculating insertion time for $tirts TIRs in $final_time minutes";
    close $logfh;

    return;
}

sub collect_feature_args {
    my $self = shift;

    my (@tirs, %aln_args);
    if ($self->all || ! $self->dir) {
	my ($files, $wdir) = $self->extract_tir_features;
        $aln_args{tirs} = { seqs => $files };
        $aln_args{resdir} = $wdir;
    }
    else {
	my $dir = $self->dir->absolute->resolve;
	my $wanted  = sub { push @tirs, $File::Find::name if -f && /exemplar_tirs.fasta$/ };
	my $process = sub { grep ! -d, @_ };
	find({ wanted => $wanted, preprocess => $process }, $dir);

	if (@tirs > 0) {
	    $aln_args{tirs} = { seqs => \@tirs };
	    $aln_args{resdir} = $dir;
	}
	else {
	    unless (-e $self->gff) {
		croak "\nERROR: No exemplar files were found in the input directory ".
		    "and the input GFF file does not appear to exist. Exiting.\n";
	    }

	    warn "\nWARNING: No exemplar files were found in the input directory. TIR age will be ".
		"calculated from TIR elements in the input GFF.\n";

	    my ($files, $wdir) = $self->extract_tir_features;
	    $aln_args{tirs} = { seqs => $files };
	    $aln_args{resdir} = $wdir;
	}
    }

    return \%aln_args;
}

sub extract_tir_features {
{'fraction_non_alphanumeric': 0.18516750978333493, 'fraction_numerical': 0.009735611339123795, 'mean_word_length': 2.6623558196434813, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 160, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4008358', 'n_tokens_mistral': 4202, 'n_tokens_neox': 3956, 'n_words': 997}