=head1 NAME

Bio::Tools::Run::Search::ssaha2_client - Ensembl search runnable for SSAHA2

=head1 SYNOPSIS

  # Do not use this object directly - it is used as part of the
  # Bio::SearchIO system.
  use Bio::Tools::Run::Search;
  my $runnable = Bio::Tools::Run::Search(-method=>'ssaha2_client')
  $runnable->database( $database ); #Database string
  $runnable->seq( $seq );           #Bio::SeqI object
  $runnable->run; # Launch the query

  my $result = $runnable->next_result; #Bio::Search::Result::* object

=head1 DESCRIPTION

This object encapsulates methods for running SSAHA sequence database
search queries within the Ensembl web framework. Read the
L<Bio::Tools::Run::Search> docs for more information about how to use this.

=cut

# Let the code begin...
package Bio::Tools::Run::Search::ssaha2_client;

use strict;
use Data::Dumper;
use vars qw( @ISA 
	     $ALGORITHM $VERSION $SEARCHIO_FORMAT 
	     $DEFAULT_KMER_LENGTH $PARAMETER_OPTIONS $PROGRAM_NAME);

use Bio::Tools::Run::Search;
use IO::Socket;

@ISA = qw( Bio::Tools::Run::Search );

BEGIN{
  $SEARCHIO_FORMAT   = 'ssaha2';
  $ALGORITHM  = 'SSAHA';
  $VERSION    = 'Unknown';
  $PROGRAM_NAME  = 'ssaha2Client.pl';
  $PARAMETER_OPTIONS = 
    {
     '-depth' =>
     {
      default => 100,
      order   => 10,
      options => [1,5,10,50,100,500,1000,5000],
      description => qq( 
Output only the top 'n' matches for each query, sorted by number
of matching bases, then by subject name, then by start position in the
query sequence ),
     },

     '-seeds' =>
     {
      default => 2,
      order   => 20,
      options => [1,2,5,10,50,100],
      description => qq(
The minimum number of matching k-mers (typical length 12bp) required
to seed an alignment ),
     },

     '-score' =>
     {
      default => 20,
      order   => 30,
      options => [10,20,50,100,500,1000],
      description => qq(
Raw score threshold; alignments that score below this will not be
reported ),
     },
    };
  }
#----------------------------------------------------------------------

sub program_name{ 
  my $self = shift;
  my $pname = $self->SUPER::program_name(@_);
  return defined( $pname ) ?  $pname : $PROGRAM_NAME;
}
sub algorithm         { return $ALGORITHM }
sub format            { return $SEARCHIO_FORMAT }
sub parameter_options { return $PARAMETER_OPTIONS }

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

=head2 command

  Arg [1]   : None
  Function  : generates the shell command to run
              the ssaha query
  Returntype: String: $command
  Exceptions: 
  Caller    : 
  Example   : 

=cut

sub command {
  my $self = shift;

  if( ! -f $self->fastafile ){ $self->throw("Need a query sequence!") }

  my $exe = $self->executable ;
  $exe || $self->throw( "Executable for ". ref($self) . " undetermined" );
  -e $exe || $self->throw( "$exe does not exist" );
  -X $exe || $self->throw( "$exe is not executable bu UID/GID" );

  my( $host, $port ) = $self->_get_server();

  my $param_str = '';
  foreach my $param( $self->option ){
    my $val = $self->option($param);
    $param_str .= " $param $val";
  }
  $param_str =~ s/[;`&|<>\s]+/ /g;
  my $command = 
    join( ' ',  $exe, -server, $host, -port, $port, -align, 1, $param_str );

  my $fastafile   = $self->fastafile;
  my $reportfile  = $self->reportfile;
  my $errorfile    = $self->errorfile;
  my $hack_to_ensure_false = "; echo ''";
  return "cat $fastafile | $command 1>$reportfile 2>$errorfile $hack_to_ensure_false";

}
#----------------------------------------------------------------------

=head2 database

  Arg [1]   : 
  Function  : Same as SUPER, but verifies that the database is a name:port
  Returntype: 
  Exceptions: 
  Caller    : 
  Example   : 

=cut

sub database {
   my $self = shift;
   if( @_ ){
       my ($host, $port) = split( ':', $_[0], 2 ); 
       $port ||  
	 $self->throw("Bad format for ssaha search DB: ".$_[0].
		      ". Use host:port" );
   }
   return $self->SUPER::database(@_);
}

#----------------------------------------------------------------------
=head2 _get_server

  Arg [1]   : None
  Function  : Internal method to convert the database string into a 
              SSAHA host and port. Database string must be in format of:
              host:port
  Returntype: array - $host, $port
  Exceptions: 
  Caller    : 
  Example   : 

=cut

sub _get_server{
  my $self = shift;
  my $database = $self->database;

  my ($host, $port ) = split( ':', $database, 2 ); 
  $port ||  
    $self->throw("Bad format for ssaha search DB: ".$database.
		 ". Use host:port" );

  # Test to see whether the server is responding
  my $status = 1;
  my $error  = '';
  for( my $i=0; $i<5; $i++ ){ # Allow up to 5 attempts to contact the server
    eval{
      my $socket = IO::Socket::INET->new( PeerAddr => $host,
					  PeerPort => $port,
					  Timeout  => 1     ) 
	or die( "$@ $host:$port" );
    };
    if( $@ ){ $status = 0; alarm(0); $error=$@; $self->debug($@) }
    else{     $status = 1; last; }
  }
  if( ! $status ){
    $self->throw( "SSAHA2 server unavailable: $error" )
  }

  return( $host, $port );
}
#----------------------------------------------------------------------

1;