package Object::Array::Plugin::Builtins;

use strict;
use warnings;

use Sub::Exporter -setup => {
  exports => [
    qw(
       size length
       element elem
       slice
       elements elems
       clear
       pop
       push
       shift
       unshift
       exists
       delete
       splice
       map
       grep
     ),
  ],
};

=head1 NAME

Object::Array::Plugin::Builtins

=head1 SYNOPSIS

See L<Object::Array>.

Provides analogues to Perl's built-in array operations.

=head1 METHODS

=head2 C<< size >>

=head2 C<< length >>

Returns the number of elements in the array.

C<< size >> and C<< length >> are synonyms.

=head2 C<< element >>

=head2 C<< elem >>

  print $array->elem(0);
  print $array->[0];

Get a single element's value.

  $array->elem(1 => "hello");
  $array->[1] = "hello";

Set a single element's value.

C<< element >> and C<< elem >> are synonyms.

=head2 C<< slice >>

  print for $array->slice([ 0, 1, 2 ]);
  print for @{$array}[0,1,2];

Get multiple values.

  $array->slice([ 0, 1, 2 ] => [ qw(a b c) ]);
  @{$array}[0,1,2] = qw(a b c);

Set multiple values.

=head2 C<< elements >>

=head2 C<< elems >>

Shortcut for all values in the array.

C<< elements >> and C<< elems >> are synonyms.

NOTE: Using methods in a for/map/etc. will not do aliasing
via $_.  Use array dereferencing if you need to do this, e.g.

  $_++ for @{$array};

=head2 C<< clear >>

Erase the array.  The following all leave the array empty:

  $array->size(0);
  $array->clear;
  @{ $array } = ();

=head2 C<< push >>

=head2 C<< pop >>

=head2 C<< shift >>

=head2 C<< unshift >>

=head2 C<< exists >>

=head2 C<< delete >>

=head2 C<< splice >>

=head2 C<< map >>

=head2 C<< grep >>

As the builtin array operations of the same names.

Note that since map and grep are called as methods, you must
use C<<sub { }>> (no bare blocks).

=cut

sub map {
  my ($self, $code) = @_;
  return $self->_array(map { $code->() } @{ $self });
}

sub grep {
  my ($self, $code) = @_;
  return $self->_array(grep { $code->() } @{ $self });
}

sub size {
  my $self = shift;
  if (@_) {
    $#{ $self->_real } = shift(@_) - 1;
  }
  return scalar @{ $self->_real };
}

*length = \*size;

sub elem {
  my $self = shift;
  unless (@_) {
    require Carp;
    Carp::croak("must specify index for element lookup");
  }

  my $idx  = shift || 0;

  if (@_) {
    $self->_real->[$idx] = shift;
  }
  return $self->_real->[$idx];
}
*element = \&elem;

sub slice {
  my $self = shift;
  my $idx  = shift;
  unless ($idx and ref($idx) eq 'ARRAY') {
    require Carp;
    Carp::croak("must specify arrayref of indices for slice");
  }

  # since tying can deal with this, might as well let it
  if (@_) {
    return $self->_array(@{ $self }[ @$idx ] = @{ +shift });
  } else {
    return $self->_array(@{ $self }[ @$idx ]);
  }
}

sub elems   { @{ shift->_real } }

*elements = \&elems;

sub clear   { @{ shift->_real } = () }

sub pop     { pop @{ shift->_real } }

sub push    { push @{ shift->_real }, @_ }

sub unshift { unshift @{ shift->_real }, @_ }

sub exists  { exists shift->_real->[shift] }

sub delete  { delete shift->_real->[shift] }

sub splice  { splice @{ shift->_real }, @_ }

# shift goes last to avoid annoying warnings
sub shift   { shift @{ shift->_real } }

1;
