package Net::Whois::Gateway::Client;

use strict;
#use Data::Dumper;
use Carp;
use IO::Socket::INET;

require bytes;
require Storable;

our $VERSION = 0.12;
our $DEBUG = 0;

our $online;

our %POSTPROCESS;
our $default_host = "localhost";
our $default_port = 54321;
our @answer;


our $configuration;

our %SOCKET_FACTORY;

# get whois info from gateway
# %param: queries*, gateway_host, gateway_post, timeout, ?????
sub whois {
    my %param = @_;

    exists $param{query}
	or die "No query given";

    my @answer = _send_request( %param );

    return apply_postprocess(@answer);
}

sub _send_request {
    my %param = @_;

    my $timeout = $param{timeout} || 30;
    my $buffer;

    local $SIG{__DIE__} = sub {
	$online = 0;
	die @_;
    };

    my $resp;

    foreach my $critical (0..1) {
	$resp = eval {
	    local $SIG{ALRM} = sub { warn "timeout\n"; die "timeout\n" }   if $timeout;
	    alarm $timeout*1.2 				 if $timeout;

	    my ($socket, $new_socket) = _get_socket( \%param );
		$socket or die "WHOIS: cannot open socket: $!";

        if ( $new_socket && $configuration ) {
            # repeat configuration as not critical one
            ping( default_config => $configuration );
        }

	    #use Data::Dumper;
	    #warn Dumper $socket;

#	    $socket->connected
#		or die "WHOIS: socket is not connected";

	    my $frozen = Storable::nfreeze( [ \%param ] );
	    $frozen = bytes::length( $frozen ). "\0" . $frozen;
    
	    my $w = $socket->syswrite( $frozen, bytes::length( $frozen ) );
	    
	    if ( ! defined $w || $w <= 0 ) {
		die "WHOIS: Cannot syswrite to socket: $!";
	    }

	    my $r = $socket->sysread( $buffer, 65536 );

	    if ( ! defined $r || $r <= 0 ) {
		die "WHOIS: Cannot sysread from socket: $!";
	    }

        return 1;
	};

	if ( $@ ) {
	    _fail_socket( \%param );
	    warn $@ if ! $critical && $@ ne "timeout\n";
	}

	last if $resp;
    }

    alarm 0 if $timeout;

    if ( $@ ) {
	if ( $@ eq "timeout\n" ) {
	    die "WHOIS: timeout calling sysread/syswrite";
	}
	else {
	    die $@;
	}
    }

    my $answer;

    if (
	$buffer &&
	$buffer =~ /^(\d+)\0/o &&
	bytes::length( $buffer ) >= $1 + bytes::length($1) + 1 
    ) {
	$answer = Storable::thaw( substr( $buffer, bytes::length($1) + 1, $1 ) );
    }
    else {
	die "WHOIS: Cannot parse BUFFER";
    }

    return @$answer;
}

sub _fail_socket {
    my $param_ref = shift;

    my $gateway_host = $param_ref->{gateway_host} || $default_host;
    my $gateway_port = $param_ref->{gateway_port} || $default_port;

    my $addr = $gateway_host.':'.$gateway_port;

    delete $SOCKET_FACTORY{ $addr };
}

sub _get_socket {
    my $param_ref = shift;

    my $gateway_host = $param_ref->{gateway_host} || $default_host;
    my $gateway_port = $param_ref->{gateway_port} || $default_port;

    my $addr = $gateway_host.':'.$gateway_port;

#    use Data::Dumper;
#    warn Dumper \%SOCKET_FACTORY;

    #warn $SOCKET_FACTORY{$addr} && $SOCKET_FACTORY{$addr}->connected;

    return ($SOCKET_FACTORY{ $addr }, 0) if	 $SOCKET_FACTORY{ $addr }
				      && $SOCKET_FACTORY{ $addr }->connected;

    #warn "reconnection $addr";
    $SOCKET_FACTORY{ $addr } = IO::Socket::INET->new( $addr );

    return ($SOCKET_FACTORY{ $addr }, 1);
}

sub close_sockets {
    $_->close() foreach values %SOCKET_FACTORY;
    %SOCKET_FACTORY = ();
}

sub configure {
    my $new_config = shift;
    $configuration = $new_config;
}

sub ping {
    my %params = (ping => 1, @_);
    my $res;
    $online = 1;
    eval {
        ($res) = _send_request(%params);
    };
    return $res;
}

sub apply_postprocess {
    my @all_results = @_;
    my @out_results;
    
    foreach my $result ( @all_results ) {
        my $server = $result->{server};
        if ($result->{whois} && defined $POSTPROCESS{$server}) {
            $result->{whois} = $POSTPROCESS{$server}->($result->{whois});
        }
        push @out_results, $result;
    }
    
    return @out_results;
}

1;
__END__

=head1 NAME

Net::Whois::Gateway::Client - Interface to Net::Whois::Gateway::Server

=head1 SYNOPSIS

    use strict;
    use Net::Whois::Gateway::Client;
    
    my @all_results = Net::Whois::Gateway::Client::whois( query => ['reg.ru', 'yandex.ru'] );
    
    # or
    
    my @domains = qw(
        yandex.ru
        rambler.ru
        reg.ru
        google.com    
    );
    
    my @all_results = Net::Whois::Gateway::Client::whois(
        query        => \@domains,
        gateway_host => '192.168.0.5',    # default 'localhost'
        gateway_port => '888',            # default 54321
        referral     => 0,                # default 1
        server       => 'whois.ripn.net', # default try to auto-determine
        omit_msg     => 0,                # default 2
        use_cnames   => 1,                # default 0
        timeout      => 10,               # default 30
        local_ips    => ['192.168.0.1'],  # default use default ip
        cache_dir    => '~/whois_temp',   # default '/tmp/whois-gateway-d'
        cache_time   => 5,                # default 1
    );

    foreach my $result ( @all_results ) {
        my $query = $result->{query} if $result;
        if ($result->{error}) {
            print "Can't resolve WHOIS-info for ".$result->{query}."\n";
        }
	else {
            print "QUERY: ".$result->{query}."\n";
            print "WHOIS: ".$result->{whois}."\n";
            print "SERVER: ".$result->{server}."\n";
        };
    }                            

=head1 DESCRIPTION

Net::Whois::Gateway::Client - it's an interface to Net::Whois::Gateway::Server,
which  provides a very quick way to get WHOIS-info for list of domains, IPs or registrars.
Internally uses POE to run parallel non-blocking queries to whois-servers.
Supports recursive queries, cache, queries to HTTP-servers.

You definitely need install Net::Whois::Gateway::Server first, to use Net::Whois::Gateway::Client.

=head1 Functions

=over

=item whois()

whois( query => \@query_list [, param => $value] )
Get whois-info for list of queries. One argument is required and some optional:

=back

=head1 whois() parameters

=over 2

=item query

query is an arrayref of domains, ips or registrars to send to
whois server. Required.

=item gateway_host

Host to connect. Whois-gateway should be running there.
Default 'localhost';

=item gateway_port

Port to connect. Default 54321;

=item server

Specify server to connect. Defaults try to be determined by the component. Optional.

=item referral

Optional.

0 - make just one query, do not follow if redirections can be done;

1 - follow redirections if possible, return last response from server; # default

2 - follow redirections if possible, return all responses;


Exapmle:
    my @all_results = Net::Whois::Gateway::Client::whois(
        query    => [ 'google.com', 'godaddy.com' ],
        referral => 2,
    );
    foreach my $result ( @all_results ) {
        my $query = $result->{query} if $result;
        if ($result->{error}) {
            print "Can't resolve WHOIS-info for ".$result->{query}."\n";
        }
	else {
            print "Query for: ".$result->{query}."\n";
            # process all subqueries
            my $count = scalar @{$result->{subqueries}};
            print "There were $count queries:\n";
            foreach my $subquery (@{$result->{subqueries}}) {
                print "\tTo server ".$subquery->{server}."\n";
                # print "\tQuery: ".$subquery->{query}."\n";
                # print "\tResponse:\n".$subquery->{whois}."\n";
            }
        }
    }                       

=item omit_msg

0 - give the whole response;

1 - attempt to strip several known copyright messages and disclaimers;

2 - will try some additional stripping rules if some are known for the spcific server.

Default is 2.

=item use_cnames

Use whois-servers.net to get the whois server name when possible.
Default is to use the hardcoded defaults.

=item timeout

Cancel the request if connection is not made within a specific number of seconds.
Default 10 sec.

=item local_ips

List of local IP addresses to use for WHOIS queries.
Addresses will be used used successively in the successive queries
Default SRS::Comm::get_external_interfaces_ips()

=item cache_dir

Whois information will be cached in this directory.
Default '/tmp/whois-gateway-d'.

=item cache_time

Number of minutes to save cache. Default 1 minute.

=head1 Postrprocessing

Call to a user-defined subroutine on each whois result depending on whois-server supported:

    $Net::Whois::Gateway::Client::POSTPROCESS{whois.crsnic.net} = \&my_func;

=head1 AUTHORS

Pavel Boldin	<davinchi@cpan.org>
Sergey Kotenko	<graykot@gmail.com>

=head1 SEE ALSO

Net::Whois::Gateway::Server L<http://search.cpan.org/perldoc?Net::Whois::Gateway::Server>