#!/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 strict;

use vars qw($VERSION $DESC $NAME $COMMAND $DATE);
$VERSION = $FAST::VERSION; 
$DESC    = "Remove duplicate sequence records in a multifasta data-stream.\n";
$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_concat_string = ":";

## 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 $count                = undef; # -c
my $concat               = undef;
my $join                 = $def_join_string;
my $description_match    = undef;
my $id_match             = undef;

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,
	   'count|c'                     => \$count,
	   'concat:s'                    => \$concat, # optional option argument, default is def_concat_string
	   'join|j=s'                    => \$join,
	   'description|d'               => \$description_match,
	   'identifier|i'                => \$id_match,
	   'fastq|q'                     => sub { $format = 'fastq'; },

	  ) 
  or pod2usage(2);

$concat = ":" if (defined $concat and not $concat);
$join = "\t" if ($join eq '\t');
		  
pod2usage(1) if $help;
pod2usage(-verbose => 2) if $man;

if (!(-t STDIN) and (@ARGV != 0)) {
  pod2usage("$0: Requires exactly zero arguments when taking input from STDIN. Try $NAME -h for help."); 
}
elsif ((-t STDIN) and (@ARGV != 1)) {
  pod2usage("$0: Requires exactly one argument unless taking input from STDIN. Try $NAME -h for help.");
}
&FAST::log($logname, $DATE, $COMMAND, $comment) if ($log); 

my $match = 'seq';
if ($description_match){$match = 'desc';}
if ($id_match){$match = 'id';}

my $OUT = FAST::Bio::SeqIO->newFh('-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);
    }
}

while ($IN or @ARGV) {
  if (@ARGV) {
    my $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 $lastseq = undef;
    my $repeat = undef;
    my @ids = ();
    my $seq;
    while ($seq = $IN->next_seq()) {
      if (not defined $lastseq){
	# handle first sequence
	$lastseq = $seq;
	next;
      }
      else { #not first sequence
	my $seqdata = $seq->$match;
	my $lastseqdata = $lastseq->$match;

	if ($seqdata ne $lastseqdata) { #no repeat, or repeat is over

	  if ($concat and $repeat) { # repeat is over

	    $lastseq->id(join $concat,@ids);
	    undef @ids;

	  }

	  if ($count) {
	    my $olddesc = $lastseq->desc();
	    $repeat = 0 unless (defined $repeat); 
	    my $countstring = sprintf("count:%d",($repeat + 1));
	    $lastseq->desc(join $join,$olddesc,$countstring);
	  }

	  undef $repeat;
	  print $OUT $lastseq;
	  $lastseq = $seq;
	}
	else { # repeat

	  if ($concat) {
	    my $seqid = $seq->id;
	    if ($repeat) { # continuing repeat
	      push @ids, $seqid;
	    }
	    else { #new repeat
	      my $lastseqid = $lastseq->id;
	      push @ids, $lastseqid, $seqid;
	    }
	  }
	  
	  $repeat++;

	}

      }
    }
    # handle last sequence
    if ($repeat){ 
      if ($concat) { 
	$lastseq->id(join $concat,@ids);
      }
    }
    if ($count) {
      my $olddesc = $lastseq->desc();
      $repeat = 0 unless (defined $repeat); 
      my $countstring = sprintf("count:%d",($repeat + 1));
      $lastseq->desc(join $join,$olddesc,$countstring);
    }
    print $OUT $lastseq;
    undef $IN;
  }
}



__END__

=head1 NAME

B<fasuniq> - Remove duplicate sequence records in a
multifasta file or datastream.

=head1 SYNOPSIS

B<fasuniq> [options] [MULTIFASTA-FILE]

[MULTIFASTA-DATA-ON-STDIN] | B<fasuniq> [options]

=head1 DESCRIPTION

B<fasuniq> eliminates redundant sequence records from the input. A
redundant record is one in which a specific data field tests equal as
a string against the same field in one or more records that
immediately follow it in the input multifasta file or datastream. By
default the actual sequences are compared, optionally identifiers or
descriptions may be tested for equality. The data on input must be
sorted with respect to the data being compared using, for example,
B<fassort -s>. Only one field in sequence records is tested. By
default, the last matching sequence record in a series of matches is
the one printed. Identifiers of adjacent duplicate sequence records
can be concatenated to the printed sequence using the --concat
option. The count of redundant records may be annotated into sequence
descriptions with the --count option.

Options specific to fasuniq:
  B<-i>, B<--identifier>           test for string equality on identifiers
  B<-d>, B<--description>          test for string equality on descriptions
  B<-c>, B<--count>                annotate descriptions with counts of duplicates
  B<-j>, B<--join>=<string>        use <string> as delimiter when appending count data, default " " 
  B<--concat>:<string>          concatenate identifiers of duplicate records using ":" by default or <string> 
 
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<fasuniq> 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 file 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<-d>,
      B<--description>

Removes duplicate sequences by matching on descriptions.

=item B<-i>,
      B<--identifier>

Removes duplicate sequences by matching on identifiers.

=item B<-c>,
      B<--count>

Annotate the number of redundant records into descriptions.

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

Use <string> to append count data to sequence record
descriptions. Use with argument "\t" to indicate a tab-character.


=item B<--concat>,
      B<--concat=[string]>

Concatenate identifiers of repeated sequences in output. Use delimiter
[string] to concatenate identifiers. If none given, default is ":"


=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<-m [dna|rna|protein]>,
      B<--moltype=[dna|rna|protein]> 		  

Specify the type of sequence on input (should not be needed in most
cases, but sometimes Bioperl cannot guess and complains when
processing data).

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

Use fastq format as input and output.

=back

=head1 EXAMPLES

Remove duplicate sequences and append concatnated IDs of duplicate sequences to printed sequence:

=over 8

B<fassort -s> data1.fas | B<fasuniq --concat>

=back


=head1 SEE ALSO

=over 8

=item C<man perlre>

=item C<perldoc perlre>

Documentation on perl regular expressions.

=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
