The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

use warnings;
use strict;
use utf8;
use open qw(:std :utf8);
use lib qw(lib ../lib);

use Test::More tests    => 53;
use Encode qw(decode encode);


BEGIN {
    # Подготовка объекта тестирования для работы с utf8
    my $builder = Test::More->builder;
    binmode $builder->output,         ":utf8";
    binmode $builder->failure_output, ":utf8";
    binmode $builder->todo_output,    ":utf8";

    use_ok 'DR::Tarantool::Iterator';
}

use constant MODEL => 'DR::Tarantool::Iterator';

is eval { MODEL->new }, undef, 'empty constructor';
like $@, qr{usage:}, 'error message';

my $iter = MODEL->new([1, 2, 3]);
ok $iter, 'constructor';
isa_ok $iter => MODEL;
is $iter->count, 3, '$iter->count';

is $iter->item(0), 1, '$iter->item(0)';
is $iter->item(1), 2, '$iter->item(1)';
is $iter->item(2), 3, '$iter->item(2)';

is $iter->item(-1), 3, '$iter->item(-1)';
is $iter->item(-2), 2, '$iter->item(-2)';
is $iter->item(-3), 1, '$iter->item(-3)';

is eval { $iter->item(3) }, undef, '$iter->item(3) (out of bound)';
like $@, qr{wrong item number: 3}, 'error message';
is eval { $iter->item(-4) }, undef, '$iter->item(-4) (out of bound)';
like $@, qr{wrong item number: -4}, 'error message';
is eval { $iter->item('abc') }, undef, '$iter->item("abc")';
like $@, qr{wrong item number format: abc}, 'error message';
is eval { $iter->item(undef) }, undef, '$iter->item(undef)';
like $@, qr{wrong item number format: undef}, 'error message';

my @res;
while(my $o = $iter->next) { push @res => $o }
is_deeply \@res, [ 1, 2, 3 ], '$iter->next';

is $iter->next, 1, '$iter->next (first)';
is $iter->reset, 0, '$iter->reset returns old iterator position';

@res = ();
while(my $o = $iter->next) { push @res => $o }
is_deeply \@res, [ 1, 2, 3 ], '$iter->next';

$iter->reset;
is_deeply scalar $iter->all, [ 1, 2, 3 ], '$iter->all';
$iter->next;
is_deeply scalar $iter->all, [ 1, 2, 3 ], '$iter->all (after next)';

@res = ();
while(my $o = $iter->next) { push @res => $o }
is_deeply \@res, [ 2, 3 ], '$iter->next after $iter->all';

is_deeply scalar $iter->all(sub { $_[0] + 1 }), [ 2, 3, 4 ],
    '$iter->all(sub { .. })';

is_deeply scalar $iter->all(sub { $_[0] + 1 }, sub { $_[0] + 2 }),
    [ [ 2, 3 ], [ 3, 4 ],  [ 4, 5 ] ],
    '$iter->all(sub { ... }, sub { ... })';

$iter->item_class('Test::Iterator::Class');
$iter->item_constructor('constructor');

my $item = $iter->item(2);
isa_ok $item => 'Test::Iterator::Class';
is eval { $item->value }, 3, '$item->value';

$iter->reset;
$iter->item_constructor(undef);
$item = $iter->next;
isa_ok $item => 'Test::Iterator::Class';
is $$item, 1, 'blessed (not constructed)';

$item = $iter->next;
isa_ok $item => 'Test::Iterator::Class';
is $$item, 2, 'blessed (not constructed)';

$iter->item_class(undef);
$item = $iter->next;
is $item, 3, 'unblessed item';


$iter = MODEL->new([3, 4, 5],
    item_class => [ 'Test::Iterator::Class', 'constructor' ]
);

isa_ok $iter->next, 'Test::Iterator::Class';
is eval { $iter->next->value }, 4, '$iter->item(1)->value';

$iter = MODEL->new([ 5, 6, 7 ],
    item_class => 'Test::Iterator::Class',
    item_constructor => 'constructor'
);

isa_ok $iter->next, 'Test::Iterator::Class';
is eval { $iter->next->value }, 6, '$iter->item(1)->value';

$iter = MODEL->new(
    [ 8, 9, 10 ],
    item_class => 'Test::Iterator::Class',
    data => { 1 => [ 2, 3] }
);
isa_ok $iter->next, 'Test::Iterator::Class';
is eval { ${ $iter->next } }, 9, '$iter->item(1) usually blessed';
is_deeply $iter->data, { 1 => [ 2, 3] }, '$iter->data (get)';
is_deeply $iter->data([ 4, { 5 => 6} ]), [ 4, { 5 => 6 } ], '$iter->data (set)';
is_deeply $iter->data, [ 4, { 5 => 6 } ], '$iter->data (get)';


$iter = MODEL->new([3, 2, 1, 102, 0, -10]);
my $iter2 = $iter->clone(1);
my $iter3 = $iter->clone;
$iter->raw_sort(sub { $_[0] <=> $_[1] });
is_deeply [ $iter->all ], [ -10, 0, 1, 2, 3, 102 ], 'raw_sort';
is_deeply [ $iter3->all ], [ $iter->all ], '->clone(0)->raw_sort';
is_deeply [ $iter2->all ], [ 3, 2, 1, 102, 0, -10 ], '->clone(1)->raw_sort';

$iter->item_class('Test::Iterator::Class', 'constructor');
$iter->sort(sub { $_[1]->value <=> $_[0]->value });
$iter->item_class(undef, undef);
is_deeply scalar $iter->all, [ 102, 3, 2, 1, 0, -10 ], '->sort';

$iter2 = $iter->grep(sub { $_[0] > 2 });
is_deeply scalar $iter2->all, [ 102, 3 ], '->grep';
$iter2 = $iter->grep(sub { $_[0] > 200 });
is_deeply scalar $iter2->all, [  ], '->grep';
$iter->item_class('Test::Iterator::Class', 'constructor');
$iter->data(\123);
$iter2 = $iter->grep(sub { $_[0]->value < 10 });
$iter3 = $iter->raw_grep(sub { $_[0] < 10 });
$iter2->item_class(undef, undef);
$iter3->item_class(undef, undef);
is_deeply scalar $iter2->all, [ 3, 2, 1, 0, -10 ], '->grep';
is_deeply scalar $iter2->all, scalar $iter3->all, 'raw_grep';

package Test::Iterator::Class;
use Test::More;

sub constructor {
    my ($class, $v) = @_;
    return bless { value => $v } => ref($class) || $class;
}

sub value {
    my ($self) = @_;
    return $self->{value};
}