package ATR::Dataset::Grid;

use strict;
use warnings;
use base 'ATR::Dataset';


=head1 NAME

ATR::Dataset::Grid - data storage by 2D array

=head1 DESCRIPTION

Subclass is instantiated from L<ATR::Dataset>x-E<gt>new_sqlfetch.  Docs
are there.

=cut

# $Id: Grid.pm,v 1.8 2005/05/23 16:04:37 mca Exp $

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

sub _init {
    my ($self, $fields, $rows) = @_;
    my $name = $self->name;

    # Check and store the DBI-style row data reference
    $self->replace_rows($rows);

    # Generate column map for as many fields as we can see
    my (%col2num, @cols);
    my $num_cols = ($rows && @$rows) ? @{ $rows->[0] } : @$fields;
    $num_cols = @$fields if $num_cols < @$fields;

    for (my $i=0; $i<$num_cols; $i++) {
	my $colname = $fields->[$i];
	$colname = $name."_".$self->__getNextIdent() if !defined $colname;
	$cols[$i] = $colname;
	$col2num{$colname} = $i;
    }
    $self->{col2num} = \%col2num;
    $self->{cols} = \@cols;
}


sub rowCount { scalar @{ $_[0]->{rows} } }


sub get {
    my ($self, $colId, $rowId) = @_;
    die "Object method needs 2 args" unless @_ == 3;

    my $colnum = $self->colid2num($colId);
    my $row = $self->_rowref($rowId, $colnum, $colId);

    return $row->[$colnum];
}

sub set {
    my ($self, $colId, $rowId, $value) = @_;
    die "Object method needs 3 args" unless @_ == 4;

    my $colnum = $self->colid2num($colId);
    my $row = $self->_rowref($rowId, $colnum, $colId);

    $row->[$colnum] = $value;
}

# can set any valid column
sub canSet { defined ($_[0]->colid2num($_[1])) }

sub _rowref {
    my ($self, $rowId, $colnum, $colId) = @_;
    my $check_colId = (@_ > 2);

    die +(caller(1))[3]." for '".$self->name."' needs \$rowId"
      unless defined $rowId;
    my $row = $self->{rows}->[$rowId];
    die +(caller(1))[3]." for '".$self->name."', rowId '$rowId' out of range"
      unless defined $row;

    if ($check_colId) {
	die "Column id '$colId' unknown in '".$self->name."'"
	  unless defined $colnum;
	die "Column id '$colId' out of range ($colnum) for data in '".$self->name."'"
	  if $colnum >= @$row;
    }

    return $row;
}


=head2 replace_rows($dbi_style)

Replaces all the internal DBI-style data rows with a new arrayref.

  $obj-E<gt>replace_rows([ [1,2,3],
                           [4,5,6], ... ]);

XXX: Currently does NOT cause rowmap wrapper objects to reindex

XXX: Should have error checking on column count.  Match (scalar @colnames) as below?  Or just be consistent, to allow naming of extra columns?

=cut

sub replace_rows {
    my ($self, $new_rows) = @_;
    $self->{rows} = $new_rows;
}


=head2 append_row(\@row) or append_row(\%row)

Adds a new row to this Dataset, returns the new C<$rownum>.
XXX: Currently does NOT cause rowmap wrapper objects to reindex

Call with arrayref of new values in the correct column order, error if
the number of values does not match the number of columns as returned
by C<colnames>.

Call with hashref of colId/value pairs.  Unset values will be left
undef, invalid columns will cause an error and the incomplete row will
not be appended.

=cut

sub append_row {
    my ($self, $new) = @_;
    die "Object method needs 1 arg" unless @_ == 2;

    my $want_cols = $self->colnames;

    if (ref($new) eq 'HASH') {
	my @newvals;
	$#newvals = $want_cols-1;
	while (my ($colId, $value) = each %$new) {
	    my $colnum = $self->colid2num($colId);
	    $newvals[$colnum] = $value;
	}
	$new = \@newvals;
    }

    if (ref($new) eq 'ARRAY') {
	my $size = @$new;
	die "Column count $size should be $want_cols, adding row to '".$self->name."'"
	  unless $size == $want_cols;
    } else {
	die "Don't understand type '".ref($new)."', can't append_row to '".$self->name."'";
    }

    push @{ $self->{rows} }, $new;
    return $#{ $self->{rows} };
}


1;
