package Bio::PrimerDesigner;

# $Id: PrimerDesigner.pm 25 2008-11-10 20:22:18Z kyclark $

=head1 NAME 

Bio::PrimerDesigner - Design PCR Primers using primer3 and epcr

=head1 SYNOPSIS

  use Bio::PrimerDesigner;

  my $pd = Bio::PrimerDesigner->new;

  #
  # Define the DNA sequence, etc.
  #
  my $dna   = "CGTGC...TTCGC";
  my $seqID = "sequence 1";

  #
  # Define design parameters (native primer3 syntax)
  #
  my %params = ( 
      PRIMER_NUM_RETURN   => 2,
      PRIMER_SEQUENCE_ID  => $seqID,
      SEQUENCE            => $dna,
      PRIMER_PRODUCT_SIZE => '500-600'
  );

  #
  # Or use input aliases
  #
  %param = ( 
      num                 => 2,
      id                  => $seqID,
      seq                 => $dna,
      sizerange           => '500-600'
  ); 

  #
  # Design primers
  #
  my $results = $pd->design( %params ) or die $pd->error;

  #
  # Make sure the design was successful
  #
  if ( !$results->left ) {
      die "No primers found\n", $results->raw_data;
  }

  #
  # Get results (single primer set)
  #
  my $left_primer  = $results->left;
  my $right_primer = $results->right;
  my $left_tm      = $results->lefttm;

  #
  # Get results (multiple primer sets)
  #
  my @left_primers  = $results->left(1..3);
  my @right_primers = $results->right(1..3);
  my @left_tms      = $results->lefttm(1..3);

=head1 DESCRIPTION

Bio::PrimerDesigner provides a low-level interface to the primer3 and
epcr binary executables and supplies methods to return the results.
Because primer3 and e-PCR are only available for Unix-like operating
systems, Bio::PrimerDesigner offers the ability to accessing the
primer3 binary via a remote server.  Local installations of primer3 or
e-PCR on Unix hosts are also supported.

=head1 METHODS

=cut

use strict;
use warnings;
use Bio::PrimerDesigner::primer3;
use Bio::PrimerDesigner::epcr;
use Readonly;

use base 'Class::Base';

Readonly my $EMPTY_STR => q{};
Readonly my %DEFAULT   => (
    method             => 'local',
    binary_path        => '/usr/local/bin',
    program            => 'primer3',
    url         
        => 'http://aceserver.biotech.ubc.ca/cgi-bin/primer_designer.cgi',
);
Readonly my %DESIGNER  => (
    primer3            => 'Bio::PrimerDesigner::primer3',
    epcr               => 'Bio::PrimerDesigner::epcr', 
);
Readonly our 
    $VERSION => '0.04'; # must break like this for Module::Build to find

# -------------------------------------------------------------------
sub init {
    my ( $self, $config ) = @_;

    for my $param ( qw[ program method url ] ) {
        $self->$param( $config->{ $param } ) or return;
    }

    if ($self->method eq 'local') {
        $self->binary_path( $config->{'binary_path'} ) or return;
    }

    my $loc = $self->method eq 'local' ? 'path' : 'url';
    $self->{ $loc } = $config->{'path'} || $config->{'url'} || $EMPTY_STR;
    return $self;
}

# -------------------------------------------------------------------
sub binary_path {

=pod

=head2 binary_path

Gets/sets path to the primer3 binary.

=cut

    my $self    = shift;

    if ( my $path = shift ) {
        if ( ! $self->os_is_unix ) {
            return $self->error("Cannot set binary_path on non-Unix-like OS");
        }
        else {
            if ( -e $path ) {
                $self->{'binary_path'} = $path;
            }
            else {
                $self->error(
                    "Can't find path to " . $self->program->binary_name .
                    ":\nPath '$path' does not exist"
                );
                return $EMPTY_STR;
            }
        }
    }

    unless ( defined $self->{'binary_path'} ) {
        $self->{'binary_path'} = 
            ( $self->os_is_unix ) ? $DEFAULT{'binary_path'} : $EMPTY_STR;
    }

    return $self->{'binary_path'};
}

# -------------------------------------------------------------------
sub design {

=pod

=head2 design

Makes the primer design or e-PCR request.  Returns an
Bio::PrimerDesigner::Result object.

=cut

    my $self     = shift;
    my %params   = @_ or $self->error("no design parameters");
    my $designer = $self->{'program'};
    my $method   = $self->method;
    my $loc      = $method eq 'local' ? $self->binary_path : $self->url;
    my $function = $designer =~ /primer3/ ? 'design' : 'run';
    
    my $result   = $designer->$function( $method, $loc, \%params )
                   or return $self->error( $designer->error );
    
    return $result;
}

# -------------------------------------------------------------------
sub epcr_example {

=head2 epcr_example

Run test e-PCR job.  Returns an Bio::PrimerDesigner::Results object.

=cut

    my $self = shift;
    my $epcr = Bio::PrimerDesigner::epcr->new;
    return $epcr->verify( 'remote', $DEFAULT{'url'} ) 
        || $self->error( $epcr->error );
}

# -------------------------------------------------------------------
sub list_aliases {

=pod

=head2 list_aliases

Lists aliases for primer3 input/output options

=cut

    my $self = shift;
    my $designer = $self->program or return $self->error;
    return $designer->list_aliases;
}

# -------------------------------------------------------------------
sub list_params {

=pod

=head2 list_params

Lists input options for primer3 or epcr, depending on the context

=cut

    my $self = shift;
    my $designer = $self->program or return $self->error;
    return $designer->list_params;
}

# -------------------------------------------------------------------
sub method {

=pod

=head2 method

Gets/sets method of accessing primer3 or epcr binaries.

=cut

    my $self    = shift;

    if ( my $arg = lc shift ) {
        return $self->error("Invalid argument for method: '$arg'")
            unless $arg eq 'local' || $arg eq 'remote';

        if ( !$self->os_is_unix && $arg eq 'local' ) {
            return $self->error("Local method doesn't work on Windows");
        }

        $self->{'method'} = $arg;
    }

    unless ( defined $self->{'method'} ) {
        $self->{'method'} = $self->os_is_unix ? $DEFAULT{'method'} : 'remote';
    }

    return $self->{'method'};
}

# -------------------------------------------------------------------
sub os_is_unix {

=pod

=head2 os_is_unix

Returns 1 if it looks like the operating system is a Unix variant, 
otherwise returns 0.

=cut

    my $self = shift;

    unless ( defined $self->{'os_is_unix'} ) {
        $self->{'os_is_unix'} = ( $^O =~ /(n[iu]x|darwin)/ ) ? 1 : 0;
    }

    return $self->{'os_is_unix'};
}

# -------------------------------------------------------------------
sub primer3_example {

=head2 primer3_example

Runs a sample design job for primers.  Returns an
Bio::PrimerDesigner::Results object.

=cut

    my $self = shift;
    my $pcr  = Bio::PrimerDesigner::primer3->new;
    return $pcr->example || $self->error( $pcr->error );
}

# -------------------------------------------------------------------
sub program {

=pod

=head2 program

Gets/sets which program to use.

=cut

    my $self    = shift;
    my $program = shift || $EMPTY_STR;
    my $reset   = 0; 

    if ( $program ) {
        return $self->error("Invalid argument for program: '$program'")
            unless $DESIGNER{ $program };
        $reset = 1;
    }

    if ( $reset || !defined $self->{'program'} ) {
        $program ||= $DEFAULT{'program'};
        my $class  = $DESIGNER{ $program };
        $self->{'program'} = $class->new or 
            return $self->error( $class->error );
    }

    return $self->{'program'};
}

# -------------------------------------------------------------------
sub run {

=pod

=head2 run

Alias to "design."

=cut
    my $self = shift;
    return $self->design( @_ );
}

# -------------------------------------------------------------------
sub url {

=pod

=head2 url

Gets/sets the URL for accessing the remote binaries.

=cut

    my $self = shift;
    my $url  = shift;

    if ( defined $url && $url eq $EMPTY_STR ) {
        $self->{'url'} = $EMPTY_STR;
    }
    elsif ( $url ) {
        $url = 'http://' . $url unless $url =~ m{https?://};
        $self->{'url'} = $url;
    }

    eval { require Bio::PrimerDesigner::Config };
    my $local_url = $EMPTY_STR;
    if ( !$@ ) {
        $local_url = $Bio::PrimerDesigner::Config->{'local_url'};
    }

    return $self->{'url'} || $local_url || $DEFAULT{'url'};
}

# -------------------------------------------------------------------
sub verify {                     
                     
=head2 verify

Tests local installations of primer3 or e-PCR to ensure that they are
working properly.

=cut

    my $self     = shift;
    my $designer = $self->{'program'};
    my $method   = $self->method;                     
    my $loc      = $method eq 'local' ? $self->binary_path : $self->url; 
    return $designer->verify( $method, $loc ) || 
        $self->error( $designer->error );
}

1;

# -------------------------------------------------------------------

=pod

=head1 AUTHORS

Copyright (C) 2003-2008 Sheldon McKay E<lt>mckays@cshl.eduE<gt>,
Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.

=head1 LICENSE

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 3 or any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA.

=head1 SEE ALSO

Bio::PrimerDesigner::primer3, Bio::PrimerDesigner::epcr.

=cut
