#!/usr/bin/perl -w

# Uycie: geodist.pl --from="fromaddr" --to="toaddr" [--unit="unit"]
# Zobacz ParseAddress(  ) poniej, jeli chodzi o format adresw. Domylna
# jednostka to "mile" (mila); inne moliwe jednostki to yard, foot, inch, 
# kilometer, meter, centimeter (odpowiednio jard, stopa, cal, kilometr,
# metr, centymetr).

use strict;
use Getopt::Long;
use Geo::Distance;
use HTTP::Request::Common;
use LWP::UserAgent;

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

my $_ADDRESS_REGEX = q<(((([^\,]+),\s*)?([^\,]+),\s*)?([A-Z]{2}))?> .
  q<(\s*(\d{5}(-\d{4})?))?>;

sub ParseAddress {

  # W miar niezawodne wyraenie regularne analizujce adres w postaci:
  #   Dom i ulica, Miast, ST KOD
  # Zakada si, e z miasta wynika stan, za z ulicy - miast; jeli nie,
  # wszystkie pola s opcjonalne. Dobrze dziaa, o ile tylko w nazwach
  # ulicy i miasta nie pojawi si przecinki.
  
  my $AddrIn = shift;
  my $ComponentsOut = shift;
  $AddrIn =~ /$_ADDRESS_REGEX/;
  $ComponentsOut->{Address} = $4 if $4;
  $ComponentsOut->{City} = $5 if $5;
  $ComponentsOut->{State} = $6 if $6;
  $ComponentsOut->{Zip} = $8 if $8;
}

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

sub GetPosition {

  # Wymusza na mappoint.msn.com zwrcenie dugoci i szerokoci geograficznej
  # adresu. MapPoint nie zwraca uytkownikowi dug/szer, ale dane te mona 
  # znale  w nagwku Location, po zadaniu prawidowego zapytania o map.
  # Jak wykazay testy, rozwizanie to dziaa niezawodnie. Najwikszym 
  # problemem jest obecnie bdna obsuga sytuacji, kiedy MapPoint znajdzie
  # wiele pasujcych adresw.

  my $AddressIn = shift;
  my $LatitudeOut = shift;
  my $LongitudeOut = shift;

  # Tworzenie agenta obsugujcego dania HTTP.
  my $ua = LWP::UserAgent->new;

  # Najpierw proste danie w celu przekierowania przez MapPoint.
  my $req = GET( 'http://mappoint.msn.com/' );
  my $res = $ua->simple_request( $req );

  # Zapisujemy URL przekierowania, pobieramy ca stron.
  my $uri = $res->headers->{location};
  my $req = GET( 'http://mappoint.msn.com' . $uri );
  my $res = $ua->request( $req );

  # Pobieramy warto pola ukrytego _  _VIEWSTATE.
  my ( $_  _VIEWSTATE ) =
    $res->content =~ /name="_  _VIEWSTATE" value="([^\"]*)"/s;

  # Przygotowanie pozostaych pl oczekiwanych przez serwis.
  my $req = POST( 'http://mappoint.msn.com' . $uri,
    [ 'FndControl:SearchType' => 'Address',
      'FndControl:ARegionSelect' => '12',
      'FndControl:StreetText' => $AddressIn->{Address},
      'FndControl:CityText' => $AddressIn->{City},
      'FndControl:StateText' => $AddressIn->{State},
      'FndControl:ZipText' => $AddressIn->{Zip},
      'FndControl:isRegionChange' => '0',
      'FndControl:resultOffSet' => '0',
      'FndControl:BkARegion' => '12',
      'FndControl:BkPRegion' => '15',
      'FndControl:hiddenSearchType' => '',
      '__VIEWSTATE' => $_  _VIEWSTATE
    ] );

  # Dziaa bez pola referer, ale na wszelki wypadek je wczamy.
  $req->push_header( 'Referer' => 'http://mappoint.msn.com' . $uri );

  # Wysyamy proste danie - interesuje nas tylko URL przekierowania.
  my $res = $ua->simple_request( $req );

  # Pobieramy i zwracamy szeroko i dugo z adresu URL.
  ( $$LatitudeOut, $$LongitudeOut ) = $res->headers->{location} =~
    /C=(-?[0-9]+\.[0-9]+)...(-?[0-9]+\.[0-9]+)/;
}

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

sub main {

  # Pobranie opcji wiersza polece.
  my ( $FromOpt, %FromAddress, $ToOpt, %ToAddress );
  my $UnitOpt = 'mile';
  GetOptions( "from=s" => \$FromOpt,
              "to=s"   => \$ToOpt,
              "unit=s" => \$UnitOpt );

  # Analiza adresu.
  ParseAddress( $FromOpt, \%FromAddress );
  ParseAddress( $ToOpt, \%ToAddress );

  # Pobranie szerokoci i dugoci geograficznej adresu.
  my ( $FromLat, $FromLon, $ToLat, $ToLon );
  GetPosition( \%FromAddress, \$FromLat, \$FromLon );
  GetPosition( \%ToAddress, \$ToLat, \$ToLon );

  # Jeli mamy przynajmniej cz liczb, szukamy odlegoci.
  if ( $FromLat && $FromLon && $ToLat && $ToLon ) {
    print "($FromLat,$FromLon) do ($ToLat,$ToLon) to ";
    my $geo = new Geo::Distance;
    print $geo->distance_calc( $UnitOpt, $FromLon,
                               $FromLat, $ToLon, $ToLat );
    if ( $UnitOpt eq 'inch' ) { print " cali\n"; }
    elsif ( $UnitOpt eq 'foot' ) { print " stp\n"; }
    else { print " ", $UnitOpt, "s\n"; }
  }
  else {
    print "Nie udao si pobra dla adresu FROM wsprzdnych\n"
      if !( $FromLat && $FromLon );
    print " Nie udao si pobra dla adresu TO wsprzdnych\n"
      if !( $ToLat && $ToLon );
  }
}

main(  );

