use strict;
use Test::More;
use Class::InsideOut ();
use Scalar::Util qw( refaddr reftype );

$|++; # try to keep stdout and stderr in order on Win32

sub check_version {
    my ($class, $version) = @_;
    eval { require $class and $class->VERSION($version) };
    return $@ eq q{} ? 0 : 1;
}
    
my @serializers = (
    {
        class   => "Storable",
        version => 3.04,
        freeze  => sub { Storable::freeze( shift ) },
        thaw    => sub { Storable::thaw( shift ) },
    },
);

my @classes = qw(
    t::Object::Scalar
    t::Object::Array
    t::Object::Hash
    t::Object::Animal::Jackalope
);
    
my %custom_prop_for_class = (
    "t::Object::Scalar"  => {
        age => "32" 
    },
    "t::Object::Array"   => {
        height => "72 inches"
    },
    "t::Object::Hash"    => { 
        weight => "190 lbs" 
    },
    "t::Object::Animal::Jackalope" => {
        color => "white",
        speed => "60 mph",
        points => 13,
        kills => 23,
    },
);

my $prop_count;
$prop_count++ for map { keys %$_ } values %custom_prop_for_class;

my $tests_per_serializer = ( 1 + (11 * @classes) + (2 * $prop_count) );

plan tests => @serializers * $tests_per_serializer;

#--------------------------------------------------------------------------#
# Setup test data
#--------------------------------------------------------------------------#

my %content_for_type = (
    SCALAR  => \do { my $s = 3.14159 },
    ARRAY   => [1, 1, 2, 3, 5, 8 ],
    HASH    => { 1 => 1, 2 => 4, 3 => 9, 4 => 16 },
);

my %names_for_class = (
    "t::Object::Scalar"             => "Larry",
    "t::Object::Array"              => "Moe",
    "t::Object::Hash"               => "Curly",
    "t::Object::Animal::Jackalope"  => "Fred",
);

#--------------------------------------------------------------------------#
# tests
#--------------------------------------------------------------------------#

for my $s ( @serializers ) {

    SKIP:
    {
    
        skip "$s->{class} $s->{version} required", $tests_per_serializer
            unless check_version( $s->{class}, $s->{version} );
        
        require_ok( $s->{class} );

        for my $class ( @classes ) {
            no strict 'refs';
            require_ok( $class );
            my $o;
            # create the object
            ok( $o = $class->new(),  
                "... Creating $class object"
            );
            
            # note the underlying type
            my $type;
            ok( $type = reftype($o),
                "... Object is reftype $type"
            );
            
            # set a name
            my $name = $names_for_class{ $class };
            $o->name( $name );
            is( $o->name(), $name,
                "... Setting 'name' to '$name'"
            );
            
            # set class-specific properties
            for my $prop ( keys %{ $custom_prop_for_class{ $class } } ) {;
                my $val = $custom_prop_for_class{ $class }{ $prop };
                $o->$prop( $val );
                is( $o->$prop(), $val,
                    "... Setting custom '$prop' property to $val"
                );
            }
            
            # store class-specific data in the reference
            my $data = $content_for_type{ $type };
            for ( reftype $o ) {
                /SCALAR/ && do { $$o = $$data; last };
                /ARRAY/  && do { @$o = @$data; last };
                /HASH/   && do { %$o = %$data; last };
            }
            pass( "... Loading base $type with data" );

            # freeze object
            my ( $frozen, $thawed );
            ok( $frozen = $s->{freeze}->( $o ),
                "... Freezing object"
            );

            # thaw object
            ok( $thawed = $s->{thaw}->( $frozen ),
                "... Thawing object"
            );
            isnt( refaddr $o, refaddr $thawed,
                "... Thawed object is a copy"
            );
            # check name
            is( $thawed->name(), $name,
                "... Property 'name' for thawed object is correct?"
            ) ;

            # check class-specific properties
            for my $prop ( keys %{ $custom_prop_for_class{ $class } } ) {;
                my $val = $custom_prop_for_class{ $class }{ $prop };
                is( $thawed->$prop(), $val,
                    "... Property '$prop' for thawed objects is correct?"
                );
            }
            
            # check thawed contents
            is_deeply( $thawed, $data,
                "... Thawed object contents are correct"
            );
            
            my @leaks = Class::InsideOut::_leaking_memory;
            ok( ! scalar @leaks,
                "... $class not leaking memory"
            ) or diag "Leaks in: @leaks";
        };
    }
}

