package Geo::USCensus::Geocoding;

use strict;
use warnings;

use LWP::UserAgent;
use JSON;
use URI;
use Geo::USCensus::Geocoding::Match;

=head1 NAME

Geo::USCensus::Geocoding - The U.S. Census Bureau geocoding service

=head1 VERSION

Version 0.01

=cut

our $VERSION = '0.01';
our $DEBUG = 0;

=head1 SYNOPSIS

    use Geo::USCensus::Geocoding;

    my $request = {
      # required fields
      street  => '123 Main Street',
      city    => 'San Francisco',   # city
      state   => 'CA',              # state/province
      zip     => '93102',           # zip/postal code
      # optional fields
      benchmark => 'Public_AR_ACS2013', # default is "Public_AR_Current"
      vintage   => 'Census2010_ACS2013', # default is "Current_Current"

      debug => 1,                   # will print the URL and some other info
    };
    my $result = Geo::USCensus::Geocoding->query($request);

    if ($result->matches) {
      my $match = $result->match(0);
      print $match->matchedAddress,"\n",
            $match->coordinates->{x},',',$match->coordinates->{y},"\n",
            $match->censustract,"\n";
    }

=head1 CLASS METHODS

=head2 query HASHREF

Send a request to the web service.  See
L<http://geocoding.geo.census.gov/geocoder> for API documentation. This 
package will always use the JSON data format and the Geographies return type.

Returns an object of class Geo::USCensus::Geocoding.

=cut

my $ua = LWP::UserAgent->new;
my $api_uri = 'http://geocoding.geo.census.gov/geocoder/geographies/address';

sub query {
  my $class = shift;
  my %opt = (
    benchmark => 'Public_AR_Census2010',
    vintage   => 'Census2010_Census2010',
  );
  if (ref $_[0] eq 'HASH') {
    %opt = (%opt, %{ $_[0] });
  } else {
    %opt = (%opt, @_);
  }

  $DEBUG = $opt{debug} || 0;
  $opt{format} = 'json';

  foreach (qw(street city state zip)) {
    die "$_ required\n" unless length($opt{$_});
  }

  my $uri = URI->new($api_uri);
  $uri->query_form(\%opt);
  warn "$class->query\n$uri\n\n" if $DEBUG;
  my $http_req = HTTP::Request->new(GET => $uri->as_string);
  my $resp = $ua->request($http_req);
  my $self = { addr_response => $resp };
  bless $self, $class;
  if ( $resp->is_success ) {
    local $@;
    my $tree = eval { from_json($resp->content) };
    if ($@) {
      $self->message("Unable to parse response:\n$@");
      return $self;
    }
    if (!exists $tree->{result}) {
      $self->message("Response does not contain geocoding results.");
      warn $self->message. "\n".$resp->content."\n\n";
      return $self;
    }
    $tree = $tree->{result};

    my @matches;
    if (exists( $tree->{addressMatches} )) {
      foreach my $am (@{ $tree->{addressMatches} }) {
        push @matches, Geo::USCensus::Geocoding::Match->new($am);
      }
    } # else what? does this happen if there's no match? a proper REST 
      # interface should throw a 404
    $self->{matches} = \@matches;
  } else {
    $self->message( $resp->status_line );
  }
  $self;
}

=head1 METHODS

=head2 message

Sets/gets an explicit error status.

=cut

sub message {
  my $self = shift;
  if (@_) {
    $self->{_message} = shift;
  }
  $self->{_message} || '';
}

=head2 matches

Returns the number of matches found. 

=cut

sub matches {
  my $self = shift;
  $self->{matches} ? scalar @{ $self->{matches} } : 0;
}

=head2 match NUMBER

Returns a specific match (starting from zero). Matches are returned 
as L<Geo::USCensus::Geocoding::Match> objects, in the order they were 
returned by the service.

=cut

sub match {
  my $self = shift;
  my $i = shift;
  $self->{matches}->[$i];
}

=head1 AUTHOR

Mark Wells, C<< <mark at freeside.biz> >>

=head1 SUPPORT

Commercial support for this module is available from Freeside Internet 
Services:

    L<http://www.freeside.biz/>

=back


=head1 LICENSE AND COPYRIGHT

Copyright (C) 2014 Mark Wells.

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

See http://dev.perl.org/licenses/ for more information.

=cut

1;
