The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

package R::YapRI::Data::Matrix;

use strict;
use warnings;
use autodie;

use Carp qw(croak cluck carp);
use R::YapRI::Base;
use R::YapRI::Interpreter::Perl qw( r_var );


###############
### PERLDOC ###
###############

=head1 NAME

R::YapRI::Data::Matrix.pm

A module to build and pass a Matrix to a YapRI command file

=cut

our $VERSION = '0.06';
$VERSION = eval $VERSION;

=head1 SYNOPSIS

  use R::YapRI::Base;
  use R::YapRI::Data::Matrix;

  ## Constructors:

  my $rmatrix = R::YapRI::Data::Matrix->new({ name => 'matrix1' });
  
  ## Accessors:

  my $matrixname = $rmatrix->get_name();
  $rmatrix->set_name('matrix');

  my $coln = $rmatrix->get_coln();
  $rmatrix->set_coln(3);

  my $rown = $rmatrix->get_rown();
  $rmatrix->set_rown(4);

  my @data = $rmatrix->get_data();
  $rmatrix->set_data(\@data);

  my @colnames = $rmatrix->get_colnames();
  $rmatrix->set_colnames(\@colnames);

  my @rownames = $rmatrix->get_colanmes();
  $rmatrix->set_colnames(\@rownames);

  ## Adding/deleting/changing data:

  $rmatrix->set_coldata($colname, [$y1, $y2, $y3, $y4]);
  $rmatrix->set_rowdata($rowname, [$x1, $x2, $x3]);
  
  $rmatrix->add_column($colname, [$yy1, $yy2, $yy3, $yy4]);
  $rmatrix->add_row($rowname, [$xx1, $xx2, $xx3]);

  my @oldcol = $rmatrix->remove_column($colname);
  my @oldrow = $rmatrix->remove_row($rowname);

  $rmatrix->change_columns($col_x, $col_z);
  $rmatrix->change_rows($row_x, $row_z);


  ## Parsers:
     
  my $rbase = R::YapRI::Base->new();
  $rmatrix->send_rbase($rbase, $mode);
 
  $rmatrix = R::YapRI::Data::Matrix->read_rbase($rbase, $block, $r_object_name);

  ## Slicers:

  my @col2 = $rmatrix->get_column($col_y);
  my @row3 = $rmatrix->get_row($row_x);
  my $elem2_3 = $rmatrix->get_element($row_x, $col_y);


=head1 DESCRIPTION

R::YapRI::Data::Matrix is a L<R::YapRI> to manipulate matrices, send to an
rbase object (L<R::YapRI::Base>) for further R manipulations.

This module pass perl variables to a R::YapRI::Data::Matrix object that convert
them in a R command line that it is passed to R::YapRI::Base object as a block.

 +-----------+    +----------------------+    +------------+    +---+--------+
 | PerlData1 | => | YaRI::Data::Matrix 1 | => |            | => |   | Input  |
 +-----------+    +----------------------+    | YaRI::Base |    | R |--------+
 | PerlData2 | <= | YaRI::Data::Matrix 2 |<=  |            | <= |   | Output |
 +-----------+    +----------------------+    +------------+    +---+--------+

It lets export data to R frame as: matrix (default), data.frames, vector 
(by_rows or by_columns) and list (by_rows or by_columns).

Rbase communication is based in two functions:

+ send_rbase, to add the matrix to a rbase block.

+ read_rbase, to create a R::YapRI::Data::Matrix based in a R matrix contained
in a rbase block.

=head1 AUTHOR

Aureliano Bombarely <aurebg@vt.edu>


=head1 CLASS METHODS

The following class methods are implemented:

=cut 



############################
### GENERAL CONSTRUCTORS ###
############################

=head1 (*) CONSTRUCTORS:

=head2 constructor new

  Usage: my $rmatrix = R::YapRI::Data::Matrix->new();

  Desc: Create a new R::YapRI::Data::Matrix object

  Ret: a R::YapRI::Data::Matrix object

  Args: A hash reference with the following parameters:
          name     => a scalar with the matrix name, 
          coln     => a scalar with the column number (int),
          rown     => a scalar with the row number (int),
          colnames => an array ref. with the column names,
          rownames => an array ref. with the row names,
          data     => an array ref. with the matrix data ordered by row.        
        
  Side_Effects: Die if the argument used is not a hash or its values arent 
                right.
                If no coln and rown are used, it will set to 0 by default.

  Example: my $rmatrix = R::YapRI::Data::Matrix->new(
                                     { 
                                       name     => 'matrix1',
                                       coln     => 3,
                                       rown     => 4,
                                       colnames => ['a', 'b', 'c'],
                                       rownames => ['W', 'X', 'Y', 'Z'],
                                       data     => [ 1, 1, 1, 2, 2, 2, 
                                                     3, 3, 3, 4, 4, 4,],
                                     }
                                   );

          
=cut

sub new {
    my $class = shift;
    my $args_href = shift;

    my $self = bless( {}, $class ); 

    my %permargs = (
	name     => '\w+',
	coln     => '^\d+$',
	rown     => '^\d+$',
        colnames => [],
        rownames => [],
        data     => [],
	);

    ## Check variables.

    my %args = ();
    if (defined $args_href) {
	unless (ref($args_href) eq 'HASH') {
	    croak("ARGUMENT ERROR: Arg. supplied to new() isnt HASHREF");
	}
	else {
	    %args = %{$args_href}
	}
    }

    foreach my $arg (keys %args) {
	unless (exists $permargs{$arg}) {
	    croak("ARGUMENT ERROR: $arg isnt permited arg for new() function");
	}
	else {
	    unless (defined $args{$arg}) {
		croak("ARGUMENT ERROR: value for $arg isnt defined for new()");
	    }
	    else {
		if (ref($permargs{$arg})) {
		    unless (ref($permargs{$arg}) eq ref($args{$arg})) {
			croak("ARGUMENT ERROR: $args{$arg} isnt permited val.");
		    }
		}
		else {
		    if ($args{$arg} !~ m/$permargs{$arg}/) {
			croak("ARGUMENT ERROR: $args{$arg} isnt permited val.");
		    }
		}
	    }
	}
    }

    ## Pass the arguments to the accessors functions

    my $name = $args{name} || '';
    $self->set_name($name);

    my $coln = $args{coln} || 0;
    $self->set_coln($coln);

    my $rown = $args{rown} || 0;
    $self->set_rown($rown);

    my $colnames = $args{colnames} || [];
    $self->set_colnames($colnames);

    my $rownames = $args{rownames} || [];
    $self->set_rownames($rownames);

    my $data = $args{data} || [];
    $self->set_data($data);

    
    return $self;
}


###############
## ACCESSORS ##
###############

=head1 (*) ACCESSORS:


=head2 get_name

  Usage: my $name = $matrix->get_name();

  Desc: Get the Matrix name for a R::YapRI::Data::Matrix object

  Ret: $name, a scalar

  Args: None    
        
  Side_Effects: None

  Example: my $name = $matrix->get_name();
          
=cut

sub get_name {
    my $self = shift;
    return $self->{name};
}

=head2 set_name

  Usage: $matrix->set_name($name);

  Desc: Set the Matrix name for a R::YapRI::Data::Matrix object

  Ret: None

  Args: $name, an scalar    
        
  Side_Effects: Die if undef value is used 

  Example: $matrix->set_name($name);
          
=cut

sub set_name {
    my $self = shift;
    my $name = shift;

    unless (defined $name) {
	croak("ERROR: No defined name argument was supplied to set_name()");
    }

    $self->{name} = $name;
}


=head2 get_coln

  Usage: my $coln = $matrix->get_coln();

  Desc: Get the matrix column number for a R::YapRI::Data::Matrix object

  Ret: $coln, a scalar

  Args: None    
        
  Side_Effects: None

  Example: my $coln = $matrix->get_coln();
          
=cut

sub get_coln {
    my $self = shift;
    return $self->{coln};
}

=head2 set_coln

  Usage: $matrix->set_coln($coln);

  Desc: Set the matrix column number for a R::YapRI::Data::Matrix object

  Ret: None

  Args: $coln, an scalar    
        
  Side_Effects: Die if undef value is used.
                Die if colnum number is not a digit

  Example: $matrix->set_coln($coln);
          
=cut

sub set_coln {
    my $self = shift;
    my $coln = shift;

    unless (defined $coln) {
	croak("ERROR: No defined coln argument was supplied to set_coln()");
    }

    ## Let use '' as empty value but if it match with any character it 
    ## should be a digit

    if ($coln =~ m/^.+/) {
	unless ($coln =~ m/^\d+$/) {
	    croak("ERROR: $coln supplied to set_coln() isnt a digit.");
	}
    }

    $self->{coln} = $coln;
}


=head2 get_rown

  Usage: my $rown = $matrix->get_rown();

  Desc: Get the matrix row number for a R::YapRI::Data::Matrix object

  Ret: $rown, a scalar

  Args: None    
        
  Side_Effects: None

  Example: my $rown = $matrix->get_rown();
          
=cut

sub get_rown {
    my $self = shift;
    return $self->{rown};
}

=head2 set_rown

  Usage: $matrix->set_rown($rown);

  Desc: Set the matrix row number for a R::YapRI::Data::Matrix object

  Ret: None

  Args: $rown, an scalar    
        
  Side_Effects: Die if undef value is used.
                Die if rown number is not a digit

  Example: $matrix->set_rown($rown);
          
=cut

sub set_rown {
    my $self = shift;
    my $rown = shift;

    unless (defined $rown) {
	croak("ERROR: No defined rown argument was supplied to set_rown()");
    }

    ## Let use '' as empty value but if it match with any character it 
    ## should be a digit

    if ($rown =~ m/^.+/) {
	unless ($rown =~ m/^\d+$/) {
	    croak("ERROR: $rown supplied to set_rown() isnt a digit.");
	}
    }

    $self->{rown} = $rown;
}


=head2 get_colnames

  Usage: my $colnames_aref = $matrix->get_colnames();

  Desc: Get the Matrix column names array for a R::YapRI::Data::Matrix object

  Ret: $colnames_aref, an array reference with the column names

  Args: None    
        
  Side_Effects: None

  Example: my @colnames = @{$matrix->get_colnames()};
          
=cut

sub get_colnames {
    my $self = shift;
    return $self->{colnames};
}

=head2 set_colnames

  Usage: $matrix->set_colnames(\@colnames);

  Desc: Set the matrix column names array for a R::YapRI::Data::Matrix object

  Ret: None

  Args: $col_names_aref, an array reference with the column names
        
  Side_Effects: Die if undef value is used
                Die if argument is not an array reference.
                Die if number of element in the array isnt equal to coln value.
                (except if colnumber is empty)

  Example: $matrix->set_colnames(['col1', 'col2']);
          
=cut

sub set_colnames {
    my $self = shift;
    my $colnam_aref = shift ||
	croak("ERROR: No colname_aref argument was supplied to set_colnames()");

    if (ref($colnam_aref) ne 'ARRAY') {
	croak("ERROR: $colnam_aref supplied to set_colnames() isnt ARRAYREF.");
    }
    my $namesn = scalar(@{$colnam_aref});
    
    ## Check that is has the same number of elelemnts than coln

    my $coln = $self->get_coln();
    if ($coln =~ m/^\d+$/ && $namesn > 0) {
	unless ($namesn == $coln) {
	    croak("ERROR: Different number of names and cols for set_colnames");
	}
    }
    elsif ($coln =~ m/^\d+$/ && $namesn == 0) {  ## Add by default colnames 
	my $n = 0;                               ## from 0 to coln-1
	while ($n + 1 <= $coln) {
	    push @{$colnam_aref}, $n;
	    $n++;
	}
    }

    $self->{colnames} = $colnam_aref;
}


=head2 get_rownames

  Usage: my $rownames_aref = $matrix->get_rownames();

  Desc: Get the Matrix row names array for a R::YapRI::Data::Matrix object

  Ret: $rownames_aref, an array reference with the row names

  Args: None    
        
  Side_Effects: None

  Example: my @rownames = @{$matrix->get_rownames()};
          
=cut

sub get_rownames {
    my $self = shift;
    return $self->{rownames};
}

=head2 set_rownames

  Usage: $matrix->set_rownames(\@rownames);

  Desc: Set the matrix row names array for a R::YapRI::Data::Matrix object

  Ret: None

  Args: $row_names_aref, an array reference with the row names
        
  Side_Effects: Die if undef value is used
                Die if argument is not an array reference.
                Die if number of element in the array isnt equal to rown value.
                (except if rownumber is empty)

  Example: $matrix->set_rownames(['row1', 'row2']);
          
=cut

sub set_rownames {
    my $self = shift;
    my $rownam_aref = shift ||
	croak("ERROR: No rowname_aref argument was supplied to set_rownames()");

    if (ref($rownam_aref) ne 'ARRAY') {
	croak("ERROR: $rownam_aref supplied to set_rownames() isnt ARRAYREF.");
    }
    my $namesn = scalar(@{$rownam_aref});
    
    ## Check that is has the same number of elelemnts than coln

    my $rown = $self->get_rown();
    if ($rown =~ m/^\d+$/ && $namesn > 0) {
	unless ($namesn == $rown) {
	    croak("ERROR: Different number of names and rows for set_rownames");
	}
    }
    elsif ($rown =~ m/^\d+$/ && $namesn == 0) {  ## Add by default rownames 
	my $n = 0;                               ## from 0 to coln-1
	while ($n + 1 <= $rown) {
	    push @{$rownam_aref}, $n;
	    $n++;
	}
    }

    $self->{rownames} = $rownam_aref;
}


=head2 get_data

  Usage: my $data_aref = $matrix->get_data();

  Desc: Get the matrix data array for a R::YapRI::Data::Matrix object.
        The data is stored as an array ordered by rows.

        Example: Matrix  |1 2 3|  => [1, 2, 3, 4, 5, 6]
                         |4 5 6|
 
  Ret: $data_aref, an array reference with the data as array

  Args: None    
        
  Side_Effects: None

  Example: my @data = @{$matrix->get_data()};
          
=cut

sub get_data {
    my $self = shift;
    return $self->{data};
}

=head2 set_data

  Usage: $matrix->set_data(\@data);

  Desc: Set the matrix data array for a R::YapRI::Data::Matrix object

  Ret: None

  Args: $data, an array reference with the data ordered by rows

         Example: Matrix  |1 2 3|  => [1, 2, 3, 4, 5, 6]
                          |4 5 6|
        
  Side_Effects: Die if undef value is used
                Die if argument is not an array reference.
                Die if number of element in the array isnt equal to  
                coln x rown (for example if coln=3 and rown=2, it should have
                6 elements)

  Example: $matrix->set_data([1, 2, 3, 4]);
          
=cut

sub set_data {
    my $self = shift;
    my $data_aref = shift ||
	croak("ERROR: No data_aref argument was supplied to set_data()");

    if (ref($data_aref) ne 'ARRAY') {
	croak("ERROR: $data_aref supplied to set_data() isnt ARRAYREF.");
    }
    my $data_n = scalar(@{$data_aref});
    
    ## Check that is has the same number of elements than coln x rown

    my $coln = $self->get_coln();
    my $rown = $self->get_rown();
    
    if ($coln =~ m/^\d+$/ && $rown =~ m/^\d+$/ && $data_n > 0) {
	my $elems = $coln * $rown;
	if ($data_n != $elems) {
	    croak("ERROR: data_n = $data_n != ($rown x $coln) to set_data().");
	}
    }
    $self->{data} = $data_aref;

    ## And set the indexes for the matrix

    my %indexes = $self->_index_matrix();
    $self->_set_indexes(\%indexes);
}

########################
## INTERNAL FUNCTIONS ##
########################

=head1 (*) INTERNAL MATRIX FUNCTIONS:


=head2 _get_indexes

  Usage: my $index_href = $matrix->_get_indexes();

  Desc: Get then matrix indexes for columns and rows, with the following 
        format:
          $index_href = { $row,$col => $array_element }
 
  Ret: $index_href, a hash reference with the matrix indexes.

  Args: None    
        
  Side_Effects: None

  Example: my %indexes = @{$matrix->_get_indexes()};
          
=cut

sub _get_indexes {
    my $self = shift;
    return $self->{_indexes};
}

=head2 _get_rev_indexes

  Usage: my $revindex_href = $matrix->_get_rev_indexes();

  Desc: Get then matrix indexes for columns and rows, with the following 
        format:
          $index_href = { $array_element => [ $row, $col ] }
 
  Ret: $rindex_href, a hash reference with the matrix indexes.

  Args: None    
        
  Side_Effects: None

  Example: my %revindexes = %{$matrix->_get_rev_indexes()};
          
=cut

sub _get_rev_indexes {
    my $self = shift;
    
    my %rev_index = ();
    my %indexes = %{$self->_get_indexes()};
    foreach my $i (keys %indexes) {
	my ($r, $c) = split(/,/, $i);
	$rev_index{$indexes{$i}} = [$r, $c];
    }
    return \%rev_index;
}



=head2 _set_indexes

  Usage: $matrix->_set_indexes(\%indexes);

  Desc: Set the matrix indexes for the data contained in the matrix

  Ret: None

  Args: \%indexes, a hash reference with key=$row,$col and value=$arrayposition
        
  Side_Effects: Die if undef value is used
                Die if argument is not an hash reference.
                Die if number of element in the hash isnt equal to  
                coln x rown (for example if coln=3 and rown=2, it should have
                6 elements)

  Example: $matrix->set_data([1, 2, 3, 4]);
          
=cut

sub _set_indexes {
    my $self = shift;
    my $ind_href = shift ||
	croak("ERROR: No index href argument was supplied to _set_indexes()");

    if (ref($ind_href) ne 'HASH') {
	croak("ERROR: $ind_href supplied to _set_indexes() isnt HASHREF.");
    }
    my $n = scalar(keys %{$ind_href});
    
    ## Check that is has the same number of elements than coln x rown

    my $coln = $self->get_coln();
    my $rown = $self->get_rown();
    
    if ($coln =~ m/^\d+$/ && $rown =~ m/^\d+$/ && $n > 0) {
	my $elems = $coln * $rown;
	if ($n != $elems) {
	    croak("ERROR: indexN = $n != ($rown x $coln) to _set_indexes().");
	}
    }

    $self->{_indexes} = $ind_href;
}

=head2 _index_matrix

  Usage: my %index_matrix = $self->_index_matrix();

  Desc: Index the matrix with an array to know segment the matrix in elements
        %index_matrix = ( $arrayposition => { row => $rowposition,
                                              col => $colposition 
                                            });
 
  Ret: The index matrix, a hash with the following elements:
       %index_matrix = ( $arrayposition => { $rowposition, $colposition })

  Args: None  
        
  Side_Effects: None

  Example: my %index_matrix = $self->_index_matrix();
          
=cut

sub _index_matrix {
    my $self = shift;

    ## Define index

    my %index = ();

    ## Get the rown, coln and data

    my $rown = $self->get_rown();
    my $coln = $self->get_coln();
    my @data = @{$self->get_data()};

    ## Assign positions

    my ($a, $r, $c) = (0, 0, 0);
    foreach my $data (@data) {
	$index{$r . ',' . $c} = $a;
	$a++;
	$c++;
	if ($c + 1 > $coln) {
	    $r++;
	    $c = 0;
	}
    }
    
    return %index;
}


=head2 _no_duplicate_names

  Usage: $rmatrix->_no_duplicate_names($method);

  Desc: Check if there are duplicate names for col or rows. Die if exists
        duplicates.
 
  Ret: None

  Args: $method, a scalar that can have 'col', 'row' or 'all'. (all by default)
        
  Side_Effects: Overwrite methods that are not 'col' or 'row' with 'all'. 
                Die if it find duplicates.

  Example: $rmatrix->_no_duplicate_names('col');
          
=cut

sub _no_duplicate_names {
    my $self = shift;
    my $method = shift
	|| 'all';
    
    my %methods = ( 
	col => $self->get_colnames(),
	row => $self->get_rownames(),
	); 
    
    my %dupl = ( col => [], row => []);

    foreach my $me (keys %methods) {
	my %exs = ();
	my @names = @{$methods{$me}};
	foreach my $name (@names) {
	    unless (exists $exs{$name}) {
		$exs{$name} = 1;
	    }
	    else {
		$exs{$name}++;
		if ($exs{$name} < 3) {
		    push @{$dupl{$me}}, $name;
		}
	    }
	}
    }

    ## With the list of duplicates

    my @coldupl = @{$dupl{col}};
    my @rowdupl = @{$dupl{row}};
    my $clist = join(',', @coldupl) || 'none';
    my $rlist = join(',', @rowdupl) || 'none';

    if ($method =~ m/^col$/i) {
	if (scalar(@coldupl) > 0) {
	    croak("ERROR: There are column duplicates ($clist).");
	}
    }
    elsif ($method =~ m/^row$/i) {
	if (scalar(@rowdupl) > 0) {	    
	    croak("ERROR: There are row duplicates ($rlist).");
	}
    }
    else {
	my $dupl = scalar(@coldupl) + scalar(@rowdupl);
	if ($dupl > 0) {
	    croak("ERROR: There are duplicate names (col=$clist, row=$rlist).");
	}
    }
}

#####################
## DATA FUNCTIONS ###
#####################

=head1 (*) DATA MANAGEMENT FUNCTIONS:


=head2 set_coldata

  Usage: $rmatrix->set_coldata($colname, \@col_data);

  Desc: Add data to an existing column, overwriting the old data
 
  Ret: None

  Args: $colname, a scalar with the name of the column
        $coldata_aref, an array ref. with the data of the column    
        
  Side_Effects: Die if no colname is used.
                Die if the number of elements in the data array is different
                than the rown

  Example: $rmatrix->set_coldata('col1', [1, 2]);
           $rmatrix->set_coldata($colnames[0], [1, 2]);
          
=cut

sub set_coldata {
    my $self = shift;
    my $colname = shift ||
	croak("ERROR: No colname was supplied to add_coldata()");
    
    my $col_aref = shift ||
	croak("ERROR: No column data aref. was supplied to add_coldata()");

    unless(ref($col_aref) eq 'ARRAY') {
	croak("ERROR: column data aref = $col_aref isnt a ARRAY REF.")
    }

    ## Check if the colname exists or the colposition, and asign a col. 
    ## position for the colname.
    
    my $colpos;
    
    my @colnames = @{$self->get_colnames()};
    my $n = 0;
    foreach my $col (@colnames) {
	if ($col eq $colname) {
	    $colpos = $n;
	}
	$n++;
    }
    unless (defined $colpos) {
	croak("ERROR: $colname doesnt exist for colnames list.")
    }    
    
    ## Now it will check that the elements number are equal to rown

    my $rown = $self->get_rown();
    if ($rown != scalar(@{$col_aref})) {
	croak("ERROR: data supplied to add_coldata dont have same row number");
    }

    ## With colpos it will replace the elements using the matrix index

    my $data_aref = $self->get_data();
    my %index = %{$self->_get_indexes()};

    foreach my $i (keys %index) {
	my ($r, $c) = split(',', $i);

	if ($c == $colpos) {
	    $data_aref->[$index{$i}] = $col_aref->[$r];
	}
    }
}


=head2 set_rowdata

  Usage: $rmatrix->set_rowdata($rowname, \@row_data);

  Desc: Add data to an existing row, overwriting the old data
 
  Ret: None

  Args: $colname, a scalar with the name of the row,
        $rowdata_aref, an array ref. with the data of the row   
        
  Side_Effects: Die if no rowname is used.
                Die if the number of elements in the data array is different
                than the column

  Example: $rmatrix->set_rowdata('row1', [1, 2]);
           $rmatrix->set_rowdata($rownames[0], [1, 2]);
          
=cut

sub set_rowdata {
    my $self = shift;
    my $rowname = shift ||
	croak("ERROR: No rowname was supplied to add_rowdata()");
    
    my $row_aref = shift ||
	croak("ERROR: No row data aref. was supplied to add_rowdata()");

    unless(ref($row_aref) eq 'ARRAY') {
	croak("ERROR: row data aref = $row_aref isnt a ARRAY REF.")
    }

    ## Check if the colname exists or the colposition, and asign a col. 
    ## position for the colname.
    
    my $rowpos;
    
    my @rownames = @{$self->get_rownames()};
    my $n = 0;
    foreach my $row (@rownames) {
	if ($row eq $rowname) {
	    $rowpos = $n;
	}
	$n++;
    }
    unless (defined $rowpos) {
	croak("ERROR: $rowname doesnt exist for rownames list.")
    }    
    
    ## Now it will check that the elements number are equal to coln

    my $coln = $self->get_coln();
    if ($coln != scalar(@{$row_aref})) {
	croak("ERROR: data supplied to add_rowdata dont have same col number");
    }

    ## With colpos it will replace the elements using the matrix index

    my $data_aref = $self->get_data();
    my %index = %{$self->_get_indexes()};

    foreach my $i (keys %index) {
	my ($r, $c) = split(',', $i);

	if ($r == $rowpos) {
	    $data_aref->[$index{$i}] = $row_aref->[$c];
	}
    }
}

=head2 add_column

  Usage: $rmatrix->add_column($colname, \@col_data);

  Desc: Add a new column to the object, rebuilding the indexes and edinting
        column number
 
  Ret: None

  Args: $colname, a scalar with the name of the column
        $coldata_aref, an array ref. with the data of the column    
        
  Side_Effects: Die if the number of elements in the data array is different
                than the row number
                Rebuild and reset the matrix indexes and the coln.

  Example: $rmatrix->add_column('col1', [1, 2]);
          
=cut

sub add_column {
    my $self = shift;
    my $colname = shift;
    
    ## Check variables

    my $coldata_aref = shift ||
	croak("ERROR: No column data was supplied to add a new column");

    unless (ref($coldata_aref) eq 'ARRAY') {
	croak("ERROR: column data aref = $coldata_aref isnt a ARRAY REF.")
    }
    else {
	my $rown = $self->get_rown();
	if ($rown != scalar(@{$coldata_aref})) {
	    croak("ERROR: element N. for col. data isnt equal to row N.");
	}
    }

    ## Now it will add a new coln

    my $old_coln = $self->get_coln();
    $self->set_coln($old_coln + 1);

    ## If it defined colname, it will add it

    if (defined $colname) {
	push @{$self->get_colnames()}, $colname;
    }
    else {
	push @{$self->get_colnames()}, $old_coln; 
    }

    ## Get the indexes to add new data and flip the hash

    my %rev_index = %{$self->_get_rev_indexes()};
	
    ## Prepare a new array data

    my @newdata = ();

    ## Use the old data, adding a new data from the column aref. after the
    ## element from the array

    my $a = 0;
    my @data = @{$self->get_data()};
    foreach my $el (@data) {
	
	my ($rr, $cc) = @{$rev_index{$a}};
	$a++;
	push @newdata, $el;
	if ($cc + 1 == $old_coln) {
	    push @newdata, $coldata_aref->[$rr];
	}
    }

    ## finally set_data (it will rebuild the indexes)
    $self->set_data(\@newdata);
}


=head2 add_row

  Usage: $rmatrix->add_row($rowname, \@row_data);

  Desc: Add a new row to the object, rebuilding the indexes and edinting
        row number
 
  Ret: None

  Args: $rowname, a scalar with the name of the row
        $rowdata_aref, an array ref. with the data of the row   
        
  Side_Effects: Die if the number of elements in the data array is different
                than the col number
                Rebuild and reset the matrix indexes and the rown.

  Example: $rmatrix->add_row('row3', [1, 2]);
          
=cut

sub add_row {
    my $self = shift;
    my $rowname = shift;
    
    ## Check variables

    my $rowdata_aref = shift ||
	croak("ERROR: No row data was supplied to add a new row");

    unless (ref($rowdata_aref) eq 'ARRAY') {
	croak("ERROR: row data aref = $rowdata_aref isnt a ARRAY REF.")
    }
    else {
	my $coln = $self->get_coln();
	if ($coln != scalar(@{$rowdata_aref})) {
	    croak("ERROR: element N. for row. data isnt equal to col N.");
	}
    }

    ## Now it will add a new rown

    my $old_rown = $self->get_rown();
    $self->set_rown($old_rown + 1);

    ## If it defined rowname, it will add it

    if (defined $rowname) {
	push @{$self->get_rownames()}, $rowname;
    }
    else {
	push @{$self->get_rownames()}, $old_rown; 
    }

    ## Get the indexes to add new data and flip the hash

    my %rev_index = %{$self->_get_rev_indexes()};
	
    ## It is ordered by row, so it will add everything after the last row
    ## that means that it will push the data. To mantain the same behaviour 
    ## than add_column it will create a new array;

    my @data = @{$self->get_data};
    push @data, @{$rowdata_aref};

    ## finally set_data (it will rebuild the indexes)
    $self->set_data(\@data);
}


=head2 delete_column

  Usage: my @coldata = $rmatrix->delete_column($colname);

  Desc: Delete a column to the object, rebuilding the indexes and edinting
        column number
 
  Ret: @coldata, an array with the column data deleted

  Args: $colname, a scalar with the name of the column  
        
  Side_Effects: Die if the column doesnt exist.
                Rebuild indexes and reset coln.

  Example: my @coldata = $rmatrix->delete_column('col1');
          
=cut

sub delete_column {
    my $self = shift;
    my $colname = shift ||
	croak("ERROR: No colname was supplied to delete_column()");

    my @newcolnames = ();
    my @oldcolnames = @{$self->get_colnames()};
    my $del = '';

    my $n = 0;
    foreach my $old (@oldcolnames) {
	unless ($colname eq $old) {
	    push @newcolnames, $old;
	}
	else {
	    $del = $n;  ## Keep the index for the deleted column
	}
	$n++;
    }

    ## Die if they have the same number of elements (doesnt exist colname)

    if (scalar(@newcolnames) == scalar(@oldcolnames)) {
	croak("ERROR: $colname used for delete_column() isnt in colname list");
    }

    ## Change the number of coln

    my $oldcoln = $self->get_coln();
    $self->set_coln($oldcoln - 1);

    ## And set the new names 

    $self->set_colnames(\@newcolnames);

    ## Finally it should remove the data

    my %revindex = %{$self->_get_rev_indexes()};

    my @deleted = ();
    my @newdata = ();
    my @data = @{$self->get_data()};

    my $a = 0;
    foreach my $data (@data) {
	my ($r, $c) = @{$revindex{$a}};
	if ($c != $del) {
	    push @newdata, $data;
	}
	else {
	    push @deleted, $data;
	}
	$a++;
    }
    
    ## And add to the object returning the deleted data

    $self->set_data(\@newdata);   
    return @deleted;
}
    

=head2 delete_row

  Usage: my @rowdata = $rmatrix->delete_row($rowname);

  Desc: Delete a row to the object, rebuilding the indexes and edinting
        row number
 
  Ret: @rowdata, an array with the row data deleted

  Args: $rowname, a scalar with the name of the row  
        
  Side_Effects: Die if the row doesnt exist.
                Rebuild indexes and reset rown.

  Example: my @rowdata = $rmatrix->delete_row('row1');
          
=cut

sub delete_row {
    my $self = shift;
    my $rowname = shift ||
	croak("ERROR: No rowname was supplied to delete_row()");

    my @newrownames = ();
    my @oldrownames = @{$self->get_rownames()};
    my $del = '';

    my $n = 0;
    foreach my $old (@oldrownames) {
	unless ($rowname eq $old) {
	    push @newrownames, $old;
	}
	else {
	    $del = $n;  ## Keep the index for the deleted row
	}
	$n++;
    }

    ## Die if they have the same number of elements (doesnt exist colname)

    if (scalar(@newrownames) == scalar(@oldrownames)) {
	croak("ERROR: $rowname used for delete_row() isnt in rowname list");
    }

    ## Change the number of coln

    my $oldrown = $self->get_rown();
    $self->set_rown($oldrown - 1);

    ## And set the new names 

    $self->set_rownames(\@newrownames);

    ## Finally it should remove the data

    my %revindex = %{$self->_get_rev_indexes()};

    my @deleted = ();
    my @newdata = ();
    my @data = @{$self->get_data()};

    my $a = 0;
    foreach my $data (@data) {
	my ($r, $c) = @{$revindex{$a}};
	if ($r != $del) {
	    push @newdata, $data;
	}
	else {
	    push @deleted, $data;
	}
	$a++;
    }
    
    ## And add to the object returning the deleted data

    $self->set_data(\@newdata);   
    return @deleted;
}

=head2 change_columns

  Usage: $rmatrix->change_columns($colname1, $colname2);

  Desc: Change the order of two columns
 
  Ret: None

  Args: $colname1, column name 1, 
        $colname2, column name 2,  
        
  Side_Effects: Die if the any of the column names used doesnt exist.

  Example: $rmatrix->change_columns('col1', 'col2');
          
=cut

sub change_columns {
    my $self = shift;
    my $colname1 = shift ||
	croak("ERROR: no colname1 arg. was supplied to change_columns()");

    my $colname2 = shift ||
	croak("ERROR: no colname2 arg. was supplied to change_columns()");


    my %col_index = ( $colname1 => '', $colname2 => '' );

    ## Get the index for this two colnames

    my @colnames = @{$self->get_colnames()};
    my ($n, $match) = (0, 0);
    foreach my $col (@colnames) {
	foreach my $selcol (keys %col_index) {
	    if ($col eq $selcol) {
		$col_index{$selcol} = $n;
		$match++;
	    }
	}
	$n++;
    }

    ## Die if match == 2

    if ($match != 2) {
	croak("ERROR: one or two of the colnames supplied isnt column list.");
    }

    ## Now, it has two col, so it will get the data

    my %col_data = ( $colname1 => [], $colname2 => [] );
    my @data = @{$self->get_data()};
    my %revindex = %{$self->_get_rev_indexes()};
    
    my $a = 0;
    foreach my $data (@data) {
	my ($r, $c) = @{$revindex{$a}};
	foreach my $colnm (sort keys %col_index) {
	    if ($col_index{$colnm} == $c) {
		push @{$col_data{$colnm}}, $data;
	    }
	}
	$a++;
    }


    ## Finally it will change the order in the colname array and it will set
    ## the coldata

    my %changed = ( 
	$colname2 => $colname1,
	$colname1 => $colname2,
	);

    foreach my $chname (sort keys %changed) {	
	$self->set_coldata($chname, $col_data{$changed{$chname}});
	
	my $old_idx = $col_index{$chname};
	my $new_idx = $col_index{$changed{$chname}};
	$self->get_colnames()->[$old_idx] = $changed{$chname};
    } 
}


=head2 change_rows

  Usage: $rmatrix->change_rows($rowname1, $rowname2);

  Desc: Change the order of two rows
 
  Ret: None

  Args: $rowname1, row name 1, 
        $rowname2, row name 2,  
        
  Side_Effects: Die if the any of the row names used doesnt exist.

  Example: $rmatrix->change_rows('row1', 'row2');
          
=cut

sub change_rows {
    my $self = shift;
    my $rowname1 = shift ||
	croak("ERROR: no rowname1 arg. was supplied to change_rows()");

    my $rowname2 = shift ||
	croak("ERROR: no rowname2 arg. was supplied to change_rows()");


    my %row_index = ( $rowname1 => '', $rowname2 => '' );

    ## Get the index for this two rownames

    my @rownames = @{$self->get_rownames()};
    my ($n, $match) = (0, 0);
    foreach my $row (@rownames) {
	foreach my $selrow (sort keys %row_index) {
	    if ($row eq $selrow) {
		$row_index{$selrow} = $n;
		$match++;
	    }
	}
	$n++;
    }

    ## Die if match == 2

    if ($match != 2) {
	croak("ERROR: one or two of the rownames supplied isnt row list.");
    }

    ## Now, it has two row, so it will get the data

    my %row_data = ( $rowname1 => [], $rowname2 => [] );
    my @data = @{$self->get_data()};
    my %revindex = %{$self->_get_rev_indexes()};
    
    my $a = 0;
    foreach my $data (@data) {
	my ($r, $c) = @{$revindex{$a}};
	foreach my $rownm (sort keys %row_index) {
	    if ($row_index{$rownm} == $r) {
		push @{$row_data{$rownm}}, $data;
	    }
	}
	$a++;
    }


    ## Finally it will change the order in the rowname array and it will set
    ## the coldata

    my %changed = ( 
	$rowname2 => $rowname1,
	$rowname1 => $rowname2,
	);

    foreach my $chname (sort keys %changed) {	
	$self->set_rowdata($chname, $row_data{$changed{$chname}});
	
	my $old_idx = $row_index{$chname};
	my $new_idx = $row_index{$changed{$chname}};
	$self->get_rownames()->[$old_idx] = $changed{$chname};
    } 
}

=head2 transpose

  Usage: my $newmatrix = $rmatrix->matrix($matrixname);

  Desc: Create a new matrix executing a transpose over the original matrix
 
  Ret: $newmatrix, a R::YapRI::Data::Matrix object

  Args: $matrixname, a new matrix name
        
  Side_Effects: Use tr_ . $rmatrix->get_name as default name.

  Example: my $newmatrix = $rmatrix->matrix();
          
=cut

sub transpose {
    my $self = shift;
    my $name = shift;

    unless (defined $name) {
	$name = 'tr_' . $self->get_name();
    }

    my $coln = $self->get_coln();
    my $rown = $self->get_rown();
    my @colnames = @{$self->get_colnames()};
    my @rownames = @{$self->get_rownames()};

    my @newdata = ();
    foreach my $col (@colnames) {
	push @newdata, $self->get_column($col);
    }

    my $newmatrix = __PACKAGE__->new({ name     => $name,
				       coln     => $rown,
				       rown     => $coln,
				       colnames => \@rownames, 
				       rownames => \@colnames,
				       data     => \@newdata,
				     });

    return $newmatrix
}



#############
## SLICERS ##
#############

=head2 get_column

  Usage: my @column = $rmatrix->get_column($colname);

  Desc: Get the column data for a concrete columnname
 
  Ret: @column, an array with the column data

  Args: $colname, a scalar with the column name
        
  Side_Effects: return an empty array if the column doesnt exist

  Example: my @column2 = $rmatrix->get_column('column2');
          
=cut

sub get_column {
    my $self = shift;
    my $colname = shift || '';

    ## Get the index

    my @colnames = @{$self->get_colnames()};
    my $ci;
    my $a = 0;
    foreach my $col (@colnames) {
	if ($col eq $colname) {
	    $ci = $a;
	}
	$a++;
    }

    ## Catch the data based in the index, and push into an array

    my @column_data = ();

    if (defined $ci) {
	my %revindex = %{$self->_get_rev_indexes()};
	my @data = @{$self->get_data()};
	my $n = 0;
	foreach my $data (@data) {
	    my ($r, $c) = @{$revindex{$n}};
	    if ($c == $ci) {
		push @column_data, $data;
	    }
	    $n++;
	}
    }
    return @column_data;
}

=head2 get_row

  Usage: my @row = $rmatrix->get_row($rowname);

  Desc: Get the row data for a concrete rowname
 
  Ret: @row, an array with the row data

  Args: $rowname, a scalar with the row name
        
  Side_Effects: return an empty array if the row doesnt exist

  Example: my @row3 = $rmatrix->get_row('row3');
          
=cut

sub get_row {
    my $self = shift;
    my $rowname = shift || '';

    ## Get the index

    my @rownames = @{$self->get_rownames()};
    my $ri;
    my $a = 0;
    foreach my $row (@rownames) {
	if ($row eq $rowname) {
	    $ri = $a;
	}
	$a++;
    }

    ## Catch the data based in the index, and push into an array

    my @row_data = ();

    if (defined $ri) {
	my %revindex = %{$self->_get_rev_indexes()};
	my @data = @{$self->get_data()};
	my $n = 0;
	foreach my $data (@data) {
	    my ($r, $c) = @{$revindex{$n}};
	    if ($r == $ri) {
		push @row_data, $data;
	    }
	    $n++;
	}
    }
    return @row_data;
}

=head2 get_element

  Usage: my $element = $rmatrix->get_element($rowname, $colname);

  Desc: Get the matrix element for concrete $rowname, $colname pair
 
  Ret: $element, a scalar with the element value

  Args: $rowname, a scalar with the row name
        $colname, a scalar with the column name
        
  Side_Effects: return undef if the row or/and column doesnt exist

  Example: my $element = $rmatrix->get_element($rowname, $colname);
          
=cut

sub get_element {
    my $self = shift;
    my $rowname = shift || '';
    my $colname = shift || '';

    my $element;
    my %selindex = ( row => '', col => '');
    my %el_index = ( row => $rowname, 
		     col => $colname );
    my %el_names = ( row => $self->get_rownames(), 
		     col => $self->get_colnames());

    ## Catch the indexes
    
    foreach my $idx (keys %el_index) {
	my $i = 0;
	my @names = @{$el_names{$idx}};
	foreach my $name (@names) {
	    if ($name eq $el_index{$idx}) {
		$selindex{$idx} = $i;
	    }
	    $i++;
	}
    }

    ## Get the data index

    my %index = %{$self->_get_indexes()};
    my $data_idx = $index{$selindex{row} . ',' . $selindex{col}};
    
    if (defined $data_idx) {
	my @data = @{$self->get_data()};
	$element = $data[$data_idx];
    }
    return $element;
}


###########################
## PARSERS AND R.WRITERS ##
###########################

=head1 (*) R. FUNCTIONS:


=head2 _matrix_cmd

  Usage: my $cmd = $rmatrix->_matrix_cmd();

  Desc: Build the command to add a new matrix 
 
  Ret: $cmd, a string with the R command

  Args: None
        
  Side_Effects: None

  Example: my $cmd = $rmatrix->_matrix_cmd();
          
=cut 

sub _matrix_cmd {
    my $self = shift;

    my $cmd = $self->get_name . ' <- matrix(';

    ## Add data using r_var function to convert in per R string
    
    $cmd .= r_var($self->get_data()) . ', ';
    
    ## add rown, coln and byrow=TRUE
    
    $cmd .= 'nrow=' . $self->get_rown() . ', ';
    $cmd .= 'ncol=' . $self->get_coln() . ', ';
    $cmd .= 'byrow=TRUE, ';

    ## Finally it will add the row and col names

    $cmd .= 'dimnames=list(';    
    $cmd .= r_var($self->get_rownames()) . ', ';
    $cmd .= r_var($self->get_colnames()) . ')';
    
    ## And close the matrix
    $cmd .= ')';

    return $cmd;
}


=head2 _dataframe_cmd

  Usage: my $cmd = $rmatrix->_dataframe_cmd();

  Desc: Build the command to add a new dataframe to R block
 
  Ret: $cmd, a string with the R command

  Args: None
        
  Side_Effects: If the column names are numeric (f.example: 1), it will
                add an X before (R doesnt accept numeric column names)

  Example: my $cmd = $rmatrix->_dataframe_cmd();
          
=cut 

sub _dataframe_cmd {
    my $self = shift;

    my $cmd;

    ## A data frame is build with R vectors of columns
    ## data.frame('col1'=c(coldata1), ... row.names=c(rownames))
    
    my $mtxname = $self->get_name();
    my @colnames = @{$self->get_colnames()};
    my @rownames = @{$self->get_rownames()};

    my @dataframe_data = ();
    foreach my $col (@colnames) {
	my @coldata = $self->get_column($col);
	if ($col =~ m/^\d+$/) {
	    $col = 'X' . $col;
	}
	push @dataframe_data, $col . '=' . r_var(\@coldata);
    }
    
    ## Now it will create the data frame
    my $dataline = join(', ', @dataframe_data);

    $cmd = $mtxname . ' <- data.frame( ' . $dataline;           ## Add data
    if (scalar(@rownames) > 0) {                               ## Add rownames
	$cmd .= ', row.names=' . r_var($self->get_rownames);
    } 
    $cmd .= ' )';                                              ## Close
    
    return $cmd;
}

=head2 _rowvectors_cmd

  Usage: my $cmds_aref = $rmatrix->_rowvectors_cmd();

  Desc: Build the command to add a list of vectors, parsing the matrix
        by rows 
 
  Ret: $cmds_aref, an array ref. with strings with the R command

  Args: None
        
  Side_Effects: Create as many vectors as rows has the matrix, with the
                row names as vector names. If the row names are numeric, 
                it will add an 'X' before the number (for example, 67 will
                be X67).

  Example: my @cmds = @{$rmatrix->_rowvectors_cmd()};
          
=cut 

sub _rowvectors_cmd {
    my $self = shift;

    my @cmds = ();

    my @rownames = @{$self->get_rownames()};
    foreach my $name (@rownames) {
	my @rowdata = $self->get_row($name);
	if ($name =~ m/^\d+$/) {
	    $name = 'X' . $name;
	}
	push @cmds, $name . ' <- ' .r_var(\@rowdata);
    }

    return \@cmds;
}


=head2 _colvectors_cmd

  Usage: my $cmds_aref = $rmatrix->_colvectors_cmd();

  Desc: Build the command to add a list of vectors, parsing the matrix
        by columns 
 
  Ret: $cmds_aref, an array ref. strings with the R command

  Args: None
        
  Side_Effects: Create as many vectors as cols has the matrix, with the
                col names as vector names. If the col names are numeric, 
                it will add an 'X' before the number (for example, 67 will
                be X67).

  Example: my @cmds = @{$rmatrix->_colvectors_cmd()};
          
=cut 

sub _colvectors_cmd {
    my $self = shift;

    my @cmds = ();

    my @colnames = @{$self->get_colnames()};
    foreach my $name (@colnames) {
	my @coldata = $self->get_column($name);
	if ($name =~ m/^\d+$/) {
	    $name = 'X' . $name;
	}
	push @cmds, $name . ' <- ' .r_var(\@coldata);
    }

    return \@cmds;
}


=head2 send_rbase

  Usage: $rmatrix->send_rbase($rbase, $block, $mode);

  Desc: Load the matrix data as a block in a rbase object (R::YapRI::Base)
        The R matrix name will the same than the perl matrix name.
        If block argument is used, it will add the command to this block.
        If block argument is undef or empty, it will create a new block with
        the argument name
 
  Ret: None

  Args: $rbase, a R::YapRI::Base object, 
        $block, block to create or add the data, an undef or empty value
                will create a new block with the matrix name.
        $mode, a scalar with the following possible values matrix, dataframe
               vectors_by_row and vectors_by_col (optional, matrix by default)
             
 
  Side_Effects: Die if no rbase object is used or if the argument used
                is not a R::YapRI::Base object

  Example: ## Default mode (R matrix in a new block with name=matrix_name)
              $rmatrix->send_rbase($rbase);

           ## Adding a matrix to BLOCK1
              $rmatrix->send_rbase($rbase, 'BLOCK1');

           ## Adding a data.frame to GRAPH1
              $rmatrix->send_rbase($rbase, 'GRAPH1', 'dataframe');

           ## Adding as vectors by row to a matrix name block 
              $rmatrix->send_rbase($rbase, '', 'vectors_by_row');
=cut

sub send_rbase {
    my $self = shift;
    my $rbase = shift ||
	croak("ERROR: No rbase argument was used for send_rbase function.");
    my $block = shift ||
	'';                              ## Default will be empty
    my $mode = shift ||
	'matrix';                        ## Matrix mode by default

    my %permmodes = (
	matrix         => '_matrix_cmd',
	dataframe      => '_dataframe_cmd',
	vectors_by_row => '_rowvectors_cmd', 
	vectosr_by_col => '_colvectors_cmd',
	);

    unless (exists $permmodes{$mode}) {
	my $l = join(',', keys %permmodes);
	croak("ERROR: $mode is an unknown mode ($l). Aborting send_rbase().");
    }

    ## Check that it is not using duplicate names

    $self->_no_duplicate_names();

    if (ref($rbase) ne 'R::YapRI::Base') {
	croak("ERROR: $rbase supplied to send_rbase() isnt R::YapRI::Base obj.");
    }

    ## Check if Block exist for rbase

    if ($block =~ m/./) {
	unless (defined $rbase->get_blocks($block)) {
	    croak("ERROR: $block isnt defined for $rbase. Aborting send_rbase");
	}
    }

    ## First get the data, and check if there are data

    my @data = @{$self->get_data()};

    unless (scalar(@data) > 0) {
	croak("ERROR: object $self doesnt have set any data to use rbase().")
    }

    ## Now it will build the R command to load a matrix

    my $mtx_name = $self->get_name;
    if ($mtx_name !~ m/\w+/) {
	croak("ERROR: Matrix=$self doesnt have any name to pass to R block");
    }
    
    ## It will create the block with the matrix name

    if ($block !~ m/./) {
	$rbase->create_block($self->get_name());
	$block = $self->get_name();
    }

    ## depending of the mode it will use different functions

    my $cmdfunc = $permmodes{$mode};
    my $cmd = $self->$cmdfunc();
    if (ref($cmd)) {                       ## For vectors_by_row and col.
	foreach my $subcmd (@{$cmd}) {
	    $rbase->add_command($subcmd, $block);
	}
    }
    else {                                 ## For matrix and dataframe.
	 $rbase->add_command($cmd, $block);
    }
}

=head2 read_rbase

  Usage: my $rmatrix = R::YapRI::Data::Matrix->read_rbase( $rbase, 
                                                        $block, 
                                                        $r_object_name);

  Desc: Create a new block with some R functions to get the data from the
        matrix. Combine it with the base block, run it and get the resultsfile.
        Parse the resultfile and get the data from it, loading it in a new
        matrix object. 
 
  Ret: A new matrix object (R::YapRI::Data::Matrix)

  Args: $rbase, a R::YapRI::Base object, 
        $block, a scalar with the name of the block that contains the R object
                to be read
        $r_object_name, name of the r_object to read
        
  Side_Effects: Die if no rbase object, block name or r_object_name is used.
                Die if rbase objects isnt a R::YapRI::Base object
                Die if block doesnt exist in the R::YapRI::Base object
                Die if the r_object_name doesnt exist in the R block used
                Die if the r_object_name isnt a matrix object.

  Example:  my $rmatrix = R::YapRI::Data::Matrix->read_rbase( $rbase, 
                                                           'BLOCK1', 
                                                           'mymatrix');
          
=cut

sub read_rbase {
    my $class = shift;
    my $rbase = shift ||
	croak("ERROR: No rbase object was supplied to read_rbase() function");
    my $block = shift ||
	croak("ERROR: No block name was supplied to read_rbase() function");
    my $r_obj = shift ||
	croak("ERROR: No r_object was supplied to read_rbase() function");

    ## Check object identity

    if (ref($rbase) ne 'R::YapRI::Base') {
    croak("ERROR: $rbase object supplied to read_rbase() isnt R::YapRI::Base");
    }

    ## Check that exists the block used

    unless (defined $rbase->get_blocks($block)) {
	croak("ERROR: Block=$block doesnt exist for $rbase object.");
    }

    ## First, check if exist the r_object

    my $rclass = $rbase->r_object_class($block, $r_obj);
    unless (defined $rclass) {
	croak("ERROR: $r_obj isnt defined for R block=$block.");
    }
    else {
	unless ($rclass =~ m/matrix/) {
	    croak("ERROR: $r_obj defined for block=$block isnt a r matrix obj");
	}
    }

    ## Second, build the command to catch the matrix object

    my %cmds = (
	1  => 'print("init_catch_matrix_' . $r_obj . '")',
	2  => 'print("init_rowcount")',
	3  => 'length(' . $r_obj . '[,1])',
	4  => 'print("end_rowcount")',
	5  => 'print("init_colcount")',
	6  => 'length(' . $r_obj . '[1,])',
	7  => 'print("end_colcount")',
	8  => 'print("init_rownames")',
	9  => 'rownames(' . $r_obj . ')',
	10 => 'print("end_rownames")',
	11 => 'print("init_colnames")',
	12 => 'colnames(' . $r_obj . ')',
	13 => 'print("end_colnames")',
	14 => 'print("init_matrix")',
	15 => 'c(t(' . $r_obj . '))',   ## R reads the matrix per col, so
	16 => 'print("end_matrix")',    ## it needs to transpose before read
	17 => 'print("end_catch_matrix_' . $r_obj . '")',
	);

    my $mblock = 'CATCH_MATRIX_' . $r_obj;
    $rbase->create_block($mblock, $block);
    foreach my $icmd (sort {$a <=> $b} keys %cmds) {
	$rbase->add_command($cmds{$icmd}, $mblock);
    }

    ## Run and get the result file

    $rbase->run_commands($mblock);
    my $rfile = $rbase->get_blocks($mblock)->get_result_file();

    ## Parse the result file and transfer the data to the perl vars.

    my $init = 0;

    my %parinit = (
	rownames => 0,
	colnames => 0,
	rowcount => 0,
	colcount => 0,
	matrix   => 0,
	);

    my %pardata = (
	rownames => [],
	colnames => [],
	rowcount => [],
	colcount => [],
	matrix   => [],
	);

    open my $rfh, '<', $rfile;
    while(<$rfh>) {
	chomp($_);
	if ($_ =~ m/"end_catch_matrix_$r_obj"/) {
	    $init = 0;
	}

	if ($init == 1) {
	   
	    ## Just remove [\d] at the beginning of each line and remove
	    ## extra spaces
	    $_ =~ s/^\s*\[\d+\]\s+//;
	    $_ =~ s/\s+/ /g;
	    
	    ## Check all the parser initiations for each class
	    foreach my $class_init (keys %parinit) {
		
		if ($_ =~ m/"end_$class_init"/) {
		    $parinit{$class_init} = 0;
		}
		if ($parinit{$class_init} == 1) {
		    
		    ## Also it will add an extra " for each " for words
		    $_ =~ s/"/""/g;
		    $_ =~ s/^\s*""/"/g;
		    $_ =~ s/""\s*$/"/g;

		    if ($_ !~ m/NULL/) {
			
			## Check if they are digits or words
			my @data = split(/"\s"/, $_);
			foreach my $data (@data) {

			    if ($data =~ m/^"(.+)"$/) {
				push @{$pardata{$class_init}}, $1;
			    }
			    else {
				my @subdata = split(/\s/, $data);
				push @{$pardata{$class_init}}, @subdata;
			    }
			} 
		    }
		}
		if ($_ =~ m/"init_$class_init"/) {
		    $parinit{$class_init} = 1;
		}
	    }
	}

	if ($_ =~ m/"init_catch_matrix_$r_obj"/) {
	    $init = 1;
	}
    }

    ## everything should be parsed, now it will create a new 
    ## R::YapRI::Data::Matrix object 
    
    my $matrix = $class->new({ 
	name     => $r_obj,
	rown     => $pardata{rowcount}->[0],
	coln     => $pardata{colcount}->[0],
	rownames => $pardata{rownames},
	colnames => $pardata{colnames},
	data     => $pardata{matrix},
			     });

    return $matrix;
}


=head1 ACKNOWLEDGEMENTS

Lukas Mueller

Robert Buels

Naama Menda

Jonathan "Duke" Leto

=head1 COPYRIGHT AND LICENCE

Copyright 2011 Boyce Thompson Institute for Plant Research

Copyright 2011 Sol Genomics Network (solgenomics.net)

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

=cut


####
1; #
####