package Amazon::SimpleDB;
use strict;
use warnings;

our $VERSION = '0.01';

use URI;
use LWP::UserAgent;
use Digest::HMAC_SHA1;
use MIME::Base64 qw(encode_base64);
use Carp qw( croak );

use Amazon::SimpleDB::Domain;
use Amazon::SimpleDB::Response;

use constant SERVICE_URI          => 'https://sdb.amazonaws.com/';
use constant KEEP_ALIVE_CACHESIZE => 10;

sub new {
    my $class = shift;
    my $args  = shift || {};
    my $self  = bless $args, $class;
    croak "No aws_access_key_id"     unless $self->{aws_access_key_id};
    croak "No aws_secret_access_key" unless $self->{aws_secret_access_key};
    unless ($self->{agent}) {
        my $agent = LWP::UserAgent->new(keep_alive => KEEP_ALIVE_CACHESIZE);
        $agent->timeout(10);
        $agent->env_proxy;
        $self->{agent} = $agent;
    }
    return $self;
}

sub domains {
    my $self   = shift;
    my $args   = shift || {};
    my $params = {};
    $params->{MaxNumberOfDomains} = $args->{'limit'} if $args->{'limit'};
    $params->{NextToken}          = $args->{'next'}  if $args->{'next'};
    return Amazon::SimpleDB::Response->new(
                                        $self->request('ListDomains', $params));
}

sub domain {
    return Amazon::SimpleDB::Domain->new({name => $_[1], account => $_[0]});
}

sub create_domain {    # note no more than 100 per account.
    my ($self, $name) = @_;
    my $res = $self->request('CreateDomain', {DomainName => $name});
    return
      Amazon::SimpleDB::Response->new(http_response => $res,
                                      account       => $self,);
}

sub delete_domain {
    my ($self, $name) = @_;
    my $res = $self->request('DeleteDomain', {DomainName => $name});
    return
      Amazon::SimpleDB::Response->new(http_response => $res,
                                      account       => $self,);
}

#--- utility methods

sub request {    # returns "raw" HTTP Response from SimpleDB
    my ($self, $action, $params) = @_;
    croak "No Action parameter" unless $action;
    $params ||= {};
    $params->{Action}           = $action;
    $params->{AWSAccessKeyId}   = $self->{aws_access_key_id};
    $params->{Version}          = '2007-11-07';
    $params->{SignatureVersion} = 1 unless defined $params->{SignatureVersion};
    $params->{Timestamp}        = timestamp() unless $params->{Expires};
    my $time = $params->{Expires} || $params->{Timestamp};
    my $sig = '';
    if ($params->{SignatureVersion} == 1) {
        $sig .= $_ . $params->{$_}
          for (sort { lc($a) cmp lc($b) } keys %$params)
          ;    # Must be alphabetical in a case-insensitive manner.
    } else {
        $sig = $params->{Action} . $time;
    }
    my $hash = Digest::HMAC_SHA1->new($self->{aws_secret_access_key});
    $hash->add($sig);
    $params->{Signature} = encode_base64($hash->digest, '');
    my $uri = URI->new(SERVICE_URI);
    $uri->query_form($params);
    return $self->{agent}->get($uri->as_string);
}

sub timestamp {
    my $t = shift;
    $t = time unless defined $t;
    my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
      gmtime($t);
    return
      sprintf("%4i-%02i-%02iT%02i:%02i:%02iZ",
              ($year + 1900),
              ($mon + 1),
              $mday, $hour, $min, $sec);
}

1;

__END__

=head1 NAME

Amazon::S3 - a "low-level" perlish interface for
working with Amazon's SimpleDB (SDB) service.

=head1 DESCRIPTION

B<This is code is in the early stages of development. Do not
consider it stable. Feedback and patches welcome.>

Amazon::SimpleDB provides a "low-level" perlish interface for
working with Amazon's SimpleDB (SMB) service. 

"Amazon SimpleDB is a web service for running queries on
structured data in real time. This service works in close
conjunction with Amazon Simple Storage Service (Amazon S3)
and Amazon Elastic Compute Cloud (Amazon EC2), collectively
providing the ability to store, process and query data sets
in the cloud. These services are designed to make web-scale
computing easier and more cost-effective for developers."

To sign up for an Amazon Web Services account, required to
use this library and the SimpleDB service, please visit the
Amazon Web Services web site at http://www.amazonaws.com/.

You will be billed accordingly by Amazon when you use this
module and must be responsible for these costs.

To learn more about Amazon's SimpleDB service, please visit:
http://simpledb.amazonaws.com/.

=head1 METHODS

=head2 Amazon::SimpleDB->new($args)

=head2 Amazon::SimpleDB->domains

=head2 $sdb->domain($name)

=head2 $sdb->create_domain($name)

=head2 $sdb->delete_domain($name)

=head2 $sdb->request($action,[\%args])

=head2 timestamp([$epoch])

=head1 TO DO

=over

=item Development of a proper test suite. Currently this
module is only testing that the code compiles. This priority
one.

=item Support the use of HTTP POST. Right now the module
only uses GET for everything. The SimpleDB is sadly GETsful
and not really REST.

=item Support retries of the SimpleDB service is a server
error (HTTP 5xx status) is returned before giving.

=item Implement a C<query_all> method that will
automatically issue multiple calls to the service if a
NextToken is returned.

=back

=head1 SUPPORT

Bugs should be reported via the CPAN bug tracker at

<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Amazon-SimpleDB>

For other issues, contact the author.

=head1 AUTHOR

Timothy Appnel <tima@cpan.org>

=head1 SEE ALSO

L<Amazon::S3>

=head1 COPYRIGHT AND LICENCE

The software is released under the Artistic License. The
terms of the Artistic License are described at
http://www.perl.com/language/misc/Artistic.html. Except
where otherwise noted, Amazon::SimpleDB is Copyright 2008,
Timothy Appnel, tima@cpan.org. All rights reserved.
