package Coat::Object;

use strict;
use warnings;
use Coat::Meta;
use Carp 'confess';

# this is the mother-class of each Coat objects, it provides
# basic instance methods such as a constructor

# The default constructor
sub new {
    my ( $class, @args ) = @_;

    # create the newborn
    my $self = {};
    bless $self, $class;

    # parse and prepare the args
    my $args = $self->build_args(@args);

    # init the object
    $self->init($args);

    # done
    return $self;
}

sub build_args {
    my ($self, @args) = @_;
    my $class = ref($self);

    my $args;
    $args = {@args} if @args % 2 == 0;

    # if BUILDARGS exists, look or it and run it
    if ($self->can('BUILDARGS')) {
        foreach my $pkg (reverse Coat::Meta->linearized_isa($class)) {
            my $buildargs_sub;
            { 
                no strict 'refs'; 
                $buildargs_sub = *{$pkg."::BUILDARGS"}; 
            }
            if (defined &$buildargs_sub) {
                $args = $self->$buildargs_sub(@args);
                last;
            }
        }
    }

    # now check everything is OK with the args
    unless (defined $args) {
        if (@args == 1) {
            if (ref($args[0]) ne 'HASHREF') {
                confess "Single argument must be an HASHREF";
            }
            else {
                $args = $args[0];
            }
        }
        else {
            confess "Invalid arguments";
        }
    }
    return $args;
}

# returns the meta-class description of that instance
sub meta {
    my ($self) = @_;
    return Coat::Meta->class( ref($self) );
}

# init an instance : put default values and set values
# given at instanciation time
sub init {
    my ( $self, $attrs ) = @_;
    my $class = ref $self;

    my $class_attr = Coat::Meta->all_attributes( $class );
    
    # setting all default values
    foreach my $attr ( keys %{$class_attr} ) {
        my $meta = $class_attr->{$attr};

        confess "You cannot have lazy attribute ($attr) without specifying a default value for it" 
            if ($meta->{lazy} && !exists($meta->{default}));

        # handling default values for non-lazy slots
        if ( (! $meta->{'lazy'}) && defined $meta->{'default'} ) {

            # saving original permission and setting it to read/write
            my $is = $meta->{'is'};
            $meta->{'is'} = 'rw';
            
            # set default value
            $self->$attr( Coat::Meta->attr_default( $self, $attr) ); 

            # restoring original permissions
            $meta->{'is'} = $is;
        }
         
        # a required read-only field must have a default value or be set at
        # instanciation time
        confess "Attribute ($attr) is required"
            if ($meta->{'required'} &&
                $meta->{'is'} eq 'ro' &&
                (! exists $meta->{'default'}) && 
                (! exists $attrs->{$attr}));
    }

    # setting values given at instanciation time
    foreach my $attr ( keys %$attrs ) {
        my $is = $class_attr->{$attr}{'is'};
        
        $class_attr->{$attr}{'is'} = 'rw';
        $self->$attr( $attrs->{$attr} );
        $class_attr->{$attr}{'is'} = $is;
    }

    $self->BUILDALL($attrs);
    return $self;
}

# This is done to let us implement easily the BUILDARGS/BUILD/DEMOLISH stuff 
# It must behave the same: with inheritance in mind.
# Thanks again to the Moose team for the idea of *ALL() methods.

sub _run_for_all {
    my ($method_name, $self, $params) = @_;
    my $class = ref($self);

    return unless $self->can($method_name);

    my $sub;
    foreach my $pkg (reverse Coat::Meta->linearized_isa($class)) {
        { 
            no strict 'refs'; 
            $sub = *{$pkg."::${method_name}"}; 
        }
        $self->$sub( %$params ) if defined &$sub;
    }
}

sub BUILDALL { _run_for_all('BUILD', @_) }

sub DEMOLISHALL { _run_for_all('DEMOLISH', @_) }

sub DESTROY { goto &DEMOLISHALL }

# taken from Moose::Object
sub dump { 
    my $self = shift;
    require Data::Dumper;
    local $Data::Dumper::Maxdepth = shift if @_;
    Data::Dumper::Dumper $self;
}

# end Coat::Object
1;
__END__

=head1 NAME

Coat::Object - The mother class for each class that uses Coat

=head1 DESCRIPTION

When a class is described with Coat, each instance of that class will inherit
from Coat::Object.

This is the mother-class for each Coat-created objects, it provides a basic default
constructor and access to the meta-class.

=head1 METHODS

=head2 new

This is the default constructor, it creates a new object for your class and
calls init with the arguments given.

=head2 init

This method initialize the instance: basically, setting default values to
attributes and setting values received (passed to the "new" method).

=head2 meta

Returns the meta-calss description: attributes declared with properties.

=head1 SEE ALSO

See C<Coat>, the meta-class for Coat::Object's.

See also C<Moose>, the mother of Coat.

=head1 AUTHORS

This module was written by Alexis Sukrieh E<lt>sukria+perl@sukria.netE<gt>

Strong and helpful reviews were made by Stevan Little and 
Matt (mst) Trout ; this module wouldn't be there without their help.
Huge thank to them.

=head1 COPYRIGHT AND LICENSE

Copyright 2007 by Alexis Sukrieh.

L<http://www.sukria.net/perl/coat/>

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself. 

=cut
