#!perl
use strict;
use warnings;
use Sereal::Encoder qw(:all);
use Data::Dumper;
use File::Spec;
use Scalar::Util qw(blessed);

# These tests use an installed Decoder to do testing on horrific
# Perl data structures such as overloaded and tied structures.

use lib File::Spec->catdir(qw(t lib));
BEGIN {
    lib->import('lib')
        if !-d 't';
}

use Sereal::TestSet qw(:all);
use Test::More;

if (not have_encoder_and_decoder()) {
    plan skip_all => 'Did not find right version of decoder';
    exit 0;
}

Sereal::Decoder->import(":all");

# First, test tied hashes. Expected behaviour: We don't segfault, we don't
# throw exceptions (unless the tied hash is not iterable repeatedly),
# we serialize the tied hash as if it was a normal hash - so no trace of
# tiedness in the output.
{
    SCOPE: {
        package TiedHash;
        require Tie::Hash;
        our @ISA = qw(Tie::StdHash);
    }

    my %testhash = (
        foo => [qw(a b c)],
        baz => 123,
        dfvgbnhmjk => "345ty6ujh",
        a => undef,
    );

    my %tied_hash;
    tie %tied_hash => 'TiedHash';
    %{tied(%tied_hash)} = %testhash;
    is_deeply(\%tied_hash, \%testhash);

    my ($out, $ok, $err, $data);
    $ok = eval {$out = encode_sereal(\%tied_hash); 1};
    $err = $@ || 'Zombie error';
    ok($ok, "serializing tied hash did not die")
        or note("Error was '$err'");
    ok(defined $out, "serializing tied hash returns string");

    $ok = eval {$data = decode_sereal($out); 1;};
    $err = $@ || 'Zombie error';
    ok($ok, "deserializing tied hash did not die")
        or note("Error was '$err', data was:\n"), hobodecode($out);
    ok(defined $data, "deserializing tied hash yields defined output");
    is_deeply($data, \%testhash, "deserializing tied hash yields expected output");
}


# Now tied arrays.
{
    SCOPE: {
        package TiedArray;
        require Tie::Array;
        our @ISA = qw(Tie::StdArray);
    }

    my @testarray = (1, 2, "foo", "bar", []);
    my @tied_array;
    tie @tied_array => 'TiedArray';
    @{tied(@tied_array)} = @testarray;
    is_deeply(\@tied_array, \@testarray);

    my ($out, $ok, $err, $data);
    $ok = eval {$out = encode_sereal(\@tied_array); 1};
    $err = $@ || 'Zombie error';
    ok($ok, "serializing tied array did not die")
        or note("Error was '$err'");
    ok(defined $out, "serializing tied array returns string");

    $ok = eval {$data = decode_sereal($out); 1;};
    $err = $@ || 'Zombie error';
    ok($ok, "deserializing tied array did not die")
        or note("Error was '$err', data was:\n"), hobodecode($out);
    ok(defined $data, "deserializing tied array yields defined output");
    is_deeply($data, \@testarray, "deserializing tied array yields expected output");
}

# Now tied scalars.
{

    SCOPE: {
        package TiedScalar;
        require Tie::Scalar;
        our @ISA = qw(Tie::StdScalar);
    }

    my $testscalar = [qw(foo bar baz)];
    my $tied_scalar;
    tie $tied_scalar => 'TiedScalar';
    ${tied($tied_scalar)} = $testscalar;
    is_deeply($tied_scalar, $testscalar);

    my ($out, $ok, $err, $data);
    $ok = eval {$out = encode_sereal(\$tied_scalar); 1};
    $err = $@ || 'Zombie error';
    ok($ok, "serializing tied scalar did not die")
      or note("Error was '$err'");
    ok(defined $out, "serializing tied scalar returns string");

    $ok = eval {$data = decode_sereal($out); 1;};
    $err = $@ || 'Zombie error';
    ok($ok, "deserializing tied scalar did not die")
      or note("Error was '$err', data was:\n"), hobodecode($out);
    ok(defined $data, "deserializing tied scalar yields defined output");
    is_deeply($data, \$testscalar, "deserializing tied scalar yields expected output");
}

# Now test re-entrancy. DO NOT DO THIS AT HOME!
SCOPE: {
    my $enc = Sereal::Encoder->new;
    my $die_run = 0;
    eval {
        local $SIG{__DIE__} = sub {
            $die_run++;
            ok(defined($enc->encode("foo")), "encode does not segfault");
            $die_run++;
        };
        $enc->encode(["foo", sub{}]);
    };
    ok($die_run == 2, "__DIE__ called, encode 2 did not die ($die_run)");
}

# github Sereal/Sereal issue 7 regression test:
SCOPE: {
    {
        package # hide from PAUSE
            Blessed::Sub::With::Overload;
        use overload '""' => sub { shift->() };
        sub new { bless $_[1] => $_[0] }
    }
    {
        package # hide from PAUSE
            Blessed::Sub::With::Lazy::Overload;
        use overload '""' => sub {
            my ($self) = @_;
            return $self->[1] if defined $self->[1];
            return "OH NOES WE DON'T HAVE A SUB" unless ref $self->[0] eq 'CODE';
            return ($self->[1] = $self->[0]->());
        };
        sub new {
            bless [
                # The callback
                $_[1],
                # Cached value
                undef
            ] => $_[0]
        }
    }
    my $data;
    $data->[0] = sub {};
    $data->[1] = $data->[0];
    $data->[2] = Blessed::Sub::With::Overload->new(sub { "hello there" });
    $data->[3] = $data->[2];
    $data->[4] = Blessed::Sub::With::Overload->new(sub { \"hello there" });
    $data->[5] = $data->[4];
    my $called;
    $data->[6] = Blessed::Sub::With::Overload->new(sub { $called++; "hello there" });
    $data->[7] = $data->[6];
    $data->[8] = $data->[6];
    $data->[9] = $data->[6];
    $data->[10] = Blessed::Sub::With::Lazy::Overload->new(sub { "hello there" });
    $data->[11] = $data->[10];

    my $encode = encode_sereal($data, {stringify_unknown => 1});
    # Before 48d5cdc3dc07fd29ac7be05678a0b614244fec4f, we'd
    # die here because $data->[1] is a ref to something that doesn't exist anymore
    my $decode = decode_sereal($encode);

    is($decode->[0], $decode->[1]);
    is($decode->[2], $decode->[3]);
    is($decode->[4], $decode->[5]);
    is($decode->[6], $decode->[$_]) for 7..9;
    is($called, 4, "We'll call the sub every time, and won't re-use the initial return value");
    ok(blessed($decode->[10]), "We won't be stringifying objects");
    like($decode->[10]->[0], qr/^CODE\(.*?\)$/, "And the subroutine we have will just be stringified as usual in Perl");
    is("$decode->[10]", "OH NOES WE DON'T HAVE A SUB", "So our subroutine won't survive the roundtrip, our object is broken");
    is_deeply($decode->[10], $decode->[11], "Both the original and the reference to it are equally screwed");
}

pass("Alive at end");
done_testing();

