#!/usr/bin/env perl

use perl5i::latest;

use lib 't/lib';
use Test::More;
use Test::perl5i;

my @a = ( 0 .. 5 );
my @b = ( 3 .. 8 );

my $d;

is_deeply( $d = @a->diff(\@b), [ 0, 1, 2 ], 'Simple number diff' );
is_deeply( $d = @b->diff(\@a), [ 6, 7, 8 ], 'Simple number diff' );

# No arguments
is_deeply( $d = @a->diff, \@a, 'No arguments' );

# Empty array
is_deeply( $d = @a->diff([]), \@a, 'Diff with an empty array');
is_deeply( $d = []->diff, [], 'Diff an empty array' );

# Context
my @d = @a->diff(\@b);
is scalar @d, 3, "Returns array in list context";

# Dies when called with non-arrayref arguments

throws_ok { @a->diff('foo')        } qr/Arguments must be/;
throws_ok { @a->diff({ foo => 1 }) } qr/Arguments must be/;
throws_ok { []->diff(\'foo')       } qr/Arguments must be/;
throws_ok { @a->diff(undef, \@a)   } qr/Arguments must be/;

# Works with strings also
is_deeply( $d = [qw(foo bar)]->diff(['bar']), ['foo'], 'Works ok with strings' );

# Mix strings and numbers
is_deeply( $d = [qw(foo bar)]->diff(\@a), [qw(foo bar)], 'Mix strings and numbers' );

# Mix numbers and strings
is_deeply( $d =  @a->diff([qw(foo bar)]), \@a, 'Mix numbers and strings' );

# Ordering shouldn't matter in the top level array
is_deeply( $d = [ 1, 2 ]->diff([ 2, 1 ]), [] );

# ... but it matters for there down ( see [ github 96 ] )
is_deeply( $d =  [ [ 1, 2 ] ]->diff([ [ 2, 1 ] ]), [ [ 1, 2 ] ] );

# Diff more than two arrays
is_deeply( $d = @a->diff(\@b, [ 'foo' ] ), [ 0, 1, 2 ], 'Diff more than two arrays' );
is_deeply( $d = @a->diff(\@b, [ 1, 2 ]  ), [ 0 ],       'Diff more than two arrays' );

is_deeply(
    $d = [ { foo => 1 }, { foo => \2 } ]->diff( [ { foo => \2 } ] ),
    [ { foo => 1 } ],
    'Works for nested data structures',
);

# Test undef
{
    my @array = (1,2,undef,4);
    is_deeply( $d = @array->diff([1,2,4,undef]), [] );
    is_deeply( $d = @array->diff([1,2,4]), [undef] );
}

# Test REF
{
    my $ref1 = \42;
    my $ref2 = \42;
    my @array = (1,2,\$ref1,4);
    is_deeply( $d = @array->diff([4,\$ref2,2,1]), [] );

    my $ref3 = \23;
    is_deeply( $d = @array->diff([1,2,\$ref3,4]), [\$ref1] );
}

# Stress test deep comparison

my $code = sub { 'foo' };

my $foo = [
    qw( foo bar baz ),              # plain elements
    { foo => 2 },                   # hash reference
    { bar => 1 },                   # hash reference
    [                               # array reference of ...
        qw( foo bar baz ),          # plain elements and ...
        { foo => { foo => $code } } # hash reference with hash ref as value
    ]                               # with code ref as value
];

my $bar = [
    qw( foo baz ),                  # bar is missing
    { foo => 1 },                   # 1 != 2, bar =! foo
    [                               # this arrayref is identical
        qw( foo bar baz ),
        { foo => { foo => $code } }
    ],
    [ qw( foo baz ), \'gorch' ]     # this is unique to $bar
];

is_deeply( $d = $foo->diff($bar), [ 'bar', { foo => 2 }, { bar => 1 }     ], "stress test 1" );
is_deeply( $d = $bar->diff($foo), [ { foo => 1 }, [qw(foo baz), \'gorch'] ], "stress test 2" );

is_deeply( $d = [ $code ]->diff([ sub { 'bar' }]), [ $code ] );

# Test overloading
{
    package Number;
    use overload
        '0+' => sub { 42 },
        fallback => 1;

    sub new { bless {} }
}

{
    package String;
    use overload
        '""' => sub { 'foo' },
        fallback => 1;

    sub new { bless {} }
}

my $answer = Number->new;
my $string = String->new;

# Minimal testing of overloaded classes
ok( $answer == 42 );
ok( $string eq 'foo' );

is_deeply( $d = [ $answer, $string ]->diff([ 'foo', 42 ]), [] );

# Overloaded objects vs. scalars
is_deeply( $d = [ $answer, $string ]->diff([  'foo'  ]), [ $answer ] );
is_deeply( $d = [ $answer, $string ]->diff([   42    ]), [ $string ] );
is_deeply( $d = [ $answer, $string ]->diff([   42    ]), [  'foo'  ] );
is_deeply( $d = [ 42,      'foo'   ]->diff([ $answer ]), [  'foo'  ] );
is_deeply( $d = [ 42,      'foo'   ]->diff([ $string ]), [   42    ] );

# Overloaded objects vs. overloaded objects.
is_deeply( $d = [ $answer, $string ]->diff([ $string ]), [ $answer ] );
is_deeply( $d = [ $answer, $string ]->diff([ $answer ]), [ $string ] );
is_deeply( $d = [ $answer, $string ]->diff([ $answer ]), [  'foo'  ] );


# Objects vs. objects
my $object = bless {}, 'Object';

is_deeply( $d = [ $object ]->diff( [ $object ] ), [ ] );
is_deeply( $d = [ $object ]->diff( [ ] ), [ $object ] );

# Overloaded objects vs. non-overloaded objects
is_deeply( $d = [ $object ]->diff( [ $answer ] ), [ $object ] );
is_deeply( $d = [ $answer ]->diff( [ $object ] ), [ $answer ] );

done_testing();
