package Net::pWhoIs;

use strict;
use Socket;
use IO::Socket::INET;
use Scalar::Util 'reftype';

our $VERSION = '0.02';
 
$| = 1;

######################################################
sub new {
######################################################
    my ($class, $args) = @_;
    my $self;

    my %defaults = (
        pwhoisserver => 'whois.pwhois.org',
        port         => 43,
    );

    # Apply defaults.
    for my $key (keys %defaults) {
        $self->{$key} = $defaults{$key};
    }

    # Apply arguments passed by human.
    # They may clobber our defaults.
    for my $key (keys %{$args}) {
        $self->{$key} = $args->{$key};
    }

    bless $self, $class;

    return $self;
}

######################################################
sub resolveReq {
######################################################
    my $self = shift;
    my $what = shift;

    if ($what !~ /\\d+\\.\\d+\\.\\d+\\.\\d+/) {
        my @host = gethostbyname($what);
        if (scalar(@host) == 0) {
            die "Failed host to resolve to IP: $what\n";
        } else {
            return Socket::inet_ntoa($host[4]);
        }
    }
}

######################################################
sub pwhois {
######################################################
    my $self = shift;
    my $what = shift;

    my @req;
    if (Scalar::Util::reftype($what) eq 'ARRAY') {
        @req  = @{$what};
    }
    else {
        push @req, $what;
    }

    my $socket = new IO::Socket::INET (
        PeerHost => $self->{pwhoisserver},
        PeerPort => $self->{port},
        Proto    => 'tcp',
    );
    die "Cannot connect to server $!\n" unless $socket;

    # Build request
    my $request = "begin\n";
    for my $elmt (@req) {
        my $resolved = $self->resolveReq($elmt);
        $request .= "$resolved\n";
    }
    $request .= "end\n";

    $socket->send($request);
    shutdown($socket, 1);

    my $responses;
    while (my $line = $socket->getline) {
        $responses .= $line;
    }
    $socket->close();

    my %results;
    my $cntr = 0;
    for my $response (split /\n\n/, $responses) {
        my $formatted = $self->formatResponse($response);
        $results{$req[$cntr++]} = $formatted;
    }

    return \%results;
}

######################################################
sub formatResponse {
######################################################
    my $self = shift;
    my $what = shift;

    my @lines = split /\n/, $what;

    my %formatted;
    for my $line (@lines) {
        my ($name, $value) = split /:\s/, $line;
        if ($name && $value) {
            $formatted{lc($name)} = $value;
        }
    }

    return \%formatted;
}

######################################################
sub printReport {
######################################################
    my $self = shift;
    my $what = shift;

    my $report;
    for my $req (keys %{$what}) {
        $report .= sprintf ("Request: %s\n", $req);
        for my $key (keys %{$what->{$req}}) {
            $report .= sprintf("%-22s : %s\n", $key, $what->{$req}{$key});
        }
    }
    return $report;
}

1;

=head1 NAME

Net::pWhoIs - Client library for Prefix WhoIs (pWhois)

=head1 SYNOPSIS

    use Net::pWhoIs;

    my $obj = Net::pWhoIs->new();
  
    # You may pass hostnames or IP addresses.
    my @array = qw(
        166.70.12.30
        207.20.243.105
        67.225.131.208
        perlmonks.org
        8.8.8.8
        12.12.12.12
        ftp2.freebsd.org
    );

    # You can pass an array.
    my $output = $obj->pwhois(\@array);

    # Or you can pass a single string.
    my $output = $obj->pwhois('8.8.8.8');

    # Generate a formatted report.
    print $obj->printReport($output);
  
    # Or manipulate the data yourself.
    for my $req (keys %{$output}) {
        printf ("Request: %s\n", $req);
        for my $key (keys %{$output->{$req}}) {
            print sprintf("%-22s : %s\n", $key, $output->{$req}{$key});
        }
    }


=head1 DESCRIPTION

Client library for pWhois service.  Includes support for bulk queries.

=head1 CONSTRUCTOR

=over 4

=item $obj = Net::pWhoIs->new( %options )

Construct a new C<Net::pWhoIs> object and return it.
Key/value pair arguments may be provided to set up the initial state.
The only require argument is: req.

    pwhoisserver  whois.pwhois.org
    port          43
    req           Required argument, value must be an array reference.

=back

=head1 METHODS

The following methods are available:

=over 4

=item Net::pWhoIs->pwhois()

Perform queries on passed arrayref.  Supports single or bulk query.  Returns a hash of hashrefs.

=back

=over 4

=item Net::pWhoIs->printReport()

Perform queries on passed arrayref.  Supports single or bulk query.  Returns a hash of hashrefs.

=back

=head1 Client

A full featured client is included: pwhoiscli.pl.  Pass it hostnames or IP seperated by space.

=head1 OUTPUT HASHREF KEYS

The following is the list hashref keys returned by pwhois.

    as-org-name
    as-path
    cache-date
    city
    country
    country-code
    ip
    latitude
    longitude
    net-name
    org-name
    origin-as
    prefix
    region
    route-originated-date
    route-originated-ts

=head1 AUTHOR

Matt Hersant <matt_hersant@yahoo.com>

=cut
