package Google::API::Client;

use strict;
use 5.008_001;
our $VERSION = '0.15';

use Google::API::Method;
use Google::API::Resource;


use constant AUTH_URI => 'https://accounts.google.com/o/oauth2/auth';
use constant TOKEN_URI => 'https://accounts.google.com/o/oauth2/token';

sub new {
    my $class = shift;
    my ($param) = @_;
    unless (defined $param->{ua}) {
        $param->{ua} = $class->_new_ua;
    }
    unless (defined $param->{json_parser}) {
        $param->{json_parser} = $class->_new_json_parser;
    }
    bless { %$param }, $class;
}

sub build {
    my $self = shift;
    my ($service, $version, $args) = @_;

    my $discovery_service_url;
    if ($args->{discovery_service_url}) {
        $discovery_service_url = $args->{discovery_service_url};
    } else {
        $discovery_service_url = 'https://{api}.googleapis.com/$discovery/rest';
        if ($version) {
            $discovery_service_url .= '?version={apiVersion}';
        }

        $service = $self->_replace_to_subdomain($service);

        if ($self->_is_v1_discovery_url($service, $version)) {
            $discovery_service_url = 'https://www.googleapis.com/discovery/v1/apis/{api}/{apiVersion}/rest';
        }
    }
    $discovery_service_url =~ s/{api}/$service/;
    $discovery_service_url =~ s/{apiVersion}/$version/;

    my $req = HTTP::Request->new(GET => $discovery_service_url);
    my $res = $self->{ua}->request($req);
    $self->{ua}{response} = $res;
    unless ($res->is_success) {
        # throw an error
        die 'could not get service document.' . $res->status_line;
    }
    my $document = $self->{json_parser}->decode($res->content);
    $self->build_from_document($document, $discovery_service_url, $args);
}

sub build_from_document {
    my $self = shift;
    my ($document, $url, $args) = @_;
    my $base = $document->{rootUrl}.$document->{servicePath};
    my $base_url = URI->new($base);
    my $resource = $self->_create_resource($document, $base_url, $args); 
    return $resource;
}

sub _create_resource {
    my $self = shift;
    my ($document, $base_url, $args) = @_;
    my $root_resource_obj = Google::API::Resource->new;
    for my $resource (keys %{$document->{resources}}) {
        my $resource_obj;
        if ($document->{resources}{$resource}{resources}) {
            $resource_obj = $self->_create_resource($document->{resources}{$resource}, $base_url, $args);
        }
        if ($document->{resources}{$resource}{methods}) {
            unless ($resource_obj) {
                $resource_obj = Google::API::Resource->new;
            }
            for my $method (keys %{$document->{resources}{$resource}{methods}}) {
                $resource_obj->set_attr($method, sub {
                    my (%param) = @_;
                    return Google::API::Method->new(
                        ua => $self->{ua},
                        json_parser => $self->{json_parser},
                        base_url => $base_url,
                        doc => $document->{resources}{$resource}{methods}{$method},
                        opt => \%param,
                    );
                });
            }
        }
        $root_resource_obj->set_attr($resource, sub { $resource_obj } );
    }
    if ($document->{auth}) {
        $root_resource_obj->{auth_doc} = $document->{auth};
    }
    return $root_resource_obj;
}

sub _new_ua {
    my $class = shift;
    require LWP::UserAgent;
    my $ua = LWP::UserAgent->new;
    return $ua;
}

sub _new_json_parser {
    my $class = shift;
    require JSON;
    my $parser = JSON->new;
    return $parser;
}

sub _replace_to_subdomain {
    my ($self, $service) = @_;

    # Following services are different from subdomains.
    # It needs to be converted.
    my %replacement = (
        'adexchangebuyer2'  => 'adexchangebuyer',
        'calendar'          => 'calendar-json',
        'content'           => 'shoppingcontent',
        'prod_tt_sasportal' => 'prod-tt-sasportal',
        'translate'         => 'translation',
    );
    if (grep { $service eq $_ } keys %replacement) {
        $service = $replacement{$service};
    }
    return $service;
}

sub _is_v1_discovery_url {
    my ($self, $service, $version) = @_;
    # Following services are still using V1 type URL
    if (($service eq 'compute' && $version eq 'alpha') ||
        ($service eq 'compute' && $version eq 'beta') ||
        ($service eq 'compute' && $version eq 'v1') ||
        ($service eq 'drive' && $version eq 'v2') ||
        ($service eq 'drive' && $version eq 'v3') ||
        ($service eq 'oauth2' && $version eq 'v2')) {
        return 1;
    }
    return;
}

1;
__END__

=encoding utf-8

=for stopwords

=head1 NAME

Google::API::Client - A client for Google APIs Discovery Service

=head1 SYNOPSIS

  use Google::API::Client;

  my $client = Google::API::Client->new;
  my $service = $client->build('urlshortener', 'v1');

  # Get shortened URL 
  my $body = {
      'longUrl' => 'http://code.google.com/apis/urlshortener/',
  };
  my $result = $url->insert(body => $body)->execute;
  $result->{id}; # shortened URL

=head1 DESCRIPTION

Google::API::Client is a client for Google APIs Discovery Service. You make using Google APIs easy.

=head1 METHODS

=over 4

=item new

=item build

Construct a resource for interacting with an API. The service name and version
are passed to specify the build function to retrieve the appropriate discovery
document from the server. Calls C<build_from_document()> with the downloaded file.

=item build_from_document

Same as the C<build()> function, but the document is to be passed I<locally>
instead of being downloaded. The C<discovery_service_url> is a deprecated 
argument. Instead, the URL is constructed by combining the C<rootUrl> and 
the C<servicePath>.

=back

=head1 AUTHOR

Takatsugu Shigeta E<lt>[email protected]<gt>

=head1 CONTRIBUTORS

Yusuke Ueno (uechoco)

Gustavo Chaves (gnustavo)

Hatsuhito UENO (uehatsu)

chylli

Richie Foreman <[email protected]> (richieforeman)

ljanvier

razsh

=head1 COPYRIGHT

Copyright 2011- Takatsugu Shigeta

=head1 LICENSE

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=head1 SEE ALSO

=cut