package SimpleDB::Class::ResultSet;
our $VERSION = '0.0300';

=head1 NAME

SimpleDB::Class::ResultSet - An iterator of items from a domain.

=head1 VERSION

version 0.0300

=head1 DESCRIPTION

This class is an iterator to walk to the items passed back from a query. 

=head1 METHODS

The following methods are available from this class.

=cut

use Moose;
use SimpleDB::Class::SQL;

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

=head2 new ( params )

Constructor.

=head3 params

A hash.

=head4 simpledb

Required. A L<SimpleDB::Class> object.

=head4 item_class

Required. A L<SimpleDB::Class::Item> subclass name.

=head4 result

A result as returned from the send_request() method from L<SimpleDB::Class::HTTP>. Either this or a where is required.

=head4 where

A where clause as defined in L<SimpleDB::Class::SQL>. Either this or a result is required.

=cut

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

=head2 where ( )

Returns the where passed into the constructor.

=cut

has where => (
    is          => 'ro',
    isa         => 'HashRef',
);

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

=head2 simpledb ( )

Returns the simpledb passed into the constructor.

=cut

has simpledb => (
    is          => 'ro',
    required    => 1,
);

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

=head2 item_class ( )

Returns the item_class passed into the constructor.

=cut

has item_class => (
    is          => 'ro',
    required    => 1,
);

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

=head2 result ( )

Returns the result passed into the constructor, or the one generated by fetch_result() if a where is passed into the constructor.

=head2 has_result () 

A boolean indicating whether a result was passed into the constructor, or generated by fetch_result().

=cut

has result => (
    is          => 'rw',
    isa         => 'HashRef',
    predicate   => 'has_result',
    default     => sub {{}},
    lazy        => 1,
);

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

=head2 iterator ( )

Returns an integer which represents the current position in the result set as traversed by next().

=cut

has iterator => (
    is          => 'rw',
    default     => 0,
);


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

=head2 fetch_result ( )

Fetches a result, based on a where clause passed into a constructor, and then makes it accessible via the result() method.

=cut

sub fetch_result {
    my ($self) = @_;
    my $select = SimpleDB::Class::SQL->new(
        item_class  => $self->item_class,
        where       => $self->where,
    );
    my %params = (SelectExpression => $select->to_sql);

    # if we're fetching and we already have a result, we can assume we're getting the next batch
    if ($self->has_result) { 
        $params{NextToken} = $self->result->{SelectResult}{NextToken};
    }

    my $result = $self->simpledb->http->send_request('Select', \%params);
    $self->result($result);
    return $result;
}

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

=head2 next () 

Returns the next result in the result set. Also fetches th next partial result set if there's a next token in the first result set and you've iterated through the first partial set.

=cut

sub next {
    my ($self) = @_;
    # get the current results
    my $result = ($self->has_result) ? $self->result : $self->fetch_result;
    my $items = (ref $result->{SelectResult}{Item} eq 'ARRAY') ? $result->{SelectResult}{Item} : [$result->{SelectResult}{Item}];
    my $num_items = scalar @{$items};
    return undef unless $num_items > 0;

    # fetch more results if needed
    my $iterator = $self->iterator;
    if ($iterator >= $num_items) {
        if (exists $result->{SelectResult}{NextToken}) {
            $self->iterator(0);
            $iterator = 0;
            $result = $self->fetch_results;
        }
        else {
            return undef;
        }
    }

    # iterate
    my $item = $items->[$iterator];
    return undef unless defined $item;
    $iterator++;
    $self->iterator($iterator);

    # make the item object
    my $cache = $self->simpledb->cache;
    ## fetch from cache even though we've already pulled it back from the db, because the one in cache
    ## might be more up to date than the one from the DB
    my $attributes = eval{$cache->get($self->item_class->domain_name, $item->{Name})}; 
    my $e;
    if ($e = SimpleDB::Class::Exception::ObjectNotFound->caught) {
        my $itemobj = $self->handle_item($item->{Name}, $item->{Attribute});
        eval{$cache->set($self->item_class->domain_name, $item->{Name}, $itemobj->to_hashref)};
        return $itemobj;
    }
    elsif ($e = SimpleDB::Class::Exception->caught) {
        warn $e->error;
        return $e->rethrow;
    }
    elsif (defined $attributes) {
        return $self->item_class->new(id=>$item->{Name}, simpledb=>$self->simpledb)->update($attributes);
    }
    else {
        SimpleDB::Class::Exception->throw(error=>"An undefined error occured while fetching the item from cache.");
    }
}

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

=head2 handle_item ( id , attributes ) 

Converts the attributes section of an item in a result set into a L<SimpleDB::Class::Item> object.

=cut

sub handle_item {
    my ($self, $id, $list) = @_;
    unless (ref $list eq 'ARRAY') {
        $list = [$list];
    }
    my $item = $self->item_class->new(simpledb=>$self->simpledb, name=>$id);
    my $attributes = {};
    my $registered_attributes = $self->item_class->attributes;
    my $select = SimpleDB::Class::SQL->new(item_class=>$self->item_class); 
    my %added = ();
    foreach my $attribute (@{$list}) {
        my $name = $attribute->{Name};

        # add unknown attributes
        if (!exists $registered_attributes->{$name} && ! exists $added{$name}) {
           $item->add_attributes($name => { isa => 'Str' }); 
           $added{$name} = 1;
        }

        # get value
        my $value = $select->parse_value($name, $attribute->{Value});

        # create expected hashref
        if (exists $attributes->{$name}) {
            if (ref $attributes->{$name} ne 'ARRAY') {
                $attributes->{$name} = [$attributes->{$name}];
            }
            push @{$attributes->{$name}}, $value;
        }
        else {
            $attributes->{$name} = $value;
        }

    }
    return $item->update($attributes);
}

=head1 LEGAL

SimpleDB::Class is Copyright 2009 Plain Black Corporation (L<http://www.plainblack.com/>) and is licensed under the same terms as Perl itself.

=cut

no Moose;
__PACKAGE__->meta->make_immutable;