#!/usr/bin/perl -w
use POSIX;
use Getopt::Long qw(:config bundling require_order auto_version);
use Pod::Usage;
use FAST;
use FAST::Bio::SeqIO;
use File::Basename;
use File::Temp qw/ tempfile /;
use Storable qw/ store_fd /;
use strict;

use vars qw($VERSION $DESC $NAME $COMMAND $DATE);
$VERSION = $FAST::VERSION; 
$DESC    = "output trailing sequence records";
$NAME    = $0;
$NAME    =~ s/^.*\///;
$COMMAND = join " ",$NAME,@ARGV;
$DATE = POSIX::strftime("%c",localtime());

use constant { true => 1, false => 0 };

## DEFAULT OPTION VALUES
my $def_format  = $FAST::DEF_FORMAT;  #7/1/13 "fasta";
my $def_logname = $FAST::DEF_LOGNAME; #7/1/13 "FAST.log.txt";
my $def_join_string =  $FAST::DEF_JOIN_STRING;
my $def_print_num = 10;
my $memory_limit  = 524288; # -M 512KB

## OPTION VARIABLES
my $man                  = undef;  # --man
my $help                 = undef;  # -h
my $moltype              = undef;  # -m, in case bioperl can't tell
my $format               = $def_format;  # --format
my $log                  = undef;        # -l
my $logname              = $def_logname; # -L
my $comment              = undef;        # -C
my $n			 = $def_print_num; # -n
my $append               = undef; # -a
my $join                 = $def_join_string; # -j

GetOptions('help|h'         		 => \$help, 
	   'man'            		 => \$man,
	   'moltype|m=s'                 => sub{  my (undef,$val) = @_; 
						  die "$NAME: --moltype or -m option argument must be \"dna\", \"rna\" or \"protein\"" 
						    unless $val =~ /dna|rna|protein/i; 
						  $moltype = $val;
						},
	   'format=s'                    => \$format,
	   'log|l'                       => \$log,
	   'logname|L=s'                 => \$logname,
	   'comment|C=s'                 => \$comment,
	   'records|number|n=i'                   => sub{  my (undef,$val) = @_; 
						  die "$NAME: --records or -n option expects a positive integer argument\n" 
						    unless $val > 0;
						  $n = $val;
					       },
	   'a|annotate'                   => \$append,
	   'join|j=s'                      => \$join,
	   'memory_limit|M=i'            => sub { my (undef, $val) = @_;
						  die "$NAME: -M option expects a positive integer argument\n" 
						    unless $val > 0;
						  $memory_limit = $val;
						},
	   'q|fastq'                     => sub {$format = 'fastq'},
	  )
  or exit(1);

pod2usage(-verbose => 1) if $help;
pod2usage(-verbose => 2) if $man;

#Sequence Length = 542 nt; Size = 1746 bytes; 3.22 bytes/nt
$memory_limit = $memory_limit / 3.22; #convert memory to # of nt/aa

my $fromSTDIN = ((-t STDIN) ? false : true);

pod2usage("$NAME: expects at least one input filename or glob. Try \"perldoc $NAME\"") if (!($fromSTDIN) && !(@ARGV));

&FAST::log($logname, $DATE, $COMMAND, $comment, $fromSTDIN) if ($log);

my $append_file_name = ($append and (!($fromSTDIN) and @ARGV > 0));

my $OUT = FAST::Bio::SeqIO->newFh(-fh => *STDOUT{IO}, '-format' => $format);
my $IN;
unless (@ARGV) {
  if ($moltype) {
    $IN = FAST::Bio::SeqIO->new(-fh => *STDIN{IO}, '-format' => $format, '-alphabet' => $moltype);
  }
  else {
    $IN = FAST::Bio::SeqIO->new(-fh => *STDIN{IO}, '-format' => $format);
  }
}

my $file = undef;
my $seq_count = 0;
my @seq_list;
my $seqlength = 0;

while ($IN or @ARGV) {
  if (@ARGV) {
    $file = shift (@ARGV);
    unless (-e $file) {
      warn "$NAME: Could not find file $file. Skipping.\n";
      next;
    }
    elsif ($moltype) {
      $IN = FAST::Bio::SeqIO->new(-file => $file, '-format' => $format, '-alphabet' => $moltype);
    }
    else {
      $IN = FAST::Bio::SeqIO->new(-file => $file, '-format' => $format);
    }
  }

  if ($IN) {
    my @files = ();
    my @file_seq_count = ();
    my $stored_seqs = 0;

    while (my $seq = $IN->next_seq()) {
      if ($append_file_name) {
	  my $annotation = join "","file:", (fileparse($file))[0];
	  $seq->desc(join $join, $seq->desc(), $annotation); 
      }

      if (scalar (@seq_list) < $n){
	push @seq_list, $seq;
      }
      else{
	shift @seq_list;
	push @seq_list, $seq;
      }
      $seqlength += $seq->length();    
      if ($seqlength > $memory_limit) {
	my $seq_num = @seq_list;
	my ($fh, $filename) = tempfile();
	push @files, $filename;
	push @file_seq_count, $seq_num;
	store_fd(\@seq_list, $fh)
	  or die("$NAME: Could not write data to $filename with Storable");
	$stored_seqs += $seq_num;
	@seq_list = ();

	if (($stored_seqs - $file_seq_count[0]) >= $n) {
	  $filename = shift @files;
	  $stored_seqs -= shift @file_seq_count;
	  unlink $filename or warn "$NAME: Could not delete temporary file $filename: $!";
	}
      }
    }
    undef $IN;

    my $diff = ($stored_seqs + @seq_list) - $n;
    my $seqsref = undef;
    my @seqs = ();

    while ($diff > 0) {
      my $filename = shift @files;
      $stored_seqs -= shift @file_seq_count;

      $seqsref = Storable::retrieve($filename)
        or die ("$NAME: Could not retrieve data from $filename with Storable");

      @seqs = @$seqsref;

      my $temp = (scalar (@seqs) - $diff);
      @seqs = @seqs[(-1 * $temp) .. -1];

      while (@seqs) {
        my $seq = shift @seqs;
	print $OUT $seq;
      }

      unlink $filename or warn "$NAME: Could not delete temporary file $filename: $!";
      $diff = ($stored_seqs + @seq_list) - $n;
    }

    if (@seq_list and @files) {
      my ($fd, $filename) = tempfile();
      my $seq_file = $filename;

      store_fd(\@seq_list, $fd)
	or die("$NAME: Could not write data to $seq_file with Storable");

      @seq_list = ();
      push @files, $filename;
    }


    while(@files) {
      my $filename = shift @files;

      $seqsref = Storable::retrieve($filename)
	or die ("$NAME: Could not retrieve data from $filename with Storable");
      @seqs = @$seqsref;

      while (@seqs) {
	my $seq = shift @seqs;
	print $OUT $seq;
      }

      unlink $filename or warn "$NAME: Could not delete temporary file $filename: $!";
    }

    while (@seq_list) {
      my $seq = shift @seq_list;
      print $OUT $seq;
    }
  }
}

__END__

=head1 NAME

B<fastail> - print last sequence records on input

=head1 SYNOPSIS

B<fastail> [OPTION]... [MULTIFASTA-FILE]...

=head1 DESCRIPTION

B<fastail> takes sequence or alignment data as input, and outputs the
last n sequence records of each input file or, if no files are
specified, of the standard input. B<fastail> by default prints the
last 10 sequence records.

Options specific to B<fastail>:
  B<-n>, B<--number>=<integer>           the number of sequence records to print
  B<-a>, B<--annotate>                    annotate sequence records with source filename in descriptions
  B<-j>, B<--join>=<string>               use <string> to join annotation in descriptions

Options general to FAST:
  B<-h>, B<--help>                  	 print a brief help message
  B<--man>             	           print full documentation
  B<--version>                         print version
  B<-l>, B<--log>                         create/append to logfile	
  B<-L>, B<--logname>=<string>            use logfile name <string>
  B<-C>, B<--comment>=<string>            save comment <string> to log
  B<--format>=<format>                 use alternative format for input  
  B<--moltype>=<[dna|rna|protein]>     specify input sequence type
  B<-q>, B<--fastq>                       use fastq format as input and output

=head1 INPUT AND OUTPUT

B<fastail> is part of FAST, the FAST Analysis of Sequences Toolbox, based
on Bioperl. Most core FAST utilities expect input and return output in
multifasta format. Input can occur in one or more files or on
STDIN. Output occurs to STDOUT. The FAST utility B<fasconvert> can
reformat other formats to and from multifasta.

=head1 OPTIONS

=over 8

=item B<-n [integer]>,
      B<--records=[integer]>

The number of sequence records to print. Must be a positive
integer. Defaults to 10.


=item B<-a>,
      B<--annotate>

Annotate sequence descriptions with file names if and when input is
being processed from files.

=item B<-j>,
      B<--join=[string]>

Use [string] to append filenames in descriptions.

=item B<-h>,
      B<--help>

Print a brief help message and exit.

=item B<--man>

Print the manual page and exit.

=item B<--version>

Print version information and exit.

=item B<-l>,
      B<--log>

Creates, or appends to, a generic FAST logfile in the current working
directory. The logfile records date/time of execution, full command
with options and arguments, and an optional comment.

=item B<-L [string]>,
      B<--logname=[string]>

Use [string] as the name of the logfile. Default is "FAST.log.txt".

=item B<-C [string]>,
      B<--comment=[string]>

Include comment [string] in logfile. No comment is saved by default.

=item B<--format=[format]> 		  

Use alternative format for input. See man page for "fasconvert" for
allowed formats. This is for convenience; the FAST tools are designed
to exchange data in Fasta format, and "fasta" is the default format
for this tool.

=item B<-q>
      B<--fastq>

Use fastq format as input and output.

=back

=head1 EXAMPLES

Print the last 10 sequences:

=over 8

cat data.fas | B<fastail> > data.dna.fas

=back

Print the last 5 sequences:

=over 8

cat data.fas | B<fastail> -n 5 > data.dna.fas

=back

Print the last 5 sequences with file name added to the description:

=over 8

cat data.fas | B<faslast> -v -n 5 > data.dna.fas
    
=back

Print the last 5 sequences without file name added to the description:
    
=over 8

B<fastail> -q -n 5 data.fas data2.fas > data.dna.fas

=back

=head1 SEE ALSO

=over 8

=item C<man FAST>

=item C<perldoc FAST>

Introduction and cookbook for FAST

=item L<The FAST Home Page|http://compbio.ucmerced.edu/ardell/FAST>"

=back 

=head1 CITING

If you use FAST, please cite I<Lawrence et al. (2015). FAST: FAST Analysis of
Sequences Toolbox.> and Bioperl I<Stajich et al.>. 

=cut
