The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package main;

use Test::More;

BEGIN
{
	if ( $::MAKE_MUTABLE )
	{
		plan skip_all => 'make_mutable is VERBOTEN';
	}
	else
	{
#		plan tests => 216;
		plan tests => 214;
#		plan 'no_plan';
	}
}

our $ASYL;

{
	package Frost::Asylum;

	#	Cheap asylum...
	#
	use Moose;

	has DATA	=> ( is => 'rw', isa => 'HashRef', default => sub { {} } );

	sub _exists
	{
		my ( $self, $class, $id, $slot_name )	= @_;

		$slot_name	||= 'id';

		::IS_DEBUG and ::DEBUG ::Dump [ $self, $class, $id, $slot_name, $self->DATA ],[qw ( self class id slot_name DATA )];

		$self->DATA->{$class}			||= {};
		$self->DATA->{$class}->{$id}	||= {};

		exists $self->DATA->{$class}->{$id}->{$slot_name};
	}

	sub _silence
	{
		my ( $self, $class, $id, $slot_name, $value )	= @_;

		( defined $slot_name )					or die 'Param class missing';

		::IS_DEBUG and ::DEBUG ::Dump [ $self, $class, $id, $slot_name, $value, $self->DATA ],[qw ( self class id slot_name value DATA )];

		$self->DATA->{$class}			||= {};
		$self->DATA->{$class}->{$id}	||= {};

		$self->DATA->{$class}->{$id}->{$slot_name}	= $value;
	};

	sub _evoke
	{
		my ( $self, $class, $id, $slot_name )	= @_;

		$slot_name	||= 'id';

		::IS_DEBUG and ::DEBUG ::Dump [ $self, $class, $id, $slot_name, $self->DATA ],[qw ( self class id slot_name DATA )];

		$self->DATA->{$class}			||= {};
		$self->DATA->{$class}->{$id}	||= {};

		$self->DATA->{$class}->{$id}->{$slot_name};
	};


	no Moose;

	if ( $::MAKE_MUTABLE )	{ __PACKAGE__->meta->make_mutable();	}
	else							{ __PACKAGE__->meta->make_immutable();	}
}

{
	package My::Asylum;

	use Moose;
	extends 'Frost::Asylum';

	no Moose;

	if ( $::MAKE_MUTABLE )	{ __PACKAGE__->meta->make_mutable();	}
	else							{ __PACKAGE__->meta->make_immutable();	}
}

{
	package Existing::Class;

	use Moose;

	no Moose;

	if ( $::MAKE_MUTABLE )	{ __PACKAGE__->meta->make_mutable();	}
	else							{ __PACKAGE__->meta->make_immutable();	}
}

{
	package Bad::Bank;

	use Frost;
	use Frost::Util;

	my ( $text, $regex, $name );

	$name	= 'id';

	foreach my $feature ( qw( index derived virtual transient ) )
	{
		$text		= "Attribute $name can not be $feature";
		$regex	= qr/$text/;
		::throws_ok{ has $name			=> ( $feature => true )	} $regex, "Error: $text";
		::throws_ok{ has '+' . $name	=> ( $feature => true )	} $regex, "Error: $text [+]";
	}

	$text		= 'Illegal inherited options => \(is\)';
	$regex	= qr/$text/;
	$text		=~ s/\\//g;
	::throws_ok{ has $name			=> ( is => 'ro'	) } $regex, "Error: ro   $text";
	::throws_ok{ has $name			=> ( is => 'rw'	) } $regex, "Error: rw   $text";
	::throws_ok{ has $name			=> ( is => 'bare'	) } $regex, "Error: bare $text";
	::throws_ok{ has '+' . $name	=> ( is => 'ro'	) } $regex, "Error: ro   $text [+]";
	::throws_ok{ has '+' . $name	=> ( is => 'rw'	) } $regex, "Error: rw   $text [+]";
	::throws_ok{ has '+' . $name	=> ( is => 'bare'	) } $regex, "Error: bare $text [+]";

	$text		= 'Illegal inherited options => \(required\)';
	$regex	= qr/$text/;
	$text		=~ s/\\//g;
	::throws_ok{ has $name			=> ( required => true,	) } $regex, "Error: true  $text";
	::throws_ok{ has '+' . $name	=> ( required => true,	) } $regex, "Error: true  $text [+]";

	::throws_ok{ has $name			=> ( required => false,	) } $regex, "Error: false $text";
	::throws_ok{ has '+' . $name	=> ( required => false,	) } $regex, "Error: false $text [+]";

	foreach my $feature ( undef, '', 'uniqueid',		#	Frost::UniqueId...
		qw(
			Any
			Item
				Bool
				Undef
				Defined
					Value
					Ref
						ScalarRef
						ArrayRef
						HashRef
						CodeRef
						RegexpRef
						GlobRef
							FileHandle
						Object
		)
	)
	{
		$text		= "Attribute \\+\?$name\'s isa => '" . ( defined $feature ? $feature : 'undef' ) . "'...";
		$regex	= qr/$text/i;
		$text		=~ s/\+\?//g;
		$text		=~ s/\\//g;
		::throws_ok{ has $name			=> ( isa => $feature ) } $regex, "Error: $text";
		::throws_ok{ has '+' . $name	=> ( isa => $feature ) } $regex, "Error: $text [+]";
	}

#	foreach $name ( qw( asylum _status _dirty real_class ) )
	foreach $name ( qw( asylum _status _dirty ) )
	{
		$text		= "Attribute $name redefined";
		$regex	= qr/$text/;
		::throws_ok{ has $name			=> ( transient => true,	isa => 'My::Asylum' )	} $regex, "Error: $text";
		::throws_ok{ has '+' . $name	=> ( transient => true,	isa => 'My::Asylum' )	} $regex, "Error: $text [+]";
	}

	::throws_ok { has error_1				=> ( derived => true,	is => 'rw',	);	}
	qr/Derived attribute error_1 is read-only by default/,	'Error: virtual + rw';

	::throws_ok { has error_2				=> ( derived => true,	virtual => true,	);	}
	qr/Attribute error_2 can only be derived or virtual/,		'Error: derived + virtual';

	::throws_ok { has error_3				=> ( derived => true,	init_arg => 'error'	);	}
	qr/Derived attribute error_3 can not have an init_arg/,	'Error: derived + init_arg';

	::throws_ok { has error_4				=> ( virtual => true, is => 'bare'	);	}
	qr/Attribute error_4 must have at least a read-only accessor/,					'Error: virtual + bare';

	::throws_ok { has error_5				=> ( transient => true, is => 'bare'	);	}
	qr/Attribute error_5 must have at least a read-only accessor/,				'Error: transient + bare';

	::throws_ok { has error_6				=> ( derived => true,	index => true,	);	}
	qr/Derived attribute error_6 can not be indexed/,		'Error: derived + index';

	::throws_ok { has error_7				=> ( virtual => true,	index => true,	);	}
	qr/Virtual attribute error_7 can not be indexed/,		'Error: virtual + index';

	::throws_ok { has error_8				=> ( transient => true,	index => true,	);	}
	qr/Transient attribute error_8 can not be indexed/,		'Error: transient + index';

	::throws_ok { has error_9				=> ( index => 'foo', isa => 'Int'	);	}
	qr/I do not understand this option \(index => foo\) on attribute \(error_9\)/,				'Error: index => foo';

	::throws_ok { has error_10				=> ( index => true,		isa => 'HashRef'	);	}
	qr/Indexed attribute error_10\'s isa => 'HashRef' does not inherit from 'Num', 'Str' or 'Frost::UniqueId'/,	'Error: index + isa => HashRef';

	::throws_ok { has error_11				=> ( index => true	);	}
	qr/Indexed attribute error_11\'s isa => 'undef' does not inherit from 'Num', 'Str' or 'Frost::UniqueId'/,		'Error: index + no isa';

	::throws_ok { has error_12				=> ( index => true, isa => 'Int',	is => 'bare'	);	}
	qr/Attribute error_12 must have at least a read-only accessor/,				'Error: index + bare';
}

{
	package Foo;

	use Frost;
	use Frost::Util;

	has foo_normal			=> ( 							isa => 'Str',	is => 'rw'	);
	has foo_transient_rw	=> ( transient => true,	isa => 'Str',	is => 'rw'	);
	has foo_transient_ro	=> ( transient => true,	isa => 'Str',					);
	has foo_virtual_rw	=> ( virtual => true,	isa => 'Str',	is => 'rw'	);
	has foo_virtual_ro	=> ( virtual => true,	isa => 'Str',					);
	has foo_derived		=> ( derived => true,	isa => 'Str',					);
	has foo_derived_def	=> ( derived => true,	isa => 'Str',	default => 'FOO_DEFAULT'	);

	sub _build_foo_derived { 'FOO_BUILD' }

	no Frost;

	if ( $::MAKE_MUTABLE )	{ __PACKAGE__->meta->make_mutable();	}
	else							{ __PACKAGE__->meta->make_immutable();	}
}

{
	package Bar;

	use Moose;
	extends 'Foo';

	use Frost::Util;

	has bar_normal			=> ( 							isa => 'Str',	is => 'rw'	);
	has bar_transient_rw	=> ( transient => true,	isa => 'Str',	is => 'rw'	);
	has bar_transient_ro	=> ( transient => true,	isa => 'Str',					);
	has bar_virtual_rw	=> ( virtual => true,	isa => 'Str',	is => 'rw'	);
	has bar_virtual_ro	=> ( virtual => true,	isa => 'Str',					);
	has bar_derived		=> ( derived => true,	isa => 'Str',					);
	has bar_derived_def	=> ( derived => true,	isa => 'Str',	default => 'BAR_DEFAULT'	);

	#	no _build_bar_derived !!!

	no Moose;

	if ( $::MAKE_MUTABLE )	{ __PACKAGE__->meta->make_mutable();	}
	else							{ __PACKAGE__->meta->make_immutable();	}
}

ok( Foo->meta()->meta()->does_role ( 'Frost::Meta::Class' ),
	'apply Frost::Meta::Class        to Foo->meta()´s metaclass' );
ok( Foo->meta()->attribute_metaclass()->meta()->does_role ( 'Frost::Meta::Attribute' ),
	'apply Frost::Meta::Attribute    to Foo->meta()´s attribute metaclass' );
ok( Foo->meta()->instance_metaclass()->meta()->does_role ( 'Frost::Meta::Instance' ),
	'apply Frost::Meta::Instance     to Foo->meta()´s instance metaclass' );
ok( Foo->meta()->constructor_class()->meta()->does_role ( 'Frost::Meta::Constructor' ),
	'apply Frost::Meta::Constructor  to Foo->meta()´s constructor metaclass' );

ok( Bar->meta()->meta()->does_role ( 'Frost::Meta::Class' ),
	'apply Frost::Meta::Class        to Bar->meta()´s metaclass' );
ok( Bar->meta()->attribute_metaclass()->meta()->does_role ( 'Frost::Meta::Attribute' ),
	'apply Frost::Meta::Attribute    to Bar->meta()´s attribute metaclass' );
ok( Bar->meta()->instance_metaclass()->meta()->does_role ( 'Frost::Meta::Instance' ),
	'apply Frost::Meta::Instance     to Bar->meta()´s instance metaclass' );
ok( Bar->meta()->constructor_class()->meta()->does_role ( 'Frost::Meta::Constructor' ),
	'apply Frost::Meta::Constructor  to Bar->meta()´s constructor metaclass' );

lives_ok		{ $ASYL	= My::Asylum->new(); }	'My asylum constructed';

my ( $foo, $bar );

lives_ok		{ $foo	= Foo->new ( id => 'FOO', asylum => $ASYL );	}	'Foo->new accepts derived Asylum';
lives_ok		{ $bar	= Bar->new ( id => 'BAR', asylum => $ASYL	);	}	'Bar->new accepts derived Asylum';

diag "\nASYL is "	. ( $ASYL->meta->is_mutable	? 'mutable' : 'IMMUTABLE' );
diag 'foo  is '	. ( $foo->meta->is_mutable		? 'mutable' : 'IMMUTABLE' );
diag 'bar  is '	. ( $bar->meta->is_mutable		? 'mutable' : 'IMMUTABLE' );

isa_ok	( $foo, 'Foo', 'foo' );
ISA_NOT	( $foo, 'Frost', 'foo' );
isa_ok	( $foo, 'Frost::Locum', 'foo' );
ISA_NOT	( $foo, 'Frost::Nucleus', 'foo' );			#	importer
ISA_NOT	( $foo, 'Frost::Role::Nucleus', 'foo' );	#	role
isa_ok	( $foo, 'Moose::Object', 'foo' );

isa_ok	( $bar, 'Bar', 'bar' );
isa_ok	( $bar, 'Foo', 'bar' );
ISA_NOT	( $bar, 'Frost', 'bar' );
isa_ok	( $bar, 'Frost::Locum', 'bar' );
ISA_NOT	( $bar, 'Frost::Nucleus', 'bar' );			#	importer
ISA_NOT	( $bar, 'Frost::Role::Nucleus', 'bar' );	#	role
isa_ok	( $bar, 'Moose::Object', 'bar' );

isa_ok	( $foo->asylum, 'My::Asylum', 'foo->asylum' );
isa_ok	( $foo->asylum, 'Frost::Asylum', 'foo->asylum' );
isa_ok	( $bar->asylum, 'My::Asylum', 'bar->asylum' );
isa_ok	( $bar->asylum, 'Frost::Asylum', 'bar->asylum' );

is ( $foo->foo_derived,			'FOO_BUILD',	'got right foo->foo_derived' );
is ( $foo->foo_derived_def,	'FOO_DEFAULT',	'got right foo->foo_derived_def' );

is ( $bar->foo_derived,			'FOO_BUILD',	'got right bar->foo_derived' );
is ( $bar->foo_derived_def,	'FOO_DEFAULT',	'got right bar->foo_derived_def' );

throws_ok	{ $bar->bar_derived eq 'BAR_BUILD' }
qr/Bar does not support builder method '_build_bar_derived' for attribute 'bar_derived'/,
'No builder defined for bar->bar_derived';

is ( $bar->bar_derived_def,	'BAR_DEFAULT',	'got right bar->bar_derived_def' );

foreach my $obj ( $foo, $bar )
{
	test_attr ( $obj,	'foo_transient_rw',	'is_transient',	true	);
	test_attr ( $obj,	'foo_transient_rw',	'is_virtual',		false	);
	test_attr ( $obj,	'foo_transient_rw',	'is_derived',		false	);
	test_attr ( $obj,	'foo_transient_rw',	'is_readonly',		false	);
	test_attr ( $obj,	'foo_transient_rw',	'_is_metadata',	'rw'	);
	test_attr ( $obj,	'foo_transient_rw',	'is_lazy_build',	undef	);

	test_attr ( $obj,	'foo_transient_ro',	'is_transient',	true	);
	test_attr ( $obj,	'foo_transient_ro',	'is_virtual',		false	);
	test_attr ( $obj,	'foo_transient_ro',	'is_derived',		false	);
	test_attr ( $obj,	'foo_transient_ro',	'is_readonly',		true	);
	test_attr ( $obj,	'foo_transient_ro',	'_is_metadata',	'ro'	);
	test_attr ( $obj,	'foo_transient_ro',	'is_lazy_build',	undef	);

	test_attr ( $obj,	'foo_virtual_rw',		'is_transient',	false	);
	test_attr ( $obj,	'foo_virtual_rw',		'is_virtual',		true	);
	test_attr ( $obj,	'foo_virtual_rw',		'is_derived',		false	);
	test_attr ( $obj,	'foo_virtual_rw',		'is_readonly',		false	);
	test_attr ( $obj,	'foo_virtual_rw',		'_is_metadata',	'rw'	);
	test_attr ( $obj,	'foo_virtual_rw',		'is_lazy_build',	undef	);

	test_attr ( $obj,	'foo_virtual_ro',		'is_transient',	false	);
	test_attr ( $obj,	'foo_virtual_ro',		'is_virtual',		true	);
	test_attr ( $obj,	'foo_virtual_ro',		'is_derived',		false	);
	test_attr ( $obj,	'foo_virtual_ro',		'is_readonly',		true	);
	test_attr ( $obj,	'foo_virtual_ro',		'_is_metadata',	'ro'	);
	test_attr ( $obj,	'foo_virtual_ro',		'is_lazy_build',	undef	);

	test_attr ( $obj,	'foo_derived',			'is_transient',	false	);
	test_attr ( $obj,	'foo_derived',			'is_virtual',		true	);
	test_attr ( $obj,	'foo_derived',			'is_derived',		true	);
	test_attr ( $obj,	'foo_derived',			'is_readonly',		true	);
	test_attr ( $obj,	'foo_derived',			'_is_metadata',	'ro'	);
	test_attr ( $obj,	'foo_derived',			'is_lazy_build',	true	);

	test_attr ( $obj,	'foo_derived_def',	'is_transient',	false	);
	test_attr ( $obj,	'foo_derived_def',	'is_virtual',		true	);
	test_attr ( $obj,	'foo_derived_def',	'is_derived',		true	);
	test_attr ( $obj,	'foo_derived_def',	'is_readonly',		true	);
	test_attr ( $obj,	'foo_derived_def',	'_is_metadata',	'ro'	);
	test_attr ( $obj,	'foo_derived_def',	'is_lazy_build',	undef	);
}

{
	test_attr ( $bar,	'bar_transient_rw',	'is_transient',	true	);
	test_attr ( $bar,	'bar_transient_rw',	'is_virtual',		false	);
	test_attr ( $bar,	'bar_transient_rw',	'is_derived',		false	);
	test_attr ( $bar,	'bar_transient_rw',	'is_readonly',		false	);
	test_attr ( $bar,	'bar_transient_rw',	'_is_metadata',	'rw'	);
	test_attr ( $bar,	'bar_transient_rw',	'is_lazy_build',	undef	);

	test_attr ( $bar,	'bar_transient_ro',	'is_transient',	true	);
	test_attr ( $bar,	'bar_transient_ro',	'is_virtual',		false	);
	test_attr ( $bar,	'bar_transient_ro',	'is_derived',		false	);
	test_attr ( $bar,	'bar_transient_ro',	'is_readonly',		true	);
	test_attr ( $bar,	'bar_transient_ro',	'_is_metadata',	'ro'	);
	test_attr ( $bar,	'bar_transient_ro',	'is_lazy_build',	undef	);

	test_attr ( $bar,	'bar_virtual_rw',		'is_transient',	false	);
	test_attr ( $bar,	'bar_virtual_rw',		'is_virtual',		true	);
	test_attr ( $bar,	'bar_virtual_rw',		'is_derived',		false	);
	test_attr ( $bar,	'bar_virtual_rw',		'is_readonly',		false	);
	test_attr ( $bar,	'bar_virtual_rw',		'_is_metadata',	'rw'	);
	test_attr ( $bar,	'bar_virtual_rw',		'is_lazy_build',	undef	);

	test_attr ( $bar,	'bar_virtual_ro',		'is_transient',	false	);
	test_attr ( $bar,	'bar_virtual_ro',		'is_virtual',		true	);
	test_attr ( $bar,	'bar_virtual_ro',		'is_derived',		false	);
	test_attr ( $bar,	'bar_virtual_ro',		'is_readonly',		true	);
	test_attr ( $bar,	'bar_virtual_ro',		'_is_metadata',	'ro'	);
	test_attr ( $bar,	'bar_virtual_ro',		'is_lazy_build',	undef	);

	test_attr ( $bar,	'bar_derived',			'is_transient',	false	);
	test_attr ( $bar,	'bar_derived',			'is_virtual',		true	);
	test_attr ( $bar,	'bar_derived',			'is_derived',		true	);
	test_attr ( $bar,	'bar_derived',			'is_readonly',		true	);
	test_attr ( $bar,	'bar_derived',			'_is_metadata',	'ro'	);
	test_attr ( $bar,	'bar_derived',			'is_lazy_build',	true	);

	test_attr ( $bar,	'bar_derived_def',	'is_transient',	false	);
	test_attr ( $bar,	'bar_derived_def',	'is_virtual',		true	);
	test_attr ( $bar,	'bar_derived_def',	'is_derived',		true	);
	test_attr ( $bar,	'bar_derived_def',	'is_readonly',		true	);
	test_attr ( $bar,	'bar_derived_def',	'_is_metadata',	'ro'	);
	test_attr ( $bar,	'bar_derived_def',	'is_lazy_build',	undef	);
}

IS_DEBUG and DEBUG ::Dump [ $ASYL, $foo, $bar ], [qw ( ASYL foo bar )];

sub test_attr
{
	my ( $obj, $name, $test, $exp )	= @_;

	my $attr	= $obj->meta()->find_attribute_by_name ( $name );

	is $attr->$test, $exp, "$name $test = " . ( $exp ? $exp : 0 );
}

1;

__END__