The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# * Statistics::RserveClient message Parser
# * @author Djun Kim
# * Based on Clément Turbelin's PHP client
# * Licensed under GPL v2 or at your option v3

package Statistics::RserveClient::Parser;

our $VERSION = '0.12'; #VERSION

#use strict;
#use warnings;
#use diagnostics;

use Exporter;
our @ISA    = qw(Exporter);
our @EXPORT = qw(parse);

use Data::Dumper;

#use Statistics::RserveClient;
#use Statistics::RserveClient::ParserException;

use Statistics::RserveClient::Funclib;
use Statistics::RserveClient qw( TRUE FALSE :xt_types );

use Statistics::RserveClient::REXP;

#use Statistics::RserveClient::REXP::Null;
#use Statistics::RserveClient::REXP::GenericVector;

#use Statistics::RserveClient::REXP::Symbol;
#use Statistics::RserveClient::REXP::List;
#use Statistics::RserveClient::REXP::Language;
#use Statistics::RserveClient::REXP::Integer;
#use Statistics::RserveClient::REXP::Double;
#use Statistics::RserveClient::REXP::String;
#use Statistics::RserveClient::REXP::Raw;
#use Statistics::RserveClient::REXP::Logical;

# * Global parameters to parse() function
# * If true, use Statistics::RserveClient_RNative wrapper instead of native array to
#   handle attributes

#public static $use_array_object = FALSE;
my $_use_array_object = FALSE;

#forward definition to avoid warnings pragma complaints
sub use_array_object();

sub use_array_object() {
    my $value = shift;
    if ( defined($value) ) {
        $_use_array_object = $value;
    }
    return $_use_array_object;
}

# * Transform factor to native strings, only for parse() method
# * If false, factors are parsed as integers
#public static $factor_as_string = TRUE;
my $_factor_as_string = TRUE;

sub factor_as_string() {
    my $value = shift;
    if ( defined($value) ) {
        $_factor_as_string = $value;
    }
    return $_factor_as_string;
}

# * parse SEXP results -- limited implementation for now (large
#   packets and some data types are not supported)
# * @param string $buf
# * @param int $offset
# * @param unknown_type $attr
#public static function parse($buf, $offset, $attr = NULL) {

sub parse {
    Statistics::RserveClient::debug "parse()\n";
    my $n = @_;
    Statistics::RserveClient::debug "num args = $n\n";
    Statistics::RserveClient::debug Dumper(@_);

    my $buf    = $_[0];
    my $offset = 0;
    my %attr   = ();

    if ( $n == 3 ) {
        $offset = $_[1];
        %attr   = $_[2];
    }
    elsif ( $n == 2 ) {
        $offset = $_[1];
    }
    elsif ( @_ == 1 ) {
        die "Statistics::RserveClient::Parser::parse(): too few arguments.\n";
    }

    Statistics::RserveClient::debug "buf = $buf\n";
    Statistics::RserveClient::debug "offset = $offset\n";

    my @a = ();

    my @names = ();
    my @na    = ();
    my @r     = split '', $buf;

    # foreach (@r) {print "[" . ord($_). ":". $_ . "]"};  print "\n";
    Statistics::RserveClient::debug Statistics::RserveClient::buf2str(\@r);

    my $i = $offset;
    my $eoa;

    Statistics::RserveClient::debug "i = $i\n";

    # some simple parsing - just skip attributes and assume short responses
    my $ra = Statistics::RserveClient::Funclib::int8( \@r, $i );
    my $rl = Statistics::RserveClient::Funclib::int24( \@r, $i + 1 );

    Statistics::RserveClient::debug "ra = $ra\n";
    Statistics::RserveClient::debug "rl = $rl\n";

    my $al;

    $i += 4;

    $offset = $eoa = $i + $rl;
# Statistics::RserveClient::debug '[ '.Statistics::RserveClient::Parser::xtName($ra & 63).', length '.$rl.' ['.$i.' - '.$eoa."]\n";
    if ( ( $ra & 64 ) == 64 ) {
        die('Fatal error: long packets are not supported (yet).');
    }
    if ( $ra > Statistics::RserveClient::XT_HAS_ATTR ) {
        # Statistics::RserveClient::debug '(ATTR*[';
        $ra &= ~Statistics::RserveClient::XT_HAS_ATTR;
        $al = Statistics::RserveClient::Funclib::int24( \@r, $i + 1 );
        %attr = parse( $buf, $i );
        # Statistics::RserveClient::debug '])';
        $i += $al + 4;
    }

    for ($ra) {
        if ( $ra == Statistics::RserveClient::XT_NULL ) {
            Statistics::RserveClient::debug "Null\n";
            @a = undef;
            # break;
        }
        elsif ( $ra == Statistics::RserveClient::XT_VECTOR ) {    # generic vector
            Statistics::RserveClient::debug "Vector\n";
            @a = ();
            while ( $i < $eoa ) {
                Statistics::RserveClient::debug "******* i = $i\n";
                #$a[] = parse($buf, &$i);
                Statistics::RserveClient::debug("recursive call to parse($buf, $i)\n");
                my $sub_ra = Statistics::RserveClient::Funclib::int8( \@r, $i );
                my @parse_result = parse( $buf, $i );
                #print "*{" . Dumper(@parse_result) . "}*\n";

                ## lists and arrays are added as references
                if ($sub_ra == Statistics::RserveClient::XT_VECTOR ||
                    scalar(@parse_result) > 1) {
                    push( @a, \@parse_result );
                } else {
                    ## otherwise it's an R "scalar" (one-element array)
                    push( @a, @parse_result );
                }
                #print Dumper(@a) . "\n";
            }
            Statistics::RserveClient::debug Dumper(@a);
         # if the 'names' attribute is set, convert the plain array into a map
            if ( defined( $attr{'names'} ) ) {
                @names = $attr{'names'};
                @na    = ();
                my $n = length($a);
                for ( my $k = 0; $k < $n; $k++ ) {
                    $na[ $names[$k] ] = $a[$k];
                }
                @a = @na;
            }
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_INT ) {
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_INT\n";
            @a = Statistics::RserveClient::Funclib::int32( \@r, $i );
            $i += 4;
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_DOUBLE ) {
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_DOUBLE\n";
            @a = Statistics::RserveClient::Funclib::flt64( \@r, $i );
            $i += 8;
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_BOOL ) {
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_BOOL\n";
            my $v = Statistics::RserveClient::Funclib::int8( \@r, $i++ );
            @a
                = ( $v == 1 )
                ? TRUE
                : ( ( $v == 0 ) ? FALSE : undef );
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_SYMNAME ) {    # symbol
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_SYMNAME\n";
            my $oi = $i;
            while ( $i < $eoa && ord( $r[$i] ) != 0 ) {
                $i++;
            }
            @a = split '', substr( $buf, $oi, $i - $oi );
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_LANG_NOTAG or $ra == Statistics::RserveClient::XT_LIST_NOTAG )
        {                                        # pairlist w/o tags
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_LANG_NOTAG or Statistics::RserveClient::XT_LIST_NOTAG\n";
            @a = ();
            while ( $i < $eoa ) {
                # $a[] = self::parse($buf, &$i);
                push( @a, parse( $buf, $i, %attr ) );
            }
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_LIST_TAG or $ra == Statistics::RserveClient::XT_LANG_TAG )
        {                                        # pairlist with tags
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_LIST_TAG or Statistics::RserveClient::XT_LANG_TAG\n";
            @a = ();

            Statistics::RserveClient::debug "eoa = $eoa\n";

            while ( $i < $eoa ) {
                Statistics::RserveClient::debug "before parse: i = $i\n";
                my $val = parse( $buf, $i );
                Statistics::RserveClient::debug "after first parse: i = $i\n";
                my $tag = parse( $buf, $i );
                Statistics::RserveClient::debug "after second parse: i = $i\n";
                $a[$tag] = $val;
            }
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_ARRAY_INT ) {    # integer array
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_ARRAY_INT\n";
            @a = ();
            while ( $i < $eoa ) {
                # $a[] = int32(@r, $i);
                push( @a, Statistics::RserveClient::Funclib::int32( \@r, $i ) );
                $i += 4;
            }
            if ( scalar(@a) == 1 ) {
                @a = $a[0];
            }
            # If factor, then transform to characters
            #if (self::$factor_as_string and isset($attr['class'])) {
            if ( factor_as_string() and defined( $attr{'class'} ) ) {
                my $c = $attr{'class'};
                if ( $c eq 'factor' ) {
                    my $n      = scalar(@a);
                    my @levels = $attr{'levels'};
                    for ( my $k = 0; $k < $n; ++$k ) {
                        $i = $a[$k];
                        if ( $i < 0 ) {
                            $a[$k] = undef;
                        }
                        else {
                            $a[$k] = $levels[ $i - 1 ];
                        }
                    }
                }
            }
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_ARRAY_DOUBLE ) {    # double array
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_ARRAY_DOUBLE\n";
            @a = ();
            while ( $i < $eoa ) {
                #$a[] = flt64(@r, $i);
                push( @a, Statistics::RserveClient::Funclib::flt64( \@r, $i ) );
                $i += 8;
            }
            if ( scalar(@a) == 1 ) {
                @a = $a[0];
            }
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_ARRAY_STR ) {    # string array
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_ARRAY_STR\n";
            @a = ();
            my $oi = $i;

            while ( $i < $eoa ) {
                if ( ord( $r[$i] ) == 0 ) {
                    #$a[] = substr($r, $oi, $i - $oi);
                    push( @a, join( '', @r[ $oi .. $i - 1 ] ) );
                    $oi = $i + 1;
                }
                $i++;
            }
            if ( scalar(@a) == 1 ) {
                @a = $a[0];
            }
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_ARRAY_BOOL ) {    # boolean vector
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_ARRAY_BOOL\n";
            my $n = Statistics::RserveClient::Funclib::int32( \@r, $i );
            $i += 4;
            my $k = 0;
            @a = ();
            while ( $k < $n ) {
                my $v = Statistics::RserveClient::Funclib::int8( \@r, $i++ );
                $a[ $k++ ]
                    = ( $v == 1 )
                    ? TRUE
                    : ( ( $v == 0 ) ? FALSE : undef );
            }
            if ( $n == 1 ) {
                @a = $a[0];
            }
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_RAW ) {    # raw vector
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_RAW\n";
            my $len = Statistics::RserveClient::Funclib::int32( \@r, $i );
            $i += 4;
            @a = splice( @r, $i, $len );
            # break;
        }

        #  elsif ($ra == Statistics::RserveClient::XT_ARRAY_CPLX) {
        #   break;
        # }

        elsif ( $ra == 48 ) {    # unimplemented type in Statistics::RserveClient
            my $uit = Statistics::RserveClient::Funclib::int32( \@r, $i );
            warn "Note: result contains type #$uit unsupported by Statistics::RserveClient.<br/>";
            @a = undef;
            # break;
        }

        else {
            warn(     'Warning: type '
                    . $ra
                    . ' is currently not implemented in the Perl client.' );
            @a = undef;
        }
    }    # end switch

    Statistics::RserveClient::debug "after parse: offset = $offset\n";
    Statistics::RserveClient::debug "after parse: \$_[1] = " . $_[1] . "\n";
    $_[1] = $offset;

    #if (self::$use_array_object) {
    if ( use_array_object() ) {
        # if ( is_array(@a) & @attr ) {
        if ( ( ref(@a) == 'ARRAY' ) & %attr ) {
            return new Statistics::RserveClient::RNative( @a, %attr );
        }
    }
    return @a;
}

# * parse SEXP to Debug array(type, length,offset, contents, n)
# * @param string $buf
# * @param int $offset
# * @param unknown_type $attr

sub parseDebug(@);

sub parseDebug(@) {
    Statistics::RserveClient::debug "parseDebug()\n";

    my $buf;
    my $offset;
    my @attr = undef;

    if ( @_ == 3 ) {
        $buf    = shift;
        $offset = shift;
        @attr   = shift;
    }
    elsif ( @_ == 2 ) {
        ( $buf, $offset ) = shift;
    }
    elsif ( @_ == 1 ) {
        die "Statistics::RserveClient::Parser::parse(): too few arguments.\n";
    }

    Statistics::RserveClient::debug "buf = $buf\n";
    Statistics::RserveClient::debug "offset = $offset\n";

    my @r = split '', $buf;

    my $i = $offset;

    my @a = ();

    # some simple parsing - just skip attributes and assume short responses
    my $ra = Statistics::RserveClient::Funclib::int8( \@r, $i );
    my $rl = Statistics::RserveClient::Funclib::int24( \@r, $i + 1 );

    Statistics::RserveClient::debug "ra = $ra\n";
    Statistics::RserveClient::debug "rl = $ra\n";

    $i += 4;

    my $eoa;
    $offset = $eoa = $i + $rl;

    my %result = ();

    $result{'type'}   = Statistics::RserveClient::Parser::xtName( $ra & 63 );
    $result{'length'} = $rl;
    $result{'offset'} = $i;
    $result{'eoa'}    = $eoa;
    if ( ( $ra & 64 ) == 64 ) {
        $result{'long'} = TRUE;
        return %result;
    }
    if ( $ra > Statistics::RserveClient::XT_HAS_ATTR ) {

        $ra &= ~Statistics::RserveClient::XT_HAS_ATTR;
        my $al = Statistics::RserveClient::Funclib::int24( \@r, $i + 1 );
        @attr = parseDebug( $buf, $i );
        $result{'attr'} = @attr;
        $i += $al + 4;
    }
    if ( $ra == Statistics::RserveClient::XT_NULL ) {
        return %result;
    }
    if ( $ra == Statistics::RserveClient::XT_VECTOR ) {    # generic vector
        @a = ();
        while ( $i < $eoa ) {
            #$a[] = self::parseDebug($buf, &$i);
            push( @a, parseDebug( $buf, $i ) );
        }
        $result{'contents'} = $a;
    }
    if ( $ra == Statistics::RserveClient::XT_SYMNAME ) {    # symbol
        my $oi = $i;
        while ( $i < $eoa && ord( $r[$i] ) != 0 ) {
            $i++;
        }
        $result{'contents'} = substr( $buf, $oi, $i - $oi );
    }
    if ( $ra == Statistics::RserveClient::XT_LIST_NOTAG || $ra == Statistics::RserveClient::XT_LANG_NOTAG )
    {                                     # pairlist w/o tags
        @a = ();
        while ( $i < $eoa ) {
            #$a[] = self::parseDebug($buf, &$i);
            push( @a, parseDebug( $buf, $i ) );
        }
        $result{'contents'} = $a;
    }
    if ( $ra == Statistics::RserveClient::XT_LIST_TAG || $ra == Statistics::RserveClient::XT_LANG_TAG )
    {                                     # pairlist with tags
        @a = ();
        while ( $i < $eoa ) {
            my $val = parseDebug( $buf, $i );
            my $tag = parse( $buf, $i );
            $a[$tag] = $val;
        }
        $result{'contents'} = $a;
    }
    if ( $ra == Statistics::RserveClient::XT_ARRAY_INT ) {    # integer array
        @a = ();
        while ( $i < $eoa ) {
            #$a[] = int32(@r, $i);
            push( @a, Statistics::RserveClient::Funclib::int32( \@r, $i ) );
            $i += 4;
        }
        if ( length($a) == 1 ) {
            $result{'contents'} = $a[0];
        }
        $result{'contents'} = $a;
    }
    if ( $ra == Statistics::RserveClient::XT_ARRAY_DOUBLE ) {    # double array
        @a = ();
        while ( $i < $eoa ) {
            push( @a, Statistics::RserveClient::Funclib::flt64( \@r, $i ) );
            $i += 8;
        }
        if ( length($a) == 1 ) {
            $result{'contents'} = $a[0];
        }
        $result{'contents'} = $a;
    }
    if ( $ra == Statistics::RserveClient::XT_ARRAY_STR ) {       # string array
        @a = ();
        my $oi = $i;
        while ( $i < $eoa ) {
            if ( ord( $r[$i] ) == 0 ) {
                # $a[] = substr($r, $oi, $i - $oi);
                push( @a, splice( @r, $oi, $i - $oi ) );
                $oi = $i + 1;
            }
            $i++;
        }
        if ( length($a) == 1 ) {
            $result{'contents'} = $a[0];
        }
        $result{'contents'} = $a;
    }
    if ( $ra == Statistics::RserveClient::XT_ARRAY_BOOL ) {    # boolean vector
        my $n = Statistics::RserveClient::Funclib::int32( \@r, $i );
        $result{'size'} = $n;
        $i += 4;
        my $k = 0;
        @a = ();
        while ( $k < $n ) {
            my $v = Statistics::RserveClient::Funclib::int8( \@r, $i++ );
  # $a[$k] = ($v === 1) ? TRUE : (($v === 0) ? FALSE : undef);
            $a[$k]
                = ( ( $v == 1 ) && is_number($v) )
                ? TRUE
                : (
                ( ( $v == 0 ) && is_number($v) ) ? FALSE : undef );
            ++$k;
        }
        if ( length($a) == 1 ) {
            $result{'contents'} = $a[0];
        }
        $result{'contents'} = $a;
    }
    if ( $ra == Statistics::RserveClient::XT_RAW ) {    # raw vector
        my $len = Statistics::RserveClient::Funclib::int32( \@r, $i );
        $i += 4;
        $result{'size'} = $len;
        my $contents = join( '', substr( @r, $i, $len ) );
        $result{'contents'} = $contents;
    }
    if ( $ra == Statistics::RserveClient::XT_ARRAY_CPLX ) {
        $result{'not_implemented'} = TRUE;
        # TODO: complex
    }
    if ( $ra == 48 ) {                # unimplemented type in Statistics::RserveClient
        my $uit = Statistics::RserveClient::Funclib::int32( \@r, $i );
        $result{'unknownType'} = $uit;
    }
    return %result;
}

#public static function parseREXP($buf, $offset, $attr = NULL) {
sub parseREXP(@);

sub parseREXP(@) {

    Statistics::RserveClient::debug "parseREXP()\n";

    my $buf;
    my $offset;
    my @attr = undef;

    if ( @_ == 3 ) {
        $buf    = shift;
        $offset = shift;
        @attr   = shift;
    }
    elsif ( @_ == 2 ) {
        ( $buf, $offset ) = shift;
    }
    elsif ( @_ == 1 ) {
        die "Statistics::RserveClient::Parser::parse(): too few arguments.\n";
    }

    #Statistics::RserveClient::debug "buf = $buf\n";
    #Statistics::RserveClient::debug "offset = $offset\n";

    my @r = split '', $buf;
    my $i = $offset;

    my @v = ();

    # some simple parsing - just skip attributes and assume short responses
    my $ra = Statistics::RserveClient::Funclib::int8( \@r, $i );
    my $rl = Statistics::RserveClient::Funclib::int24( \@r, $i + 1 );

    Statistics::RserveClient::debug "ra = $ra\n";
    Statistics::RserveClient::debug "rl = $ra\n";

    # Statistics::RserveClient::debug Dumper($rl);

    #my $eoa = int24(0);
    my $eoa = 0;

    my $al, $i += 4;

    $offset = $eoa = $i + $rl;
    if ( ( $ra & 64 ) == 64 ) {
        die('Fatal error: long packets are not supported (yet).');
    }

    if ( $ra > Statistics::RserveClient::XT_HAS_ATTR ) {
        $ra &= ~Statistics::RserveClient::XT_HAS_ATTR;
        $al = Statistics::RserveClient::Funclib::int24( \@r, $i + 1 );
        @attr = parseREXP( $buf, $i );
        $i += $al + 4;
    }
    for ($ra) {
        if ( $ra == Statistics::RserveClient::XT_NULL ) {
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_NULL\n";
            $a = new Statistics::RserveClient::REXP::Null();
            # break;
        }
        elsif ( $ra == Statistics::RserveClient::XT_VECTOR ) {    # generic vector
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_VECTOR\n";
            @v = ();
            while ( $i < $eoa ) {
                # $v[] = self::parseREXP($buf, &$i);
                push( @v, parseREXP( $buf, $i ) );
            }
            $a = new Statistics::RserveClient::REXP::GenericVector();
            $a->setValues(@v);
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_SYMNAME ) {    # symbol
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_SYMNAME\n";
            my $oi = $i;
            while ( $i < $eoa && ord( $r[$i] ) != 0 ) {
                $i++;
            }
            my $v = substr( $buf, $oi, $i - $oi );
            my $a = new Statistics::RserveClient::REXP::Symbol();
            $a->setValue($v);
            # break;
        }
        elsif ( $ra == Statistics::RserveClient::XT_LIST_NOTAG or $ra == Statistics::RserveClient::XT_LANG_NOTAG )
        {                                        # pairlist w/o tags
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_LIST_NOTAG or Statistics::RserveClient::XT_LANG_NOTAG\n";
            @v = ();
            while ( $i < $eoa ) {
                #$v[] = self::parseREXP($buf, &$i);
                push( @v, parseREXP( $buf, $i ) );
            }
            my $clasz
                = ( $ra == Statistics::RserveClient::XT_LIST_NOTAG )
                ? 'Statistics::RserveClient::REXP::List'
                : 'Statistics::RserveClient::REXP::Language';
            $a = new $$clasz();
            $a->setValues($a);
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_LIST_TAG or $ra == Statistics::RserveClient::XT_LANG_TAG )
        {    # pairlist with tags
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_LIST_TAG or Statistics::RserveClient::XT_LANG_TAG\n";
            my $clasz
                = ( $ra == Statistics::RserveClient::XT_LIST_TAG )
                ? 'Statistics::RserveClient::REXP::List'
                : 'Statistics::RserveClient::REXP::Language';
            my @v     = ();
            my @names = ();
            while ( $i < $eoa ) {
                #$v[] = self::parseREXP($buf, &$i);
                push( @v, parseREXP( $buf, $i ) );
                # $names[] = self::parseREXP($buf, &$i);
                push( @names, parseREXP( $buf, $i ) );
            }
            $a = new $$clasz();
            $a->setValues(@v);
            $a->setNames(@names);
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_ARRAY_INT ) {    # integer array
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_ARRAY_INT\n";
            my @v = ();
            while ( my $i < $eoa ) {
                #$v[] = int32(@r, $i);
                push( @v, Statistics::RserveClient::Funclib::int32( \@r, $i ) );
                $i += 4;
            }
            $a = new Statistics::RserveClient::REXP::Integer();
            $a->setValues(@v);
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_ARRAY_DOUBLE ) {    # double array
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_ARRAY_DOUBLE\n";
            @v = ();
            while ( my $i < $eoa ) {
                # $v[] = flt64($r, $i);
                push( @v, Statistics::RserveClient::Funclib::flt64( \@r, $i ) );
                $i += 8;
            }
            $a = new Statistics::RserveClient::REXP::Double();
            $a->setValues(@v);
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_ARRAY_STR ) {    # string array
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_ARRAY_STR\n";
            @v = ();
            my $oi = $i;
            while ( my $i < $eoa ) {
                if ( ord( $r[$i] ) == 0 ) {
                    # $v[] = substr($r, $oi, $i - $oi);
                    push( @v, substr( @r, $oi, $i - $oi ) );
                    $oi = $i + 1;
                }
                $i++;
            }
            $a = new Statistics::RserveClient::REXP::String();
            $a->setValues(@v);
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_ARRAY_BOOL ) {    # boolean vector
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_ARRAY_BOOL\n";
            my $n = Statistics::RserveClient::Funclib::int32( \@r, $i );
            $i += 4;
            my $k  = 0;
            my @vv = ();
            while ( $k < $n ) {
                my $v = Statistics::RserveClient::Funclib::int8( \@r, $i++ );
                $vv[$k]
                    = ( $v == 1 )
                    ? TRUE
                    : ( ( $v == 0 ) ? FALSE : undef );
                $k++;
            }
            $a = new Statistics::RserveClient::REXP::Logical();
            $a->setValues(@vv);
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_RAW ) {    # raw vector
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_RAW\n";
            my $len = Statistics::RserveClient::Funclib::int32( \@r, $i );
            $i += 4;
            my @v = substr( @r, $i, $len );
            my $a = new Statistics::RserveClient::REXP::Raw();
            $a->setValue(@v);
            # break;
        }

        elsif ( $ra == Statistics::RserveClient::XT_ARRAY_CPLX ) {
            Statistics::RserveClient::debug "Statistics::RserveClient::XT_ARRAY_CPLX\n";
            $a = FALSE;
            # break;
        }

        elsif ( $ra == 48 ) {    # unimplemented type in Statistics::RserveClient
            Statistics::RserveClient::debug "48\n";
            my $uit = Statistics::RserveClient::Funclib::int32( \@r, $i );
        # echo "Note: result contains type #$uit unsupported by Statistics::RserveClient.<br/>";
            @a = undef;
            # break;
        }

        else {
            warn(     'Warning: type '
                    . $ra
                    . ' is currently not implemented in the Perl client.' );
            @a = FALSE;
        }
    }

    Statistics::RserveClient::debug "dumping a:\n";
    Statistics::RserveClient::debug Dumper(@a);
    Statistics::RserveClient::debug "done\n";

    #if ( scalar(@attr) && is_object(@a) ) {
    if ( scalar(@attr) && @a ) {
        @a->setAttributes(@attr);
    }

    return @a;
}

#public static function  xtName($xt) {

sub xtName($) {
    my $xt = shift;

    if    ( $xt == Statistics::RserveClient::XT_NULL )         { return ('null'); }
    elsif ( $xt == Statistics::RserveClient::XT_INT )          { return 'int'; }
    elsif ( $xt == Statistics::RserveClient::XT_STR )          { return 'string'; }
    elsif ( $xt == Statistics::RserveClient::XT_DOUBLE )       { return 'real'; }
    elsif ( $xt == Statistics::RserveClient::XT_BOOL )         { return 'logical'; }
    elsif ( $xt == Statistics::RserveClient::XT_ARRAY_INT )    { return 'int*'; }
    elsif ( $xt == Statistics::RserveClient::XT_ARRAY_STR )    { return 'string*'; }
    elsif ( $xt == Statistics::RserveClient::XT_ARRAY_DOUBLE ) { return 'real*'; }
    elsif ( $xt == Statistics::RserveClient::XT_ARRAY_BOOL )   { return 'logical*'; }
    elsif ( $xt == Statistics::RserveClient::XT_ARRAY_CPLX )   { return 'complex*'; }
    elsif ( $xt == Statistics::RserveClient::XT_SYM )          { return 'symbol'; }
    elsif ( $xt == Statistics::RserveClient::XT_SYMNAME )      { return 'symname'; }
    elsif ( $xt == Statistics::RserveClient::XT_LANG )         { return 'lang'; }
    elsif ( $xt == Statistics::RserveClient::XT_LIST )         { return 'list'; }
    elsif ( $xt == Statistics::RserveClient::XT_LIST_TAG )     { return 'list+T'; }
    elsif ( $xt == Statistics::RserveClient::XT_LIST_NOTAG )   { return 'list/T'; }
    elsif ( $xt == Statistics::RserveClient::XT_LANG_TAG )     { return 'lang+T'; }
    elsif ( $xt == Statistics::RserveClient::XT_LANG_NOTAG )   { return 'lang/T'; }
    elsif ( $xt == Statistics::RserveClient::XT_CLOS )         { return 'clos'; }
    elsif ( $xt == Statistics::RserveClient::XT_RAW )          { return 'raw'; }
    elsif ( $xt == Statistics::RserveClient::XT_S4 )           { return 'S4'; }
    elsif ( $xt == Statistics::RserveClient::XT_VECTOR )       { return 'vector'; }
    elsif ( $xt == Statistics::RserveClient::XT_VECTOR_STR )   { return 'string[]'; }
    elsif ( $xt == Statistics::RserveClient::XT_VECTOR_EXP )   { return 'expr[]'; }
    elsif ( $xt == Statistics::RserveClient::XT_FACTOR )       { return 'factor'; }
    elsif ( $xt == Statistics::RserveClient::XT_UNKNOWN )      { return 'unknown'; }
    else {
        # unknown type
        return '<? ' . $xt . '>';
    }
}

# * @param Statistics::RserveClient::REXP $value
#  * This function is not functional. Please use it only for testing
#public static function createBinary(Statistics::RserveClient::REXP $value) {
sub createBinary($);

sub createBinary($) {

    my $value = shift;
    # Current offset
    my $o        = 0;                   # Init with header size
    my $contents = '';
    my $type     = $value->getType();

    for ($type) {
        if ( $type == Statistics::RserveClient::XT_S4 || $type == Statistics::RserveClient::XT_NULL ) {
            # break;
        }
        elsif ( $type == Statistics::RserveClient::XT_INT ) {
            my $v = 0 + $value->at(0);
            $contents .= Statistics::RserveClient::Funclib::mkint32($v);
            $o += 4;
            # break;
        }
        elsif ( $type == Statistics::RserveClient::XT_DOUBLE ) {
            my $v = 0.0 + $value->at(0);
            $contents .= Statistics::RserveClient::Funclib::mkfloat64($v);
            $o += 8;
            # break;
        }
        elsif ( $type == Statistics::RserveClient::XT_ARRAY_INT ) {
            my @vv = $value->getValues();
            my $n  = scalar(@vv);
            my $v;
            for ( my $i = 0; $i < $n; ++$i ) {
                $v = $vv[$i];
                $contents .= Statistics::RserveClient::Funclib::mkint32($v);
                $o += 4;
            }
            # break;
        }
        elsif ( $type == Statistics::RserveClient::XT_ARRAY_BOOL ) {
            my @vv = $value->getValues();
            my $n  = scalar(@vv);
            my $v;
            $contents .= Statistics::RserveClient::Funclib::mkint32($n);
            $o += 4;
            if ($n) {
                for ( my $i = 0; $i < $n; ++$i ) {
                    $v = $vv[$i];
                    if ( !defined($v) ) {
                        $v = 2;
                    }
                    else {
                        $v = 0 + $v;
                    }
                    if ( $v != 0 and $v != 1 ) {
                        $v = 2;
                    }
                    $contents .= chr($v);
                    ++$o;
                }
                while ( ( $o & 3 ) != 0 ) {
                    $contents .= chr(3);
                    ++$o;
                }
            }
            # break;
        }
        elsif ( $type == Statistics::RserveClient::XT_ARRAY_DOUBLE ) {
            my @vv = $value->getValues();
            my $n  = scalar(@vv);
            my $v;
            for ( my $i = 0; $i < $n; ++$i ) {
                $v = 0.0 + $vv[$i];
                $contents .= Statistics::RserveClient::Funclib::mkfloat64($v);
                $o += 8;
            }
            # break;
        }
        elsif ( $type == Statistics::RserveClient::XT_RAW ) {
            my $v = $value->getValue();
            my $n = $value->length();
            $contents .= Statistics::RserveClient::Funclib::mkint32($n);
            $o += 4;
            $contents .= $v;
            # break;
        }
        elsif ( $type == Statistics::RserveClient::XT_ARRAY_STR ) {
            my @vv = $value->getValues();
            my $n  = scalar(@vv);
            my $v;
            for ( my $i = 0; $i < $n; ++$i ) {
                $v = $vv[$i];
                if ($v) {
                    if ( ord( substr( $v, 0, 1 ) ) == 255 ) {
                        $contents .= chr(255);
                        ++$o;
                    }
                    $contents .= $v . chr(0);
                    $o += length($v) + 1;
                }
                else {
                    $contents .= chr(255) . chr(0);
                    $o += 2;
                }
            }
            while ( ( $o & 3 ) != 0 ) {
                $contents .= chr(1);
                ++$o;
            }
            # break;
        }
        elsif ($type == Statistics::RserveClient::XT_LIST_TAG
            || $type == Statistics::RserveClient::XT_LIST_NOTAG
            || $type == Statistics::RserveClient::XT_LANG_TAG
            || $type == Statistics::RserveClient::XT_LANG_NOTAG
            || $type == Statistics::RserveClient::XT_LIST
            || $type == Statistics::RserveClient::XT_VECTOR
            || $type == Statistics::RserveClient::XT_VECTOR_EXP )
        {
            my @l     = $value->getValues();
            my @names = ();
            if (   $type == Statistics::RserveClient::XT_LIST_TAG
                || $type == Statistics::RserveClient::XT_LANG_TAG )
            {
                @names = $value->getNames();
            }
            my $i = 0;
            my $n = scalar(@l);
            while ( $i < $n ) {
                my $x = $l[$i];
                if ( defined($x) ) {
                    $x = new Statistics::RserveClient::REXP::Null();
                }
                my $iof = strlen($contents);
                $contents .= createBinary($x);
                if (   $type == Statistics::RserveClient::XT_LIST_TAG
                    || $type == Statistics::RserveClient::XT_LANG_TAG )
                {
                    my $sym = new Statistics::RserveClient::REXP::Symbol();
                    $sym->setValue( $names[$i] );
                    $contents .= createBinary($sym);
                }
                ++$i;
            }
            # break;
        }

        elsif ( $type == Statistics::RserveClient::XT_SYMNAME or $type == Statistics::RserveClient::XT_STR ) {
            my $s = '' . $value->getValue();
            $contents .= $s;
            $o += strlen($s);
            $contents .= chr(0);
            ++$o;
            #padding if necessary
            while ( ( $o & 3 ) != 0 ) {
                $contents .= chr(0);
                ++$o;
            }
            # break;
        }

        else {
            # default for switch - handle this?
            die "unknown type";
        }
    }

    #
    # TODO: handling attr
    #  $attr = $value->attr();
    #  $attr_bin = '';
    #  if (defined($attr) ) {
    #    $attr_off = self::createBinary($attr, $attr_bin, 0);
    #    $attr_flag = Statistics::RserveClient::XT_HAS_ATTR;
    #   }
    #   else {
    #     $attr_off = 0;
    #     $attr_flag = 0;
    #   }
    # [0]   (4) header SEXP: len=4+m+n, XT_HAS_ATTR is set
    # [4]   (4) header attribute SEXP: len=n
    # [8]   (n) data attribute SEXP
    # [8+n] (m) data SEXP

    my $attr_flag = 0;
    my $length    = $o;
    my $isLarge   = ( $length > 0xfffff0 );
    my $code      = $type | $attr_flag;

    # SEXP Header (without ATTR)
    # [0]  (byte) eXpression Type
    # [1]  (24-bit int) length
    my @r;
    push( @r, chr( $code & 255 ) );
    push( @r, Statistics::RserveClient::Funclib::mkint24($length) );
    push( @r, $contents );
    return @r;
}

sub is_object($$) {
    # blessed $_[1] && $_[1]->isa($_[0]);
    my ( $obj, $name );
    if ( defined($obj) ) {
        return isa $obj, $name;
    }
    else {
        return FALSE;
    }
}

1;