The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Mojo::YR;

=head1 NAME

Mojo::YR - Get weather information from yr.no

=head1 DESCRIPTION

L<Mojo::YR> is an (a)synchronous weather data fetcher for the L<Mojolicious>
framework. The backend for weather data is L<http://yr.no>.

Look at the resources below for mere information about the API:

=over 4

=item * L<http://api.yr.no/weatherapi/documentation>

=item * L<http://api.yr.no/weatherapi/locationforecast/1.8/documentation>

=item * L<http://api.yr.no/weatherapi/textforecast/1.6/documentation>

=back

=head1 SYNOPSIS

  use Mojo::YR;
  my $yr = Mojo::YR->new;

  # Fetch location_forecast ==========================================
  my $now = $yr->location_forecast([59, 10])->find('pointData > time')->first;
  my $temp = $now->at('temperature');

  warn "$temp->{value} $temp->{unit}";

  # Fetch text_forecast ==============================================
  my $today = $yr->text_forecast->children('time')->first;
  my $hordaland = $today->at('area[name="Hordaland"]');

  warn $hordaland->at('header')->text;
  warn $hordaland->at('in')->text; # "in" holds the forecast text

=cut

use Mojo::Base -base;
use Mojo::UserAgent;

our $VERSION = '0.02';

=head1 ATTRIBUTES

=head2 url_map

  $hash_ref = $self->url_map;

Returns the URL used to fetch data.

Note: These will always be what you expect. If the resources get changed in
the future, a C<version()> attribute will be added to this class to ensure
you always get the same URL map.

Default:

  {
    location_forecast => 'http://api.yr.no/weatherapi/locationforecast/1.8/',
    text_forecast => 'http://api.yr.no/weatherapi/textforecast/1.6/',
  };

=cut

has url_map => sub {
  my $self = shift;

  return {
    location_forecast => 'http://api.yr.no/weatherapi/locationforecast/1.8/',
    text_forecast     => 'http://api.yr.no/weatherapi/textforecast/1.6/',
    text_location_forecast => 'http://api.yr.no/weatherapi/textlocation/1.0/',
    sunrise                => 'http://api.yr.no/weatherapi/sunrise/1.0/',
  };
};

has _ua => sub {
  Mojo::UserAgent->new;
};

=head1 METHODS

=head2 location_forecast

  $self = $self->location_forecast([$latitude, $longitude], sub { my($self, $err, $dom) = @_; ... });
  $self = $self->location_forecast(\%args, sub { my($self, $err, $dom) = @_; ... });
  $dom = $self->location_forecast([$latitude, $longitude]);
  $dom = $self->location_forecast(\%args);

Used to fetch
L<weather forecast for a specified place|http://api.yr.no/weatherapi/locationforecast/1.8/documentation>.

C<%args> is required (unless C<[$latitude,$longitude]> is given):

  {
    latitude => $num,
    longitude => $num,
  }

C<$dom> is a L<Mojo::DOM> object you can use to query the result.
See L</SYNOPSIS> for example.

=cut

sub location_forecast {
  my ($self, $args, $cb) = @_;
  my $url = Mojo::URL->new($self->url_map->{location_forecast});

  if (ref $args eq 'ARRAY') {
    $args = {latitude => $args->[0], longitude => $args->[1]};
  }
  if (2 != grep { defined $args->{$_} } qw( latitude longitude )) {
    return $self->$cb('latitude and/or longitude is missing', undef);
  }

  $url->query([lon => $args->{longitude}, lat => $args->{latitude},]);

  $self->_run_request($url, $cb);
}

=head2 text_location_forecast

  $self = $self->text_location_forecast([$latitude, $longitude], sub { my($self, $err, $dom) = @_; ... });
  $self = $self->text_location_forecast(\%args, sub { my($self, $err, $dom) = @_; ... });
  $dom = $self->text_location_forecast([$latitude, $longitude]);
  $dom = $self->text_location_forecast(\%args);

Used to fetch
L<textual weather forecast for a specified place|http://api.yr.no/weatherapi/textlocation/1.0/documentation>.

C<%args> is required (unless C<[$latitude,$longitude]> is given):

  {
    latitude => $num,
    longitude => $num,
    language => 'nb', # default
  }

C<$dom> is a L<Mojo::DOM> object you can use to query the result.
See L</SYNOPSIS> for example.

=cut

sub text_location_forecast {
  my ($self, $args, $cb) = @_;
  my $url = Mojo::URL->new($self->url_map->{text_location_forecast});

  if (ref $args eq 'ARRAY') {
    $args = {latitude => $args->[0], longitude => $args->[1]};
  }
  if (2 != grep { defined $args->{$_} } qw( latitude longitude )) {
    return $self->$cb('latitude and/or longitude is missing', undef);
  }
  $args->{language} ||= 'nb';

  $url->query($args);

  $self->_run_request($url, $cb);
}


=head2 text_forecast

  $dom = $self->text_forecast(\%args);
  $self = $self->text_forecast(\%args, sub { my($self, $err, $dom) = @_; ... });

Used to fetch
L<textual weather forecast for all parts of the country|http://api.yr.no/weatherapi/textforecast/1.6/documentation>.

C<%args> is optional and has these default values:

  {
    forecast => 'land',
    language => 'nb',
  }

C<$dom> is a L<Mojo::DOM> object you can use to query the result.
See L</SYNOPSIS> for example.

=cut

sub text_forecast {
  my $cb   = ref $_[-1] eq 'CODE' ? pop : undef;
  my $self = shift;
  my $args = shift || {};
  my $url  = Mojo::URL->new($self->url_map->{text_forecast});

  $url->query(
    [
      forecast => $args->{forecast} || 'land',
      language => $args->{language} || 'nb',
    ]
  );

  $self->_run_request($url, $cb);
}

=head2 sunrise

  $dom = $self->sunrise(\%args);
  $self = $self->sunrise(\%args, sub { my($self, $err, $dom) = @_; ... });

Used to fetch
L<When does the sun rise and set for a given place|http://api.yr.no/weatherapi/sunrise/1.0/documentation>

C<%args> is required 

  {
    lat => $num,
    lon => $num,
    date => 'YYYY-MM-DD', # OR
    from => 'YYYY-MM-DD',
    to   => 'YYYY-MM-DD',
  }

C<$dom> is a L<Mojo::DOM> object you can use to query the result.
See L</SYNOPSIS> for example.

=cut

sub sunrise {
  my $cb   = ref $_[-1] eq 'CODE' ? pop : undef;
  my $self = shift;
  my $args = shift || {};
  my $url  = Mojo::URL->new($self->url_map->{sunrise});

  $url->query($args);

  $self->_run_request($url, $cb);
}

sub _run_request {
  my ($self, $url, $cb) = @_;

  if (!$cb) {
    my $tx = $self->_ua->get($url);
    die scalar $tx->error if $tx->error;
    return $tx->res->dom->children->first;
  }

  Scalar::Util::weaken($self);
  $self->_ua->get(
    $url,
    sub {
      my ($ua, $tx) = @_;
      my $err = $tx->error;

      return $self->$cb($err, undef) if $err;
      return $self->$cb('', $tx->res->dom->children->first)
        ;    # <weather> is the first element. don't want that
    },
  );

  return $self;
}

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2014 Jan Henning Thorsen

This program is free software, you can redistribute it and/or modify it under
the terms of the Artistic License version 2.0.

=head1 AUTHOR

Jan Henning Thorsen - C<jhthorsen@cpan.org>
Marcus Ramberg - C<mramberg@cpan.org>

=cut

1;