
use strict;
use warnings;

use Perl6::MetaModel;

use PIL::Run::Container::Scalar;

sub mk_containers {
    map {
        my $c = PIL::Run::Container::Scalar->new;
        $c->scalar_store($_);
        $c;
    } @_;
}

role 'IArray' => {};

class 'PIL::Run::Container::Array' => {
    does => [ 'IArray' ],
    instance => {
        attrs => [ '@:slots' ],
        BUILD => sub {
            my $self = shift;
            $self->set_value('@:slots', []);
        },        
        methods => {
            'array_fetch' => sub { # evaluated in rvalue contexxt
                my $self = shift;
                @{$self->get_value('@:slots')}
            },

            'array_store' => sub { # evaluated in lvalue context as a list
                my $self = shift;
                my $repl = shift;
                @{$self->get_value('@:slots')} = @$repl;
            },

            'array_fetchKeys' => sub { # returns list of indices
                my $self = shift;
                0 .. $#{$self->get_value('@:slots')};
            },

            'array_fetchElem' => sub { # $idx -> $container # autovivification! - returns **CONTAINER**
                my $self = shift;
                my $idx = shift;
                $self->get_value('@:slots')->[$idx]; # type == Container::Scalar
            },
            
            'array_storeElem' => sub { # $idx -> $container -> ()
                my $self = shift;
                my $idx = shift;
                my $container = shift;
                $self->_extend_to_slot($idx);
                $self->get_value('@:slots')->[$idx] = $container;
            },

            'array_fetchVal' => sub { # $idx -> $value
                my $self = shift;
                my $idx = shift;
                $self->array_fetchElem($idx)->scalar_fetch;
            },
     
            'array_storeVal' => sub { # $idx -> $value -> ()
                my $self = shift;
                my $idx = shift;
                my $value = shift;
                $self->_extend_to_slot($idx);
                $self->array_fetchElem($idx)->scalar_store($value);
            },

            'array_fetchSize' => sub { # $int
                my $self = shift;
                my $idx = shift;
                my $value = shift;
                scalar @{$self->get_value('@:slots')};
            },

            'array_storeSize' => sub { # $int -> ()
                my $self = shift;
                my $size = shift;
                if ($size > (my $orig = $self->array_fetchSize)){
                    $self->array_extendSize($size - $orig);
                } else {
                    $#{$self->get_value('@:slots')} = ($size - 1);
                }
            },

            'array_extendSize' => sub { # $int -> () # +=?
                my $self = shift;
                my $size = shift;
                push @{$self->get_value('@:slots')}, mk_containers((undef) x ($size));
            },

            '_extend_to_slot' => sub {
                my $self = shift;
                my $idx = shift;
                my $size = $idx + 1;
                if ($self->array_fetchSize < $size){
                    $self->array_storeSize($size);
                }
            },

            'array_deleteElem' => sub { # $int -> (); # undef $array[$idx];
                my $self = shift;
                my $idx = shift;
                undef $self->get_value('@:slots')->[$idx];
            },

            'array_existsElem' => sub { # $int -> $bool;
                my $self = shift;
                my $idx = shift;
                exists $self->get_value('@:slots')->[$idx];
            },

            'array_clear' => sub {
                my $self = shift;
                @{$self->get_value('@:slots')} = (); # just ref = []?
            },

            'array_push' => sub { # @values -> ()
                my $self = shift;
                my $values = shift;
                push @{$self->get_value('@:slots')}, mk_containers(@$values);
            },

            'array_unshift' => sub { # @values -> ()
                my $self = shift;
                my $values = shift;
                unshift @{$self->get_value('@:slots')}, mk_containers(@$values);
            },

            'array_pop' => sub { # $val
                my $self = shift;
                (pop @{$self->get_value('@:slots')})->scalar_fetch;
            },

            'array_shift' => sub { # $val
                my $self = shift;
                (shift @{$self->get_value('@:slots')})->scalar_fetch;
            },

            'array_splice' => sub { # $idx_from -> $idx_to -> @replace_list -> @list
                my $self = shift;
                my $idx_from = shift;
                my $idx_to = shift;
                my $replacement =     shift;
                map { $_->scalar_fetch } splice(@{$self->get_value('@:slots')}, $idx_from, $idx_to, mk_containers(@$replacement));
            },
        },
    },
};

1;

