
# This is a Perl5 file

# ChangeLog
#
# 2005-10-06
# * Fixed perlification of arrays -- basically it used to be
#     join ",", map { ~$_ }     @elems;  # it's now:
#     join ",", map { $_.perl } @elems;
# * Don't output "..." to create ranges, but "..", so .perl will return valid
#   Perl 6 code for lazy/infinite arrays:
#     (1, 2 ... 42)  # wrong
#     (1, 2 .. 42)   # correct
# * The perlification of an array containing only one element is now ($item,)
#   instead of ($item) (($item) is not an array, but ($item,) is).
#
# 2005-09-12
# * several fixes: @a[1,2].delete; @a.uniq; (-Inf..Inf); (-Inf..0) 
#
# 2005-09-08
# * finite sub-slices work: ($x,@a[1..3])=(1,2,3,4)
#
# 2005-09-05
# * delete slice
#
# 2005-08-31
# * New methods: keys(), values(), pairs(), kv(), pick()
#
# 2005-08-29
# * Lazy lists are deep cloned when Array is cloned
#
# 2005-08-29
# * Full support for lazy splicing and sparse array
# * Added support for store() past the end of the array
# * Simplified fetch()
#
# 2005-08-27
# * Fixed fetch/store single elements from a lazy slice
# * Fixed fetch/store of whole lazy slice
#   supports syntax: @a = (0..Inf); @a[1,10,100..10000] = @a[500..30000]
#   (needs optimization)
# * New method tied()
# * Fixed binding of fetched result
# * Fixed stringification of unboxed values
# * New parameter 'max' in perl() and str() methods - controls how many elements
#   of the lazy array are stringified.
# * Array is stringified using parenthesis.
#
# 2005-08-26
# * New internal class 'Perl6::Slice'
#   supports syntax: @a = (2,3,4,5); @a[1,2] = @a[0,3]
#
# 2005-08-12
# * store(List) actually means store([List])
# * fixed new(), elems(), pop(), shift(), fetch()
# * fixed "defined $i" to "elems == 0" in push/pop
#
# 2005-08-11
# * fixed some syntax errors
#
# 2005-08-10
# * Ported from Perl6 version

# BUGS
# - Infinite sub slices are not supported yet:
#   ($x,@a[1..99])=(1,2,3,4)   - ok - non-finite sub-slice
#   (@a[1..Inf])=(1,2,3,4)     - ok - not a sub-slice
#   ($x,@a[1..Inf])=(1,2,3,4)  - not supported

# RECENTLY FIXED
#   @a[10,100,1000,10000,100000]=(1..9999999) 
#        returned undef (the list went into array element zero)

# TODO - @a[1].delete doesn't work

# TODO - tied arrays should use the standard Perl 5 interface
#        test push, scalar, etc - with @INC

# TODO - PIL-Run - (1,undef,2) returns (1,2) - but (1,\undef,2) works
# TODO - PIL-Run - grep() using 'Code'

# TODO - finish lazy slices
#        @a[1..100000000]=@b[1..20000000]
#        (@a[1..100],@b[1..50])=(@a[1..50],@b[1..100])

# TODO - check specification of Array, Hash, Pair stringification

# TODO - sum()
# TODO - there are too many methods under AUTOLOAD - upgrade them to real methods
#
# TODO - optimize Eager array to O(1)
#      - currently disabled with "if 0 &&" (Perl6::Container::Array)
#
# TODO - ($a,undef,$b) = @a - fixed, add test
#      - (@a[1..10],$a,undef,$b) = @a
#      - ($a,@x[1,2])=(4,5,6)
#
# TODO - @a[1] == Scalar
# TODO - test - @a[1] := $x

# TODO - tied arrays are copied in "Eager" mode?
#        (PerlJam) - well, I wouldn't think you'd have to copy them all at once.
#        Each element of @a would be sort of a lazy proxy for each element in the tied array @b

# TODO - @a[1..2]=(1,2) 
#        &postcircumfix:<[ ]> has to be "is rw"
#        &infix:<,> has to be is rw, too (think ($a, undef, $b) = (1,2,3))

# TODO - store(List) actually means store([List]), Perl6 version
# TODO - fix "defined $i" to "elems == 0" in push/pop, Perl6 version
# TODO - splice() should accept a 'List' object, Perl6 version too
#
# TODO - Tests: - add to t/data_types/lazy_lists.t
# TODO - fetch/store should not destroy binding
# TODO - test splice offset == 0, 1, 2, -1, -2, -Inf, Inf
# TODO - test splice length == 0, 1, 2, Inf, negative
# TODO - test splice list == (), (1), (1,2), Iterators, ...
# TODO - test splice an empty array
# TODO - test multi-dimensional array

# Notes:
# * Cell is implemented in the Perl6::Container::Scalar package

use strict;
use Carp;

use Perl6::MetaModel;
use Perl6::Value;
use Perl6::Container::Scalar;

use constant Inf => Perl6::Value::Num::Inf;

my $class_description = '-0.0.1-cpan:FGLOCK';

# ------ Perl6::Slice -----

sub Perl6::Slice::new { 
    my $class = shift;
    my %param = @_;  
    #warn "NEW SLICE: ".Perl6::Value::stringify($param{array})." -- ".Perl6::Value::stringify($param{slice})."\n";
    bless { %param }, $class;
} 
sub Perl6::Slice::clone { 
    my $self = shift;
    return $self->unbind;
    #my $a = Array->new;
    #$a->push( $self->items );
    #$a = $a->clone;
    #return $a;
} 
sub Perl6::Slice::items {
    my $self = shift;
    #warn "SLICE ITEMS: ".Perl6::Value::stringify($self->{array})." -- ".Perl6::Value::stringify($self->{slice})."\n";
    #$self->{array}->items;
    my @a;
    warn "Infinite sub-slices are not supported yet" 
        if Perl6::Value::numify( $self->is_infinite );
    for ( 0 .. Perl6::Value::numify( $self->elems ) - 1 ) {
        push @a, $self->fetch( $_ );
    }
    # warn "    items: @a\n";
    return @a;
}
sub Perl6::Slice::fetch {
    my $self = shift;
    my $i = shift;
    my $pos = Perl6::Value::numify( $self->{slice}->fetch( $i ) );
    #warn "SLICE FETCH: at ($i) $pos -- @_ -- ".Perl6::Value::stringify($self->{array}->fetch( $pos, @_ ))."\n";
    return unless defined $pos && $pos >= 0;
    $self->{array}->fetch( $pos, @_ );
}
sub Perl6::Slice::store {
    my $self = shift;
    my $i = shift;
    my $pos = Perl6::Value::numify( $self->{slice}->fetch( $i ) );
    #warn "SLICE STORE: at ($i) $pos -- @_"."\n";
    return unless defined $pos && $pos >= 0;
    $self->{array}->store( $pos, @_ );
}
sub Perl6::Slice::is_infinite {
    my $self = shift; 
    $self->{slice}->is_infinite()->unboxed;
}
sub Perl6::Slice::elems {
    my $self = shift; 
    $self->{slice}->elems()->unboxed;
}
sub Perl6::Slice::unbind {
    # creates a new Array - not bound to the original array/slice
    my $self = shift; 
    #warn "SLICE UNBIND: ".Perl6::Value::stringify($self->{array})." -- ".Perl6::Value::stringify($self->{slice})."\n";
    my $ary = $self->{array};
    my @idx = $self->{slice}->items;
    my $result = Array->new;
    my $pos = 0;
    for my $i ( @idx ) {
        # warn "unbind() loop...";
        if ( UNIVERSAL::isa( $i, 'Perl6::Value::List' ) ) {
            die "Not implemented: instantiate lazy slice using a non-contiguous list"
                unless $i->is_contiguous;
            my $start = $i->start;
            my $end =   $i->end;
            die "Slice start/end is not defined"
                unless defined $end && defined $start;
            die "Not implemented: instantiate lazy slice using a reversed list"
                unless $end >= $start;
            #warn "unbind: Index: ". $i->str . "\n";
            # warn "Slicing from $start to $end";
            my $slice = $ary->splice( $start, ( $end - $start + 1 ) );
            my $elems = $slice->elems->unboxed;
            # warn "splice 2 - elems = $elems - slice isa $slice";
            $ary->splice( $start, 0, $slice );
            # warn "splice done";
            # items should be cloned before storing
            my @items = $slice->unboxed->items;
            @items = map {
                        # warn "unbind - elems ". $_->elems . "\n";
                        UNIVERSAL::isa( $_, 'Perl6::Value::List' ) ? $_->clone : $_
                    } @items;
            $result->push( @items );
            if ( $elems < ( $end - $start + 1 ) ) {
                my $diff = $end - $start + 1 - $elems;
                # warn "Missing $diff elements";
                $result->push( Perl6::Value::List->from_x( item => undef, count => $diff ) );
            }
            $pos = $pos + $end - $start + 1;
            # warn "pos = $pos";
        }
        else {
            # non-lazy slicing
            my $tmp = $ary->fetch( $i )->fetch;
            $result->store( $pos, $tmp );
            $pos++;            
        }
    }
    return $result;
}
sub Perl6::Slice::write_thru {
    # writes back to the bound Array using the slice as an index
    my $self = shift; 
    my $other = shift;
    #warn "SLICE WRITE THROUGH: ".Perl6::Value::stringify($self->{array})." -- ".Perl6::Value::stringify($self->{slice})."\n";
    #warn "               FROM: ".Perl6::Value::stringify($other)."\n";
    #warn "SLICE WRITE THROUGH: ".$self->{array}." -- ".$self->{slice}."\n";
    #warn "               FROM: ".$other."\n";
    my $ary = $self->{array};
    my @idx = $self->{slice}->items;
    my $pos = 0;
    for my $i ( @idx ) {
        #warn "write loop... ". Perl6::Value::stringify($i)." -- $i\n";
        if ( UNIVERSAL::isa( $i, 'Perl6::Value::List' ) ) {
            # warn "List -- ". $i->is_contiguous;
            die "Not implemented: instantiate lazy slice using a non-contiguous list"
                unless $i->is_contiguous;
            my $start = $i->start;
            my $end =   $i->end;
            die "Slice start/end is not defined"
                unless defined $end && defined $start;
            die "Not implemented: instantiate lazy slice using a reversed list"
                unless $end >= $start;
            #warn "write_thru: Slicing from position $pos to ( $start .. $end )\n";
            #warn "    Index: ". $i->str . "\n";
            # items should be cloned before storing
            #my $ary_elems = $ary->elems->unboxed;
            #warn "other.elems = ".$other->elems->unboxed."\n";
            my $max_ary_elems = $ary->elems->unboxed - $start;
            #warn "array has $ary_elems elements, starts in $start, max = $max_ary_elems\n";
            my $slice_size = $end - $start + 1;
            $slice_size = $max_ary_elems if $slice_size > $max_ary_elems;
            my $slice = $other->splice( $pos, $slice_size );
            my $elems = $slice->elems->unboxed;
            # warn "splice 3 - elems = $elems - slice isa $slice";
            my @items = $slice->unboxed->items;
            if ( $elems < $slice_size ) {
                my $diff = $slice_size - $elems;
                #warn "Missing $diff elements";
                push @items, Perl6::Value::List->from_x( item => undef, count => $diff )
                    if $diff > 0;
            }
            #warn "  STORE SLICE pos $pos to $start, $slice_size, @items";
            # TODO - XXX - don't use splice on slices, because it drops bindings
            $ary->splice( $start, $slice_size, @items );
            $pos = $pos + $slice_size;
            #warn "pos = $pos";
        }
        else {
            # non-lazy slicing
            my $tmp = $other->fetch( $pos )->fetch;
            $ary->store( $i, $tmp );
            $pos++;            
        }
    }
    return;
}

# ------ end Perl6::Slice -----

class1 'Array'.$class_description => {
    is => [ $::Object ],
    class => {
        attrs => [],
        methods => {}
    },
    instance => {
        attrs => [ [ '$:cell' => { 
                        access => 'rw', 
                        build => sub { 
                            # warn " ---- new @_ ---- ";
                            my $cell = Perl6::Cell->new;
                            my $h = Perl6::Container::Array->new( items => [ @_ ] );
                            $cell->{v} = $h;
                            $cell->{type} = 'Array';
                            return $cell;
                        } } ] ],
        DESTROY => sub {
            # _('$:cell' => undef); # XXX - MM2.0 gc workaround
        },
        methods => { 

            # @a := @b 
            'bind' =>     sub {
                my ( $self, $thing ) = @_;
                die "argument to Array bind() must be a Array"
                    unless $thing->cell->{type} eq 'Array';
                _('$:cell', $thing->cell);
                return $self;
            },
            'cell' =>     sub { _('$:cell') },  # cell() is used by bind() / XXX - just rename $:cell to $.cell
            'id' =>       sub { _('$:cell')->{id} },  

            'tieable' =>  sub { _('$:cell')->{tieable} != 0 },
            'tie' =>      sub { shift; _('$:cell')->tie(@_) },
            'untie' =>    sub { _('$:cell')->untie },
            'tied' =>     sub { _('$:cell')->{tied} },

             # See perl5/Perl6-MetaModel/t/14_AUTOLOAD.t  
            'isa' =>      sub { ::next_METHOD() },
            'does' =>     sub { ::next_METHOD() },
            'ref' =>      sub { $::CLASS }, 
            'unboxed' =>  sub { 
                _('$:cell')->{tied} ? _('$:cell')->{tied} : _('$:cell')->{v}
            },
            'undefine' => sub { (shift)->store( Array->new ) },
            'delete' =>   sub {
                # delete a slice, returns deleted items
                my ( $self, @list ) = @_;
                #warn "Trying to delete() a non-slice" unless $self->tied;

                if ( UNIVERSAL::isa( $self->tied, 'Perl6::Slice' ) ) {
                    # delete from slice
                    my $ret = Array->new();
                    $ret = $self->clone;
                    $self->store( Perl6::Value::List->from_x( item => undef, count => Inf ) );
                    return $ret;
                }

                #warn "DELETE LIST @list";
                $self->slice( @list )->delete( 
                    Perl6::Value::List->from_num_range( start => 0, end => Inf ) 
                );
                
            },
            'slice' =>    sub {
                # Returns an array whose fetch/store are bound to this array
                my ( $self, @list ) = @_;

                my $list = $list[0];
                if ( !Perl6::Value::p6v_isa($list,'Array') ) {
                    $list = Array->new();
                    $list->push( $_ ) for @list;
                }

                # given $list = (4,5,6)
                # given an index $i = 1
                #   get $list[$i] == 5
                #   ignore request if index == undef
                #   store/fetch from array[$list[$i]] == array[5]
                my $ret = Array->new();
                $ret->cell->{tieable} = 1;
                my $proxy = Perl6::Slice->new( 
                    array => $self, 
                    slice => $list,   
                );
                $ret->tie( $proxy );
                return $ret;
            },
            'zip' => sub {
                my ( $array, @array_list ) = map {
                        Perl6::Value::p6v_isa( $_, 'Array' ) ? 
                        $_->to_list : 
                        warn "Argument to zip() must be an Array"; 
                    } @_; 
                my $res = Array->new;
                $res->push( $array->zip( @array_list ) );
                return $res;
            },
            'map' => sub {
                my $array = shift;  $array = $array->clone->to_list;
                my $code = shift;
                die "Argument to map() must be a Code" unless Perl6::Value::p6v_isa( $code, 'Code' );
                my $res = Array->new;
                $res->push( $array->map( $code ) );
                return $res;
            },
            'kv' => sub  { 
                my $array = shift; 
                my $keys = $array->keys;
                my $values = $array->values;
                return $keys->zip( $values );
            },
            'pairs' => sub  { 
                my $array = shift; 
                $array = $array->clone;
                my $shifted = 0;
                my $popped = $array->elems->unboxed - 1;
                my $ret = Array->new();
                # XXX - rewrite this using map()
                $ret->push(
                    # XXX - TODO - optimization - shift_n, pop_n
                    Perl6::Value::List->new(
                        cstart => sub { 
                            return Pair->new( 
                                '$.key' =>   $shifted++, 
                                '$.value' => $array->shift )
                        },
                        cend =>   sub { 
                            return Pair->new( 
                                '$.key' =>   $popped--, 
                                '$.value' => $array->pop )
                        },
                        celems => sub { $array->elems->unboxed },
                        is_lazy => 1,
                    )
                );
                return $ret;
            },
            'values' => sub  { 
                my $array = shift; 
                return $array->clone;
            },
            'keys' => sub  { 
                my $array = shift; 
                my $ret = Array->new();
                $ret->push(
                    Perl6::Value::List->from_num_range( 
                        start => 0, 
                        end =>   $array->elems->unboxed - 1 ) ); 
                return $ret;
            },
            'pick' => sub  { 
                my $array = shift; 
                my $n = $array->elems->unboxed;
                $n = 10E9 if $n == &Inf;
                return $array->fetch( int( rand( $n ) ) );
            },
            'AUTOLOAD' => sub {
                my ($self, @param) = @_;
                my $method = __('$AUTOLOAD');
                my $tmp = $self->unboxed;
                # warn "AUTOLOAD ",ref($tmp), ' ', $method, " @param == " . $tmp->$method( @param );

                @param = 
                    map {
                        # Perl6::Value::p6v_isa( $_, 'Array' ) ? $_->unboxed->items :  
                        Perl6::Value::p6v_isa( $_, 'List' ) ? $_->unboxed :  
                        UNIVERSAL::isa( $_, 'Perl6::Container::Array' ) ? $_->items : 
                        $_ 
                    } @param;
                
                if ( $method eq 'clone' || $method eq 'splice' || $method eq 'reverse' ) {
                    my $ret = Array->new();
                    my @result = $tmp->$method( @param )->items;
                    $ret->unboxed->push( @result );
                    #warn "-- @result "; # . Perl6::Value::stringify($result->shift). " ... ". Perl6::Value::stringify($result->pop);
                    #warn "reversed: ".$ret->str->unboxed;
                    #use Data::Dumper; $Data::Dumper::Indent=1;
                    #print Dumper($ret);
                    return $ret;
                }
                
                if ( $method eq 'push'   || $method eq 'unshift' || $method eq 'store' ) {
                    #warn "STORING THINGS $method @param";
                    if ( $method eq 'store' && @param == 1 ) {
                        # whole Array store
                        #warn "WHOLE ARRAY STORE";
                        # XXX - what if the array is tied?
                        #  @a = (2,3,4,5); @a[1,2] = @a[0,3]
                        my $other = $param[0];
                        # if ( $self->cell->{tied} ||
                        #      $other->cell->{tied} ) 
                        
                        # if ( $other->is_infinite->unboxed ) {
                        #    die "Infinite slices and tied arrays are not yet fully supported";
                        # }

                        if ( Perl6::Value::p6v_isa( $other, 'Array' ) ) {
                            if ( UNIVERSAL::isa( $other->tied, 'Perl6::Slice' ) ) {
                                # unbind the slice from the original arrays
                                $other = $other->tied->unbind;
                            }
                        }
                        else {
                            my $tmp = Array->new();
                            $tmp->push( $other );
                            $other = $tmp;
                        }

                        my @items = $other->unboxed->items;  
                        if ( UNIVERSAL::isa( $self->tied, 'Perl6::Slice' ) ) {
                            #warn "WRITE THROUGH ".Perl6::Value::stringify($other);
                            $self->tied->write_thru( $other );
                            return $other;
                            #return $self;
                        }
                        #warn "got @items - current = ". $self->cell->{v};

                        # unbind cells
                        @items = map {
                                Perl6::Value::p6v_isa($_,'Scalar') ? $_->fetch : $_
                            } @items;

                        my $ret = Perl6::Container::Array->from_list( @items );
                        $self->cell->{v} = $ret;
                        return $self;
                    }

                    if ( $method eq 'store' ) {
                        #warn "STORING @param";
                        my $pos = shift @param;
                        my $elem = $tmp->fetch( $pos );
                        if ( Perl6::Value::p6v_isa( $elem, 'Scalar' ) ) {
                            #warn "CELL TO STORE IS A SCALAR: $elem";
                            $elem->store( @param );
                        }
                        else
                        {
                            #warn "CELL TO STORE IS NOT YET A SCALAR: $elem";
                            my $scalar = Scalar->new();
                            $scalar->store( @param );
                            $tmp->store( $pos, $scalar );
                        }
                        return $self;
                    }

                    #for ( @param ) {
                    #    next if UNIVERSAL::isa( $_, 'Perl6::Value::List' );
                    #    next if Perl6::Value::p6v_isa( $_, 'Scalar' );
                    #    next if Perl6::Value::p6v_isa( $_, 'Array' );
                    #    next if Perl6::Value::p6v_isa( $_, 'Hash' );
                    #    my $tmp = $_;
                    #    $_ = Scalar->new();
                    #    $_->store( $tmp );
                    #    warn " SCALAR ",$_->str->unboxed;
                    #};
                    #warn "Array.$method PARAM @param\n";
                    $tmp->$method( @param );
                    return $self;
                }

                if ( $method eq 'fetch' ) {
                    # warn "FETCHING THINGS @param";
                    if ( @param == 0 ) {
                        # whole Array fetch
                        return $self;
                    }
                    my $elem = $tmp->$method( @param );
                    my $scalar;
                    if ( Perl6::Value::p6v_isa( $elem, 'Scalar' ) ) {
                        #warn "FETCHED CELL IS A SCALAR: $elem";
                        $scalar = $elem;
                    }
                    else
                    {
                        #warn "FETCHED CELL IS NOT YET A SCALAR: $elem [ @param ]";
                        $scalar = Scalar->new();
                        $scalar->store( $elem );
                        # replace Value with Scalar
                        #warn "STORE = @param, $scalar";
                        # XXX - TODO - test with multi-dim fetch
                        $self->store( @param, $scalar );
                        $scalar = $tmp->$method( @param );
                    }
                    return $scalar;
                    #my $ret = Scalar->new();
                    #$ret->bind( $scalar );
                    #return $ret;
                }

                if ( $method eq 'pop'   || $method eq 'shift' ) {
                    my $elem = $tmp->$method( @param );
                    unless ( Perl6::Value::p6v_isa( $elem, 'Scalar' ) ) {
                        # XXX - I think only fetch() need to return Scalar 
                        my $scalar = Scalar->new();
                        $scalar->store( $elem );
                        return $scalar;
                    }
                    return $elem;
                }

                if ( $method eq 'elems' || $method eq 'int' || $method eq 'num' ) {
                    return Int->new( '$.unboxed' => $tmp->elems( @param ) )
                }
                if ( $method eq 'exists' ) {
                    # XXX - TODO - recursive to other dimensions
                    return Bit->new( '$.unboxed' => ($tmp->elems > Perl6::Value::numify($param[0]) ) )
                }
                if ( $method eq 'is_infinite' ) {
                    return Bit->new( '$.unboxed' => $tmp->$method( @param ) )
                }
                
                return $tmp->$method( @param );
            },
            
            str => sub {
                #warn "PRINT @_\n";
                my $self = shift;
                my %param = @_;
                my $samples = $param{'max'};
                # my $self = $array->unboxed; # _('$:cell')->{tied} ? _('$:cell')->{tied} : _('$:cell')->{v};
                # warn "ELEMS ",$self->elems;
                $samples-- if defined $samples;
                $samples = 100 unless defined $samples || $self->is_infinite; 
                $samples = 2   unless defined $samples;
                my @start;
                my @end;
                my $tmp;
                for ( 0 .. $samples ) {
                    no warnings 'numeric';
                    last if $_ >= Perl6::Value::numify( $self->elems );
                    $tmp = $self->fetch( $_ );
                    $tmp = Perl6::Value::stringify( $tmp );
                    push @start, $tmp;
                    last if $tmp eq 'Inf' || $tmp eq '-Inf';
                }
                for ( map { - $_ - 1 } 0 .. $samples ) {
                    no warnings 'numeric';
                    # warn "  UNSHIFT: ".$self->elems->unboxed." ".Perl6::Value::numify( $self->elems )." + $_ >= scalar ".(scalar @start)."\n";
                    last unless Perl6::Value::numify( $self->elems ) + $_ >= scalar @start;
                    $tmp = $self->fetch( $_ );
                    $tmp = Perl6::Value::stringify( $tmp );
                    unshift @end, $tmp;
                    last if $tmp eq 'Inf' || $tmp eq '-Inf';
                }
                my $str = '';
                if ( @start > 0 ) {
                    if ( Perl6::Value::numify( $self->elems ) == ( scalar @start + scalar @end ) ) {
                        $str =  join( ' ', map { Perl6::Value::stringify($_) } @start, @end );
                    }
                    else {
                        $str =  join( ' ', map { Perl6::Value::stringify($_) } @start ) .
                                ' .. ' . 
                                join( ' ', map { Perl6::Value::stringify($_) } @end );
                    }
                }
                return Str->new( '$.unboxed' => $str );                
            },
            perl => sub {
                #warn "PRINT @_\n";
                my $self = shift;
                my %param = @_;
                my $samples = $param{'max'};
                # my $self = $array->unboxed; # _('$:cell')->{tied} ? _('$:cell')->{tied} : _('$:cell')->{v};
                # warn "ELEMS ",$self->elems;
                $samples-- if defined $samples;
                $samples = 100 unless defined $samples || $self->is_infinite; 
                $samples = 2   unless defined $samples;
                my @start;
                my @end;
                my $tmp;
                for ( 0 .. $samples ) {
                    no warnings 'numeric';
                    last if $_ >= Perl6::Value::numify( $self->elems );
                    $tmp = $self->fetch( $_ );
                    $tmp = Perl6::Value::stringify( $tmp->perl );
                    push @start, $tmp;
                    last if $tmp eq 'Inf' || $tmp eq '-Inf';
                }
                for ( map { - $_ - 1 } 0 .. $samples ) {
                    no warnings 'numeric';
                    # warn "  UNSHIFT: ".$self->elems->unboxed." ".Perl6::Value::numify( $self->elems )." + $_ >= scalar ".(scalar @start)."\n";
                    last unless Perl6::Value::numify( $self->elems ) + $_ >= scalar @start;
                    $tmp = $self->fetch( $_ );
                    $tmp = Perl6::Value::stringify( $tmp->perl );
                    unshift @end, $tmp;
                    last if $tmp eq 'Inf' || $tmp eq '-Inf';
                }
                my $str = '';
                if ( @start > 0 ) {
                    if ( Perl6::Value::numify( $self->elems ) == ( scalar @start + scalar @end ) ) {
                        $str =  join( ', ', @start, @end );
                    }
                    else {
                        $str =  join( ', ', @start ) .
                                ' .. ' . 
                                join( ', ', @end );
                    }
                }
                # Ensure that ($only_one_item,).perl gets perlificated
                # correctly (i.e. not ($item), but ($item,)).
                $str .= "," if Perl6::Value::numify( $self->elems ) == 1;
                return Str->new( '$.unboxed' => '(' . $str . ')' );                
            },
        },
    }
};

# ----- unboxed functions

package Perl6::Container::Array;

use strict;
use Perl6::Value;
use Perl6::Value::List;
use Carp;

use constant Inf => Perl6::Value::Num::Inf;

sub new {
    my $class = shift;
    my %param = @_;
    my @items = @{$param{items}};
    # warn "-- new -- @items --";
    return bless { items => \@items }, $class;
}

sub clone { 
    # XXX - TODO - clone Scalars
    my $self = bless { %{ $_[0] } }, ref $_[0];
    @{$self->{items}} = map {
            UNIVERSAL::isa( $_, 'Perl6::Value::List' ) ? $_->clone : $_
        } @{$self->{items}};
    return $self;
}

sub sum { 
    my $self = shift;
    my $sum = 0;
    for ( @{$self->{items}} ) {
        if ( UNIVERSAL::isa( $_, 'Perl6::Value::List' ) ) {
            $sum += $_->sum
        }
        elsif ( ref( $_ ) ) {
            $sum += $_->num->unboxed
        }
        else {
            $sum += $_
        }
    }
    return $sum;
}

sub items {
    my $self = shift;
    # my @x = %$self;  warn "-- items -- @x --";
    return @{$self->{items}};
}

sub from_list {
    my $class = shift;
    $class->new( items => [@_] );
}

sub _shift_n { 
    my $array = shift;
    my $length = shift;
    my @ret;
    my @tmp = @{$array->{items}};
    if ( $length == Inf ) {
        my $len = $array->elems;
        @{$array->{items}} = ();
        return ( $len, @tmp );
    }
    my $ret_length = 0;
    while ( @tmp ) {
        # warn "ret $ret_length == ".scalar(@ret)." length $length";
        last if $ret_length >= $length;
        if ( 0 && UNIVERSAL::isa( $tmp[0], 'ARRAY') ) {
            if ( @{$tmp[0]} ) {
                my $diff = $length - $ret_length;
                my @i = splice( @{$tmp[0]}, 0, $diff );
                push @ret, \@i;
                $ret_length += @i;
                last if $ret_length >= $length;
            }
            else {
                shift @tmp;
            }
            next;
        }
        if ( UNIVERSAL::isa( $tmp[0], 'Perl6::Value::List') ) {
            if ( $tmp[0]->elems > 0 ) {
                # my $i = $tmp[0]->shift;
                my $li = $tmp[0];
                my $diff = $length - $ret_length;
                my $i = $li->shift_n( $diff );
                push @ret, $i;
                if ( UNIVERSAL::isa( $i, 'Perl6::Value::List') ) {
                    $ret_length += $i->elems;
                }
                else {
                    $ret_length++;
                }
                # warn "push list ". $i->start . ".." . $i->end . " now length=$ret_length";
                last if $ret_length >= $length;
            }
            else {
                shift @tmp;
            }
            next;
        }
        push @ret, shift @tmp;
        $ret_length++;
    };
    @{$array->{items}} = @tmp;
    # warn "ret @ret ; array @tmp ";
    return ( $ret_length, @ret );
}

sub _pop_n {
    my $array = shift;
    my $length = shift;
    my @ret;
    my @tmp = @{$array->{items}};
    if ( $length == Inf ) {
        my $len = $array->elems;
        @{$array->{items}} = ();
        return ( $len, @tmp );
    }
    my $ret_length = 0;
    while ( @tmp ) {
        # warn "ret ".scalar(@ret)." length $length";
        last if $ret_length >= $length;
        if ( 0 && UNIVERSAL::isa( $tmp[0], 'ARRAY') ) {
            if ( @{$tmp[0]} ) {
                my $diff = $length - $ret_length;
                my @i = splice( @{$tmp[0]}, -$diff, $diff );
                push @ret, \@i;
                $ret_length += @i;
                last if $ret_length >= $length;
            }
            else {
                shift @tmp;
            }
            next;
        }
        if ( UNIVERSAL::isa( $tmp[-1], 'Perl6::Value::List') ) {
            if ( $tmp[-1]->elems > 0 ) {
                # my $i = $tmp[-1]->pop;
                # unshift @ret, $i;
                my $li = $tmp[-1];
                my $diff = $length - $ret_length;
                my $i = $li->pop_n( $diff );
                unshift @ret, $i;
                if ( UNIVERSAL::isa( $i, 'Perl6::Value::List') ) {
                    $ret_length += $i->elems;
                }
                else {
                    $ret_length++;
                }
                # warn "pop list ". $i->start . ".." . $i->end . " now length=$ret_length";
                last if $ret_length >= $length;
            }
            else {
                pop @tmp;
            }
            next;
        }
        unshift @ret, pop @tmp;
        $ret_length++;
    };
    @{$array->{items}} = @tmp;
    # warn "ret @ret ; array @tmp ";
    return ( $ret_length, @ret );
}

sub elems {
    my $array = shift;
    my $count = 0;
    for ( @{$array->{items}} ) {
        $count += UNIVERSAL::isa( $_, 'ARRAY') ? 0 + @$_ :
                  UNIVERSAL::isa( $_, 'Perl6::Value::List') ? $_->elems  :
                  1;
    }
    $count;
}

sub is_infinite {
    my $array = shift;
    for ( @{$array->{items}} ) {
        return 1 if UNIVERSAL::isa( $_, 'Perl6::Value::List') && $_->is_infinite;
    }
    0;
}

sub is_lazy {
    my $array = shift;
    for ( @{$array->{items}} ) {
        return 1 if UNIVERSAL::isa( $_, 'Perl6::Value::List') && $_->is_lazy;
    }
    0;
}

sub flatten {
    # this needs optimization
    my $array = shift;
    my $ret = $array->clone;
    for ( @{$ret->{items}} ) {
        $_ = $_->flatten() if UNIVERSAL::isa( $_, 'Perl6::Value::List') && $_->is_lazy;
    }
    $ret;
}

sub splice { 
    my $array =  shift;
    my $offset = shift; $offset = Perl6::Value::numify( $offset ); $offset = 0   unless defined $offset;
    my $length = shift; $length = Perl6::Value::numify( $length ); $length = Inf unless defined $length;
    my @list = @_;
    my $class = ref($array);
    my ( @head, @body, @tail );
    my ( $len_head, $len_body, $len_tail );
    # print "items: ", $array->items, " splice: $offset, $length, ", @list, "\n";
    # print 'insert: ', $_, ' ', $_->ref for @list, "\n";
    # print " offset $offset length $length \n";
    if ( $offset >= 0 ) {
        ( $len_head, @head ) = $array->_shift_n( $offset );
        if ( $length >= 0 ) {
            #  head=shift offset -> body=shift length -> tail=remaining
            ( $len_body, @body ) = $array->_shift_n( $length );
            ( $len_tail, @tail ) = $array->_shift_n( Inf );
        }
        else {
            #  tail=pop length -> head=shift offset -> body=remaining 
            ( $len_tail, @tail ) = $array->_pop_n( -$length );
            ( $len_body, @body ) = $array->_shift_n( Inf );
        }
    }
    else {
        ( $len_tail, @tail ) = $array->_pop_n( -$offset );
        ( $len_head, @head ) = $array->_shift_n( Inf );
        if ( $length >= 0 ) {
            # negative offset, positive length
            #  tail=pop length -> head=remaining -> body=shift tail until body == length
            # make $#body = $length
            my $tail = $class->from_list( @tail );
            ( $len_body, @body ) = $tail->_shift_n( $length );
            @tail = $tail->items;
        }
        else {
            # negative offset, negative length
            #  tail=pop length -> head=remaining -> body=shift tail until tail == length
            # make $#tail = -$length
            my $body = $class->from_list( @tail );
            ( $len_tail, @tail ) = $body->_pop_n( -$length );
            @body = $body->items;
        }
    };
    # print "off: $offset len: $length head: @head body: @body tail: @tail list: @list\n";
    @{$array->{items}} = ( @head, @list, @tail );
    return $class->from_list( @body );
}

sub end  {
    my $array = shift;
    return unless $array->elems;
    my $x = $array->pop;
    $array->push( $x );
    return $x;
}

sub fetch {
    # XXX - this is inefficient because it needs 2 splices
    # see also: splice()
    my $array = shift;
    my $pos =   shift; $pos = Perl6::Value::numify( $pos );
    
    #use Data::Dumper;
    #warn "-- array -- ". Dumper( $array );
    #warn "uninitialized value used in numeric context"
    #    unless defined $pos;  
    return if $pos >= $array->elems;

    my $ret = $array->splice( $pos, 1 );
    ($ret) = @{$ret->{items}};
    $ret = $ret->shift if UNIVERSAL::isa( $ret, 'Perl6::Value::List' );
    if ( $pos < 0 ) {
        if ( $pos == -1 ) {
            $array->push( $ret );
        }
        else {
            $array->splice( $pos+1, 0, $ret );
        }
    }
    else {
        $array->splice( $pos, 0, $ret );
    }
    # warn "FETCH $pos returns $ret";
    return $ret;
}

sub store {
    my $array = shift;
    my $pos =   shift; $pos = Perl6::Value::numify( $pos );
    my $item  = shift;
    # warn "uninitialized value used in numeric context"
    #    unless defined $pos;  
    if ( UNIVERSAL::isa( $item, 'Perl6::Value::List') ) {
        my $class = ref($array);
        $item = $class->new( items => [$item] );
    }
    if ( $pos <= $array->elems ) {
        # 'Array' takes care of proper cell re-binding 
        my $scalar = $array->fetch( $pos );
        if ( Perl6::Value::p6v_isa( $scalar, 'Scalar' ) ) {
            # warn "Store to scalar\n";
            $scalar->store( $item );
        }
        else {
            $array->splice( $pos, 1, $item );
        }
        return $array;
    }
    # store after the end 
    my $fill = Perl6::Value::List->from_x( item => undef, count => ( $pos - $array->elems ) );
    push @{$array->{items}}, $fill, $item;
    return $array;
}

sub reverse {
    my $array = shift;
    my @rev = reverse @{$array->{items}};
    @rev = map {
            UNIVERSAL::isa( $_, 'ARRAY' ) ? [ reverse( @$_ ) ] : 
            UNIVERSAL::isa( $_, 'Perl6::Value::List' ) ? $_->reverse : 
            $_
        } @rev;
    return Perl6::Container::Array->from_list( @rev );
}

sub to_list {
    my $array = shift;
    my $ret = $array->clone;
    # XXX - TODO - optimization - return the internal list object, if there is one
    # XXX - TODO - optimization - add shift_n, pop_n closures
    return Perl6::Value::List->new(
            cstart => sub { $ret->shift },
            cend =>   sub { $ret->pop },
            celems => sub { $ret->elems },
            is_lazy => $ret->is_lazy,
        )
}

sub unshift {
    my $array = shift;
    unshift @{$array->{items}}, @_;
    return $array;
}

sub push {
    my $array = shift;
    push @{$array->{items}}, @_;
    return $array;
}

sub pop {
    my $array = shift;
    my ( $length, $ret ) = $array->_pop_n( 1 );
    # warn "POP $length -- ". $ret->elems if UNIVERSAL::isa( $ret, 'Perl6::Value::List' );
    $ret = $ret->shift if UNIVERSAL::isa( $ret, 'Perl6::Value::List' );
    return $ret;
}

sub shift {
    my $array = shift;
    my ( $length, $ret ) = $array->_shift_n( 1 );
    # warn "SHIFT $length -- ". $ret->elems if UNIVERSAL::isa( $ret, 'Perl6::Value::List' );
    $ret = $ret->shift if UNIVERSAL::isa( $ret, 'Perl6::Value::List' );
    return $ret;
}

package Perl6::Container::Array::Native;

sub new {
    my $class = shift;
    # arrayref => \@INC;
    bless { @_ }, $class;
}

sub store {
    my ( $this, $key, $value ) = @_;
    my $s = Perl6::Value::numify( $key );
    $this->{arrayref}[$s] = $value->unboxed;
    return $value;
}
sub fetch {
    my ( $this, $key ) = @_;
    my $s = Perl6::Value::numify( $key );
    $this->{arrayref}[$s]
}
sub push {
    my ( $this, $value ) = @_;
    push @{$this->{arrayref}}, $value->unboxed;
    return $value;
}
sub unshift {
    my ( $this, $value ) = @_;
    unshift @{$this->{arrayref}}, $value->unboxed;
    return $value;
}
sub pop {
    my ( $this ) = @_;
    return pop @{$this->{arrayref}};
}
sub shift {
    my ( $this ) = @_;
    return shift @{$this->{arrayref}};
}
sub delete {
    my ( $this, $key ) = @_;
    my $s = Perl6::Value::numify( $key );
    my $r = delete $this->{arrayref}[$s];
}
sub clear {
    my ( $this ) = @_;
    @{$this->{arrayref}} = ();
}
sub elems {
    my ( $this ) = @_;
    scalar @{$this->{arrayref}};
}
sub items {
    my ( $this ) = @_;
    @{$this->{arrayref}};
}

1;
__END__

=head1 NAME

Perl6::Container::Array - Perl extension for Perl6 "Array" class

=head1 SYNOPSIS

  use Perl6::Container::Array;

  ...

=head1 DESCRIPTION

...


=head1 SEE ALSO

Pugs

=head1 AUTHOR

Flavio S. Glock, E<lt>fglock@gmail.com<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2005 by Flavio S. Glock

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.4 or,
at your option, any later version of Perl 5 you may have available.


=cut
