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

BEGIN {
	chdir 't' if -d 't';
	@INC = '../lib';
}

#
# A couple of simple classes to use as struct elements.
#
package aClass;
sub new { bless {}, shift }
sub meth { 42 }

package RecClass;
sub new { bless {}, shift }

#
# The first of our Class::Struct based objects.
#
package MyObj;
use Class::Struct;
use Class::Struct 'struct'; # test out both forms
use Class::Struct SomeClass => { SomeElem => '$' };

struct( s => '$', a => '@', h => '%', c => 'aClass' );

#
# The second Class::Struct objects:
# test the 'compile-time without package name' feature.
#
package MyOther;
use Class::Struct s => '$', a => '@', h => '%', c => 'aClass';

#
# test overriden accessors
#
package OverrideAccessor;
use Class::Struct;

{ 
 no warnings qw(Class::Struct);
 struct( 'OverrideAccessor', { count => '$' } );
}

sub count {
  my ($self,$count) = @_;

  if ( @_ >= 2 ) {
    $self->{'OverrideAccessor::count'} = $count + 9;
  }

  return $self->{'OverrideAccessor::count'};
}

#
# back to main...
#
package main;

use Test::More;

my $obj = MyObj->new;
isa_ok $obj, 'MyObj';

$obj->s('foo');
is $obj->s(), 'foo';

isa_ok $obj->a, 'ARRAY';
$obj->a(2, 'secundus');
is $obj->a(2), 'secundus';

$obj->a([4,5,6]);
is $obj->a(1), 5;

isa_ok $obj->h, 'HASH';
$obj->h('x', 10);
is $obj->h('x'), 10;

$obj->h({h=>7,r=>8,f=>9});
is $obj->h('r'), 8;

is $obj->c, undef;

$obj = MyObj->new( c => aClass->new );
isa_ok $obj->c, 'aClass';
is $obj->c->meth(), 42;


$obj = MyOther->new;
isa_ok $obj, 'MyOther';

$obj->s('foo');
is $obj->s(), 'foo';

isa_ok $obj->a, 'ARRAY';
$obj->a(2, 'secundus');
is $obj->a(2), 'secundus';

$obj->a([4,5,6]);
is $obj->a(1), 5;

isa_ok $obj->h, 'HASH';
$obj->h('x', 10);
is $obj->h('x'), 10;

$obj->h({h=>7,r=>8,f=>9});
is $obj->h('r'), 8;

is $obj->c, undef;

$obj = MyOther->new( c => aClass->new );
isa_ok $obj->c, 'aClass';
is $obj->c->meth(), 42;



my $obk = SomeClass->new();
$obk->SomeElem(123);
is $obk->SomeElem(), 123;

my $recobj = RecClass->new();
isa_ok $recobj, 'RecClass';

my $override_obj = OverrideAccessor->new( count => 3 );
is $override_obj->count, 12;

$override_obj->count( 1 );
is $override_obj->count, 10;


use Class::Struct Kapow => { z_zwap => 'Regexp', sploosh => 'MyObj' };

is eval { main->new(); }, undef,
    'No new method injected into current package';

my $obj3 = Kapow->new();

isa_ok $obj3, 'Kapow';
is $obj3->z_zwap, undef, 'No z_zwap member by default';
is $obj3->sploosh, undef, 'No sploosh member by default';
$obj3->z_zwap(qr//);
isa_ok $obj3->z_zwap, 'Regexp', 'Can set z_zwap member';
$obj3->sploosh(MyObj->new(s => 'pie'));
isa_ok $obj3->sploosh, 'MyObj',
    'Can set sploosh member to object of correct class';
is $obj3->sploosh->s, 'pie', 'Can set sploosh member to correct object';

my $obj4 = Kapow->new( z_zwap => qr//, sploosh => MyObj->new(a => ['Good']) );

isa_ok $obj4, 'Kapow';
isa_ok $obj4->z_zwap, 'Regexp', 'Initialised z_zwap member';
isa_ok $obj4->sploosh, 'MyObj', 'Initialised sploosh member';
is_deeply $obj4->sploosh->a, ['Good'], 'with correct object';

my $obj5 = Kapow->new( sploosh => { h => {perl => 'rules'} } );

isa_ok $obj5, 'Kapow';
is $obj5->z_zwap, undef, 'No z_zwap member by default';
isa_ok $obj5->sploosh, 'MyObj', 'Initialised sploosh member from hash';
is_deeply $obj5->sploosh->h, { perl => 'rules'} , 'with correct object';

is eval {
    package MyObj;
    struct( s => '$', a => '@', h => '%', c => 'aClass' );
}, undef, 'Calling struct a second time fails';

like $@, qr/^function 'new' already defined in package MyObj/,
    'fails with the expected error';

is eval { MyObj->new( a => {} ) }, undef,
    'Using a hash where an array reference is expected';
like $@, qr/^Initializer for a must be array reference/,
    'fails with the expected error';

is eval { MyObj->new( h => [] ) }, undef,
    'Using an array where a hash reference is expected';
like $@, qr/^Initializer for h must be hash reference/,
    'fails with the expected error';

is eval { Kapow->new( sploosh => { h => [perl => 'rules'] } ); }, undef,
    'Using an array where a hash reference is expected in an initialiser list';
like $@, qr/^Initializer for h must be hash reference/,
    'fails with the expected error';

is eval { Kapow->new( sploosh => [ h => {perl => 'rules'} ] ); }, undef,
    "Using an array for a member object's initialiser list";
like $@, qr/^Initializer for sploosh must be hash or MyObj reference/,
    'fails with the expected error';

is eval {
    package Crraack;
    use Class::Struct 'struct';
    struct( 'pow' => '@$%!' );
}, undef, 'Bad type fails';
like $@, qr/^'\@\$\%\!' is not a valid struct element type/,
    'with the expected error';

is eval {
    $obj3->sploosh(MyOther->new(s => 3.14));
}, undef, 'Setting member to the wrong class of object fails';
like $@, qr/^sploosh argument is wrong class/,
    'with the expected error';
is $obj3->sploosh->s, 'pie', 'Object is unchanged';

is eval {
    $obj3->sploosh(MyObj->new(s => 3.14), 'plop');
}, undef, 'Too many arguments to setter fails';
like $@, qr/^Too many args to sploosh/,
    'with the expected error';
is $obj3->sploosh->s, 'pie', 'Object is unchanged';

is eval {
    package Blurp;
    use Class::Struct 'struct';
    struct( Blurp => {}, 'Bonus!' );
}, undef, 'hash based class with extra argument fails';
like $@, qr/\Astruct usage error.*\n.*\n/,
    'with the expected confession';

is eval {
    package Zamm;
    use Class::Struct 'struct';
    struct( Zamm => [], 'Bonus!' );
}, undef, 'array based class with extra argument fails';
like $@, qr/\Astruct usage error.*\n.*\n/,
    'with the expected confession';

is eval {
    package Thwapp;
    use Class::Struct 'struct';
    struct( Thwapp => ['Bonus!'] );
}, undef, 'array based class with extra constructor argument fails';
like $@, qr/\Astruct usage error.*\n.*\n/,
    'with the expected confession';

is eval {
    package Rakkk;
    use Class::Struct 'struct';
    struct( z_zwap => 'Regexp', sploosh => 'MyObj', 'Bonus' );
}, undef, 'default array based class with extra constructor argument fails';
like $@, qr/\Astruct usage error.*\n.*\n/,
    'with the expected confession';

is eval {
    package Awk;
    use parent -norequire, 'Urkkk';
    use Class::Struct 'struct';
    struct( beer => 'foamy' );
}, undef, '@ISA is not allowed';
like $@, qr/^struct class cannot be a subclass \(\@ISA not allowed\)/,
    'with the expected error';

done_testing;