#!/usr/bin/perl -w
use POSIX;
use Getopt::Long qw(:config bundling require_order auto_version);
use Pod::Usage;
use FAST;
use strict;
use FAST::Bio::SeqIO;
use Safe;

use vars qw($VERSION $DESC $NAME $COMMAND $DATE);
$VERSION = $FAST::VERSION; 
$DESC    = "regex-based substitutions on 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";

## OPTION VARIABLES
my $format               = $def_format;  
my $man                  = undef;  # --man
my $help                 = undef;  # -h
my $moltype              = undef;  # -m, in case bioperl can't tell
my $log                  = undef;        # -l
my $logname              = $def_logname; # -L
my $comment              = undef;        # -C
my $sequence             = undef;        # -s
my $description          = undef;        # -d
my $insensitive          = undef;        # -i
my $global               = undef;        # -g
my $fastq                = 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;
						},
	   'log|l'                       => \$log,
	   'logname|L=s'                 => \$logname,
	   'comment|C=s'                 => \$comment,
	   'format=s'                    => \$format,
	   'global|g'                    => \$global,
	   'insensitive|i'               => \$insensitive,
	   'sequence|s'                  => \$sequence,
	   'description|d'               => \$description,
	   'fastq|q'                     => sub{$format = 'fastq';},
	  ) 

  or pod2usage(2);
		  
pod2usage(-verbose => 1) if $help;
pod2usage(-verbose => 2) if $man;
my $fromSTDIN = ((-t STDIN) ? false : true);

pod2usage("$NAME: expects at least three arguments when input from file.\n") if ( (-t STDIN) && (@ARGV < 3));
pod2usage("$NAME: expects exactly two arguments when input on STDIN.\n")    if (!(-t STDIN) &&  @ARGV != 2);

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

my $re_string = shift @ARGV;
my $string = shift @ARGV;

my $re;
if ($insensitive) {
  $re = qr/$re_string/i;
}
else {
  $re = qr/$re_string/;
}

my ($selector,$type);
if ($sequence) {
  $selector = "seq";
  $type = "sequence";
}
elsif ($description) { 
  $selector = "desc";
  $type = "description";
}
else {
  $selector = "id";
  $type = "identifier";
}


my $OUT = FAST::Bio::SeqIO->newFh('-format' => $def_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) { 
    while (my $seq = $IN->next_seq()) {
      $_ = $seq->$selector();
      if (/$re/) {
	my $options = '';
        $options = "g" if ($global);
	eval sprintf "s/%s/%s/%s", $re, $string, $options; 
	die "$NAME: $@\n" if $@;
	$seq->$selector($_);
      }
      print $OUT $seq;
    }
    undef $IN;
  }
}



__END__

=head1 NAME

B<fassub> -- regex-based substitutions on sequence records

=head1 SYNOPSIS

B<fassub> [OPTION]... [PERL-REGEX] [SUBSTITUTION] [MULTIFASTA-FILE]...

=head1 DESCRIPTION

B<fassub> takes multifasta format sequence or alignment data as input,
and faciliates text substitutions on sequence identifiers (by
default), sequences or descriptions using the Perl B<s///>
substitution operator. B<fassub> takes two mandatory arguments
corresponding to the two arguments of B<s///>; the first a Perl regex
matching a set of strings to be substituted and the second,
replacement text, possibly including special characters that require
backslash quoting or capture buffer expressions. The first regex
argument may require quoting to protect special characters from
interpretation by the shell.  By default, only the left-most match is
substituted, use B<--global>/B<-g> to substitute all matches. Use
single quotes on arguments at the shell to deploy numbered match
variables like "$1" in your substitutions, which will back-reference
capture groups in your regex.

Options specific to B<fassub>:
  B<-s>, B<--sequence>              substitute on sequences
  B<-d>, B<--description>           substitute on descriptions  
  B<-i>, B<--insensitive>           match text case-insensitively
  B<-g>, B<--global>                substitute all matches in the data  

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<-q>, B<--fastq>                 use fastq format as input and output

=head1 INPUT AND OUTPUT

B<fassub> 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<-s>, 
      B<--sequence>

Substitute on the sequences. By default substitution occurs on identifiers.

=item B<-d>,
      B<--description>

Substitute on the descriptions. By default substitution occurs on identifiers.

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

Substitute case-insensitively. 

=item B<-g>,
      B<--global>

Substitute all matches in the data. By default only the first match on each line will be subsituted.

=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

Reverse genus and species in t/data/P450.fas:

=over 8

B<fassub> -d '\[(\w+) (\w+)\]' '[$2 $1]' t/data/P450.fas

=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
