#!/usr/bin/env perl

use strict;
use warnings;
use Test::More;
use Test::Exception;

my $pkg;

BEGIN {
    $pkg = 'Catmandu::Iterable';
    use_ok $pkg;
}
require_ok $pkg;

{

    package T::IterableWithoutGenerator;
    use Moo;

    package T::Iterable;
    use Moo;
    with $pkg;

    has data => (is => 'rw');

    sub generator {
        my ($self) = @_;
        my $data   = $self->data;
        my $n      = 0;
        sub {
            return $data->[$n++] if $n < @$data;
            return;
        };
    }

    package T::CountArgs;
    use Moo;

    sub count_args {@_ - 1}

}

throws_ok {
    Role::Tiny->apply_role_to_package('T::IterableWithoutGenerator', $pkg)
}
qr/missing generator/;

my $iter = T::Iterable->new(data => [1, 2, 3]);

is_deeply $iter->to_array, [1, 2, 3];

is $iter->count, 3;

{
    my $d = [];
    my $n = $iter->each(sub {push @$d, $_[0]});
    is_deeply $d, $iter->to_array;
    is $n,        3;
}

is_deeply $iter->tap(sub {$_[0]})->to_array, $iter->to_array;

is $iter->any(sub {$_[0] < 3}), 1;
is $iter->any(sub {$_[0] > 3}), 0;

is $iter->many(sub {$_[0] < 3}), 1;
is $iter->many(sub {$_[0] < 2}), 0;

is $iter->all(sub {$_[0] > 0}), 1;
is $iter->all(sub {$_[0] > 1}), 0;

is_deeply $iter->map(sub {$_[0] + 1})->to_array, [2, 3, 4];

is $iter->reduce(sub {my ($memo, $num) = @_; $memo + $num;}), 6;
is $iter->reduce(1, sub {my ($memo, $num) = @_; $memo + $num;}), 7;
is_deeply $iter->reduce(
    {}, sub {my ($memo, $num) = @_; $memo->{$num} = $num + 1; $memo;}
    ),
    {1 => 2, 2 => 3, 3 => 4};

is $iter->first, 1;

is_deeply $iter->rest->to_array, [2, 3];

is_deeply $iter->take(1)->to_array, [1];
is_deeply $iter->take(2)->to_array, [1, 2];

is $iter->detect(sub {$_[0] == 3}), 3;
is $iter->detect(sub {$_[0] == 4}), undef;

is_deeply $iter->select(sub {$_[0] < 1})->to_array, [];
is_deeply $iter->select(sub {$_[0] > 1})->to_array, [2, 3];
is_deeply $iter->grep (sub {$_[0] < 1})->to_array, [];
is_deeply $iter->grep (sub {$_[0] > 1})->to_array, [2, 3];

is_deeply $iter->reject(sub {$_[0] < 2})->to_array, [2, 3];
is_deeply $iter->reject(sub {$_[0] > 0})->to_array, [];

is $iter->detect(qr'[12]'), 1;
is_deeply $iter->select(qr'[12]')->to_array, [1, 2];
is_deeply $iter->grep (qr'[12]')->to_array, [1, 2];
is_deeply $iter->reject(qr'[12]')->to_array, [3];

$iter->data([{num => 1}, {num => 2}, {num => 3}]);

is_deeply $iter->detect(num => qr'[12]'), {num => 1};
is_deeply $iter->select(num => qr'[12]')->to_array, [{num => 1}, {num => 2}];
is_deeply $iter->grep (num => qr'[12]')->to_array, [{num => 1}, {num => 2}];
is_deeply $iter->reject(num => qr'[12]')->to_array, [{num => 3}];

is_deeply $iter->pluck('num')->to_array,
    $iter->map(sub {$_[0]->{num}})->to_array;

$iter->data([T::CountArgs->new]);
is_deeply $iter->invoke('count_args')->to_array, [0];
is_deeply $iter->invoke('count_args', 'arg1', 'arg2')->to_array, [2];

$iter->data([{a => {b => 'c'}}, 'd', {c => {b => 'a'}}]);
is $iter->includes({c => {a => 'b'}}), 0;
is $iter->contains({c => {a => 'b'}}), 0;
is $iter->includes({c => {b => 'a'}}), 1;
is $iter->contains({c => {b => 'a'}}), 1;

$iter->data([1 .. 10]);
is_deeply $iter->group(3)->invoke('to_array')->to_array,
    [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]];
is_deeply $iter->group(1)->invoke('to_array')->to_array,
    [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10]];
$iter->data([]);
is_deeply $iter->group(3)->invoke('to_array')->to_array, [];

$iter->data([1, 2, 3]);
is_deeply $iter->interleave->to_array, $iter->data;
is_deeply $iter->interleave(
    T::Iterable->new(data => [4, 5, 6]),
    T::Iterable->new(data => [7, 8, 9])
)->to_array, [1, 4, 7, 2, 5, 8, 3, 6, 9];
is_deeply $iter->interleave(T::Iterable->new(data => [4, 5]))->to_array,
    [1, 4, 2, 5, 3];
$iter->data([1, 2]);
is_deeply $iter->interleave(T::Iterable->new(data => [4, 5, 6]))->to_array,
    [1, 4, 2, 5, 6];

$iter->data([2, 1, '10foo', 'foo10', -1, -2]);
is $iter->min, -2;
is $iter->max, 2;

$iter->data([]);
is $iter->min, undef;
is $iter->max, undef;

$iter->data(['foo']);
is $iter->min, undef;
is $iter->max, undef;

$iter->data(['foo', 'oof']);
is $iter->min, undef;
is $iter->max, undef;

$iter->data([{n => 10}, {n => 9}, {n => 1}]);
is $iter->min(sub {shift->{n}}), 1;
is $iter->max(sub {shift->{n}}), 10;

$iter->data([{n => 10}, {n => 9}, {n => 1}]);
is_deeply $iter->stop_if(sub {shift->{n} == 9})->to_array, [{n => 10}];
is_deeply $iter->stop_if(sub {shift->{n} == 1})->to_array,
    [{n => 10}, {n => 9}];

is_deeply $iter->sorted('n')->to_array, [{n => 1}, {n => 10}, {n => 9}];

$iter->data([3, 21, 1]);

is_deeply $iter->sorted->to_array, [1, 21, 3];
is_deeply $iter->sorted(sub {$_[1] <=> $_[0]})->to_array, [21, 3, 1];

$iter->data([]);
is $iter->run, 0;

$iter->data([1, 2]);
is $iter->run, 1;

my $has_run = 0;
$iter->tap(sub {$has_run = 1})->run;
is $has_run, 1;

# external iteration
{
    $iter->data([{n => 1}, {n => 2}, {n => 3}]);
    my $iter_data = [];
    while (my $data = $iter->next) {
        push @$iter_data, $data;
    }
    is_deeply $iter->data, $iter_data;
    is $iter->next,        undef;
    $iter->rewind;
    is_deeply $iter->next, {n => 1};
}

done_testing;
