package Anonymous::Object;
use strict;
use warnings;
use Data::Dumper;
our $VERSION = 0.01;

BEGIN {
	$Data::Dumper::Deparse = 1;
}

sub new {
	my ( $cls, %args ) = ( shift(), scalar @_ == 1 ? %{ $_[0] } : @_ );
	my $self      = bless {}, $cls;
	my %accessors = ( unique => {}, meta => { default => {}, }, object_name => { default => 'Anonymous::Object' } );
	for my $accessor ( keys %accessors ) {
		my $param
		    = defined $args{$accessor}
		    ? $args{$accessor}
		    : $accessors{$accessor}->{default};
		my $value
		    = $self->$accessor( $accessors{$accessor}->{builder}
			? $accessors{$accessor}->{builder}->( $self, $param )
			: $param );
		unless ( !$accessors{$accessor}->{required} || defined $value ) {
			die "$accessor accessor is required";
		}
	}
	return $self;
}

sub object_name {
	my ($self, $value) = @_;
	if ( defined $value ) {
		if ( ref $value ) {
			die qq{Str: invalid value $value for accessor object_name}
		}
		$self->{object_name} = $value;
	}
	return $self->{object_name};
}

sub unique {
	my ( $self, $value ) = @_;
	if ( defined $value ) {
		if ( ref $value || $value !~ m/^[-+\d]\d*$/ ) {
			die qq{Int: invalid value $value for accessor unique};
		}
		$self->{unique} = $value;
	}
	return $self->{unique};
}

sub meta {
	my ( $self, $value ) = @_;
	if ( defined $value ) {
		if ( ( ref($value) || "" ) ne "HASH" ) {
			die qq{HashRef: invalid value $value for accessor meta};
		}
		$self->{meta} = $value;
	}
	return $self->{meta};
}

sub hash_to_object {
	my ( $self, $hash ) = @_;
	if ( ( ref($hash) || "" ) ne "HASH" ) {
		$hash = defined $hash ? $hash : 'undef';
		die
		    qq{HashRef: invalid value $hash for variable \$hash in method hash_to_object};
	}

	$self->add_method(
		{   
			name => 'new',
			code => $self->add_new($hash)
		}
	);

	for my $key ( keys %{$hash} ) {
		$self->add_method(
			{   
				name => $key,
				code => qq|return \$_[0]->{$key};|
			}
		);
	}
	return $self->build;

}

sub add_new {
	my ( $self, $new ) = @_;
	if ( ( ref($new) || "" ) ne "HASH" ) {
		$new = defined $new ? $new : 'undef';
		die
		    qq{HashRef: invalid value $new for variable \$new in method add_new};
	}

	return sprintf q|return bless { %s }, __PACKAGE__;|, join q|,|,
	    map { sprintf q|%s => %s|, $_, $self->stringify_struct( $new->{$_} ) }
	    keys %{$new};

}

sub add_methods {
	my ( $self, $methods ) = @_;
	if ( !defined($methods) || ( ref($methods) || "" ) ne "ARRAY" ) {
		$methods = defined $methods ? $methods : 'undef';
		die
		    qq{ArrayRef: invalid value $methods for variable \$methods in method add_methods};
	}

	for my $method ( @{$methods} ) {
		$self->add_method($method);
	}

}

sub add_method {
	my ( $self, $method ) = @_;
	if ( ( ref($method) || "" ) ne "HASH" ) {
		$method = defined $method ? $method : 'undef';
		die
		    qq{HashRef: invalid value $method for variable \$method in method add_method};
	}

	if ( $method->{cleaerer} ) {
		$self->meta->{ q|clear_| . $method->{name} }
		    = qq|return delete \$self->{$method->{name}};|;
	}
	if ( $method->{predicate} ) {
		$self->meta->{ q|has_| . $method->{name} }
		    = qq|return exists \$self->{$method->{name}};|;
	}
	if ( $method->{code} ) {
		$self->meta->{ $method->{name} } = $method->{code};
	}

}

sub build {
	my ($self) = @_;

	if ( !$self->meta->{new} ) {
		$self->add_method(
			{   name => 'new',
				code => $self->add_new( {} )
			}
		);
	}
	my $class = sprintf q|%s::%s|, $self->{object_name}, $self->{unique}++;
	my @methods;
	for my $method ( keys %{ $self->meta } ) {
		push @methods, sprintf q|sub %s { %s }|, $method,
		    $self->meta->{$method};
	}
	eval sprintf( 'package %s; %s; 1;', $class, join "\n", @methods );
	return $class->new;
}

sub stringify_struct {
	my ( $self, $struct ) = @_;
	$struct = ref $struct ? Dumper $struct : $struct;
	return 'undefined' unless defined $struct;
    	$struct =~ s/\$VAR1 = //;
        $struct =~ s/\s*\n*\s*package Module\:\:Generate\;|use warnings\;|use strict\;//g;
        $struct =~ s/{\s*\n*/{/;
        $struct =~ s/;$//;
	return $struct;
}

1;

__END__

=head1 NAME

Anonymous::Object - Generate Anonymous Objects

=head1 VERSION

Version 0.01

=cut

=head1 SYNOPSIS

Quick summary of what the module does.
	
	use Anonymous::Object;

	my $anon = Anonymous::Object->new({
		object_name => 'Custom::Object'
	})->hash_to_object({
		a => 1,
		b => 2,
		c => 3
	});

	$anon->a; # 1
	$anon->b; # 2
	$anon->c; # 3

=head1 SUBROUTINES/METHODS

=head2 new

Instantiate a new Anonymous::Object object.

	Anonymous::Object->new

=head2 hash_to_object

call hash_to_object method. Expects param $hash to be a HashRef.

	$obj->hash_to_object($hash)

=head2 add_new

call add_new method. Expects param $new to be a HashRef.

	$obj->add_new($new)

=head2 add_methods

call add_methods method. Expects param $methods to be a ArrayRef.

	$obj->add_methods($methods)

=head2 add_method

call add_method method. Expects param $method to be a HashRef.

	$obj->add_method($method)

=head2 build

call build method. Expects no params.

	$obj->build()

=head2 stringify_struct

call stringify_struct method. Expects param $struct to be any value including undef.

	$obj->stringify_struct($struct)

=head1 ACCESSORS

=head2 object_name

get or set object_name.

	$obj->object_name;

	$obj->object_name($value);

=head2 unique

get or set unique.

	$obj->unique;

	$obj->unique($value);

=head2 meta

get or set meta.

	$obj->meta;

	$obj->meta($value);

=head1 AUTHOR

LNATION, C<< <email at lnation.org> >>

=head1 BUGS

Please report any bugs or feature requests to C<bug-anonymous::object at rt.cpan.org>, or through
the web interface at L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Anonymous-Object>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Anonymous::Object

You can also look for information at:

=over 4

=item * RT: CPAN's request tracker (report bugs here)

L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Anonymous-Object>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/Anonymous-Object>

=item * CPAN Ratings

L<https://cpanratings.perl.org/d/Anonymous-Object>

=item * Search CPAN

L<https://metacpan.org/release/Anonymous-Object>

=back

=head1 ACKNOWLEDGEMENTS

=head1 LICENSE AND COPYRIGHT

This software is Copyright (c) 2020 by LNATION.

This is free software, licensed under:

  The Artistic License 2.0 (GPL Compatible)

=cut

 
