The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Net::Google::Spreadsheets::Table;
use Any::Moose;
use Any::Moose '::Util::TypeConstraints';
use Net::Google::DataAPI;
use XML::Atom::Util qw(nodelist first create_element);
use Net::Google::Spreadsheets::Worksheet;
use Net::Google::Spreadsheets::Record;

with 'Net::Google::DataAPI::Role::Entry';

subtype 'ColumnList'
    => as 'ArrayRef[Net::Google::Spreadsheets::Table::Column]';
coerce 'ColumnList'
    => from 'ArrayRef[HashRef]'
    => via {
        [ map {Net::Google::Spreadsheets::Table::Column->new($_)} @$_ ]
    };
coerce 'ColumnList'
    => from 'ArrayRef[Str]'
    => via {
        my @c;
        my $index = 0;
        for my $value (@$_) {
            push @c, Net::Google::Spreadsheets::Table::Column->new(
                index => ++$index,
                name => $value,
            );
        }
        \@c;
    };
coerce 'ColumnList'
    => from 'HashRef'
    => via {
        my @c;
        while (my ($key, $value) = each(%$_)) {
            push @c, Net::Google::Spreadsheets::Table::Column->new(
                index => $key,
                name => $value,
            );
        }
        \@c;
    };

subtype 'WorksheetName'
    => as 'Str';
coerce 'WorksheetName'
    => from 'Net::Google::Spreadsheets::Worksheet'
    => via {
        $_->title
    };

feedurl record => (
    entry_class => 'Net::Google::Spreadsheets::Record',
    arg_builder => sub {
        my ($self, $args) = @_;
        return {content => $args};
    },
    as_content_src => 1,
);

has summary => ( is => 'rw', isa => 'Str' );
has worksheet => ( is => 'ro', isa => 'WorksheetName', coerce => 1 );
has header => ( is => 'ro', isa => 'Int', required => 1, default => 1 ); 
has start_row => ( is => 'ro', isa => 'Int', required => 1, default => 2 );
has num_rows => ( is => 'ro', isa => 'Int' );
has columns => ( is => 'ro', isa => 'ColumnList', coerce => 1 );
has insertion_mode => ( is => 'ro', isa => (enum ['insert', 'overwrite']), default => 'overwrite' );

after from_atom => sub {
    my ($self) = @_;
    my $gsns = $self->ns('gs')->{uri};
    my $elem = $self->elem;
    $self->{summary} = $self->atom->summary;
    $self->{worksheet} = first( $elem, $gsns, 'worksheet')->getAttribute('name');
    $self->{header} = first( $elem, $gsns, 'header')->getAttribute('row');
    my @columns;
    my $data = first($elem, $gsns, 'data');
    $self->{insertion_mode} = $data->getAttribute('insertionMode');
    $self->{start_row} = $data->getAttribute('startRow');
    $self->{num_rows} = $data->getAttribute('numRows');
    for (nodelist($data, $gsns, 'column')) {
        push @columns, Net::Google::Spreadsheets::Table::Column->new(
            index => $_->getAttribute('index'),
            name => $_->getAttribute('name'),
        );
    }
    $self->{columns} = \@columns;
};

around to_atom => sub {
    my ($next, $self) = @_;
    my $entry = $next->($self);
    my $gsns = $self->ns('gs')->{uri};
    $entry->summary($self->summary) if $self->summary;
    $entry->set($gsns, 'worksheet', '', {name => $self->worksheet});
    $entry->set($gsns, 'header', '', {row => $self->header});
    my $data = create_element($gsns, 'data');
    $data->setAttribute(startRow => $self->start_row);
    $data->setAttribute(insertionMode => $self->insertion_mode);
    $data->setAttribute(numRows => $self->num_rows) if $self->num_rows;
    for ( @{$self->columns} ) {
        my $column = create_element($gsns, 'column');
        $column->setAttribute(index => $_->index);
        $column->setAttribute(name => $_->name);
        $data->appendChild($column);
    }
    $entry->set($gsns, 'data', $data);
    return $entry;
};

__PACKAGE__->meta->make_immutable;

no Any::Moose;
no Any::Moose '::Util::TypeConstraints';
no Net::Google::DataAPI;

package # hide from PAUSE
    Net::Google::Spreadsheets::Table::Column;
use Any::Moose;

has 'index' => ( is => 'ro', isa => 'Str' );
has 'name' => ( is => 'ro', isa => 'Str' );

__PACKAGE__->meta->make_immutable;

no Any::Moose;

1;
__END__

=head1 NAME

Net::Google::Spreadsheets::Table - A representation class for Google Spreadsheet table.

=head1 SYNOPSIS

  use Net::Google::Spreadsheets;

  my $service = Net::Google::Spreadsheets->new(
    username => 'mygoogleaccount@example.com',
    password => 'mypassword',
  );

  # get a table
  my $table = $service->spreadsheet(
    {
        title => 'list for new year cards',
    }
  )->table(
    {
        title => 'sample table',
    }
  );

  # create a record
  my $r = $table->add_record(
    {
        name => 'Nobuo Danjou',
        nick => 'lopnor',
        mail => 'danjou@soffritto.org',
        age  => '33',
    }
  );

  # get records
  my @records = $table->records;

  # search records
  @records = $table->records({sq => 'age > 20'});
  
  # search a record 
  my $record = $table->record({sq => 'name = "Nobuo Danjou"'});

=head1 METHODS

=head2 records(\%condition)

Returns a list of Net::Google::Spreadsheets::Record objects. Acceptable arguments are:

=over 2

=item * sq

Structured query on the full text in the worksheet. see the URL below for detail.

=item * orderby

Set column name to use for ordering.

=item * reverse

Set 'true' or 'false'. The default is 'false'. It reverses the order.

=back

See L<http://code.google.com/intl/en/apis/spreadsheets/docs/3.0/reference.html#RecordParameters> for details.

=head2 record(\%condition)

Returns first item of records(\%condition) if available.

=head2 add_record(\%contents)

Creates new record and returns a Net::Google::Spreadsheets::Record object representing it. 
Arguments are contents of a row as a hashref.

  my $record = $table->add_record(
    {
        name => 'Nobuo Danjou',
        nick => 'lopnor',
        mail => 'danjou@soffritto.org',
        age  => '33',
    }
  );

=head1 CAVEATS

You can access to the table structure and records only from the Google Spreadsheets API. It means you can't create tables, add records or delete tables via web interface. It will make some problems if you want to access the data both from API and web interface. In that case, you should use $worksheets->row(s) methods instead. See the documents below for details:

L<http://code.google.com/intl/en/apis/spreadsheets/data/3.0/developers_guide_protocol.html#TableFeeds>

=head1 SEE ALSO

L<https://developers.google.com/google-apps/spreadsheets/>

L<Net::Google::AuthSub>

L<Net::Google::Spreadsheets>

L<Net::Google::Spreadsheets::Spreadsheet>

L<Net::Google::Spreadsheets::Record>

=head1 AUTHOR

Nobuo Danjou E<lt>danjou@soffritto.orgE<gt>

=cut