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

use v5.16;
use warnings;

use Sub::Name ();

use mop::internals::util;

our $VERSION   = '0.01';
our $AUTHORITY = 'cpan:STEVAN';

use parent 'mop::object', 'mop::internals::observable';

mop::internals::util::init_attribute_storage(my %name);
mop::internals::util::init_attribute_storage(my %version);
mop::internals::util::init_attribute_storage(my %authority);

mop::internals::util::init_attribute_storage(my %roles);
mop::internals::util::init_attribute_storage(my %attributes);
mop::internals::util::init_attribute_storage(my %methods);
mop::internals::util::init_attribute_storage(my %required_methods);

# temporary, for bootstrapping
sub new {
    my $class = shift;
    my %args  = @_;

    my $self = $class->SUPER::new( @_ );

    $name{ $self }       = \($args{'name'});
    $version{ $self }    = \($args{'version'});
    $authority{ $self }  = \($args{'authority'});

    $roles{ $self }            = \($args{'roles'} || []);
    $attributes{ $self }       = \({});
    $methods{ $self }          = \({});
    $required_methods{ $self } = \({});

    $self;
}

sub BUILD {
    my $self = shift;

    mop::internals::util::install_meta($self);

    if (my @nometa = grep { !mop::meta($_) } @{ $self->roles }) {
        die "No metaclass found for these roles: @nometa";
    }
}

sub clone {
    my $self = shift;
    my (%args) = @_;

    die "You must specify a name when cloning a metaclass"
        unless $args{name};

    my $methods = $self->method_map;
    $args{methods} //= {
        map { $_ => $methods->{$_}->clone } keys %$methods
    };

    my $attributes = $self->attribute_map;
    $args{attributes} //= {
        map { $_ => $attributes->{$_}->clone } keys %$attributes
    };

    my $clone = $self->SUPER::clone(%args);

    for my $method (keys %{ $args{methods} }) {
        $clone->get_method($method)->set_associated_meta($clone);
    }

    for my $attribute (keys %{ $args{attributes} }) {
        $clone->get_attribute($attribute)->set_associated_meta($clone);
    }

    return $clone;
}

# identity

sub name       { ${ $name{ $_[0] } // \undef } }
sub version    { ${ $version{ $_[0] } // \undef } }
sub authority  { ${ $authority{ $_[0] } // \undef } }

# roles

sub roles { ${ $roles{ $_[0] } } }

sub add_role {
    my ($self, $role) = @_;
    push @{ $self->roles } => $role;
}

sub does_role {
    my ($self, $name) = @_;
    foreach my $role ( @{ $self->roles } ) {
        return 1 if $role->name eq $name
                 || $role->does_role( $name );
    }
    return 0;
}

# attributes

sub attribute_class { 'mop::attribute' }

sub attribute_map { ${ $attributes{ $_[0] } } }

sub attributes { values %{ $_[0]->attribute_map } }

sub add_attribute {
    my ($self, $attr) = @_;
    $self->attribute_map->{ $attr->name } = $attr;
    $attr->set_associated_meta($self);
}

sub get_attribute {
    my ($self, $name) = @_;
    $self->attribute_map->{ $name }
}

sub has_attribute {
    my ($self, $name) = @_;
    exists $self->attribute_map->{ $name };
}

sub remove_attribute {
    my ($self, $name) = @_;
    delete $self->attribute_map->{ $name };
}

# methods

sub method_class { 'mop::method' }

sub method_map { ${ $methods{ $_[0] } } }

sub methods { values %{ $_[0]->method_map } }

sub add_method {
    my ($self, $method) = @_;
    $self->method_map->{ $method->name } = $method;
    $method->set_associated_meta($self);
    $self->remove_required_method($method->name);
}

sub get_method {
    my ($self, $name) = @_;
    $self->method_map->{ $name }
}

sub has_method {
    my ($self, $name) = @_;
    exists $self->method_map->{ $name };
}

sub remove_method {
    my ($self, $name) = @_;
    delete $self->method_map->{ $name };
}

# required methods

sub required_method_map { ${ $required_methods{ $_[0] } } }

sub required_methods { keys %{ $_[0]->required_method_map } }

sub add_required_method {
    my ($self, $name) = @_;
    $self->required_method_map->{ $name } = 1;
}

sub remove_required_method {
    my ($self, $name) = @_;
    delete $self->required_method_map->{ $name };
}

sub requires_method {
    my ($self, $name) = @_;
    defined $self->required_method_map->{ $name };
}

# events

sub FINALIZE {
    my $self = shift;

    mop::internals::util::apply_all_roles($self, @{ $self->roles })
        if @{ $self->roles };

    # XXX gross
    if ($self->isa('mop::class')) {
        die 'Required method(s) [' . (join ', ' => $self->required_methods)
            . '] are not allowed in ' . $self->name
            . ' unless class is declared abstract'
            if $self->required_methods && not $self->is_abstract;
    }

    $self->fire('before:FINALIZE');

    {
        no strict 'refs';
        *{ $self->name . '::VERSION' } = \$self->version;
        @{ $self->name . '::ISA' } = ($self->superclass)
            if $self->isa('mop::class') && defined $self->superclass;
    }

    for my $method ($self->methods) {
        # XXX
        if ($self->isa('mop::class')) {
            my @super_methods = (
                map { $_ ? $_->get_method($method->name) : undef }
                map { mop::meta($_) }
                @{ mro::get_linear_isa($self->name) }
            );
            shift @super_methods;
            @super_methods = grep { defined } @super_methods;

            if (my $super = $super_methods[0]) {
                mop::apply_metaclass($method, $super);
            }
        }

        my $name = $self->name . '::' . $method->name;
        my $body = ref($method) eq 'mop::method' && !$method->has_events
            ? $method->body
            : sub { $method->execute(shift, \@_) };
        no strict 'refs';
        no warnings 'redefine';
        *$name = Sub::Name::subname $name => $body;
    }

    $self->fire('after:FINALIZE');
}

our $METACLASS;

sub __INIT_METACLASS__ {
    return $METACLASS if defined $METACLASS;
    require mop::class;
    $METACLASS = mop::class->new(
        name       => 'mop::role',
        version    => $VERSION,
        authority  => $AUTHORITY,
        superclass => 'mop::object'
    );

    $METACLASS->add_attribute(mop::attribute->new(
        name    => '$!name',
        storage => \%name,
        default => \(sub { die "name is required when creating a role or class" }),
    ));

    $METACLASS->add_attribute(mop::attribute->new(
        name    => '$!version',
        storage => \%version
    ));

    $METACLASS->add_attribute(mop::attribute->new(
        name    => '$!authority',
        storage => \%authority
    ));

    $METACLASS->add_attribute(mop::attribute->new(
        name    => '$!roles',
        storage => \%roles,
        default => \sub { [] },
    ));

    $METACLASS->add_attribute(mop::attribute->new(
        name    => '$!attributes',
        storage => \%attributes,
        default => \sub { {} },
    ));

    $METACLASS->add_attribute(mop::attribute->new(
        name    => '$!methods',
        storage => \%methods,
        default => \sub { {} },
    ));

    $METACLASS->add_attribute(mop::attribute->new(
        name    => '$!required_methods',
        storage => \%required_methods,
        default => \sub { {} },
    ));

    $METACLASS->add_method( mop::method->new( name => 'BUILD', body => \&BUILD ) );

    $METACLASS->add_method( mop::method->new( name => 'clone', body => \&clone ) );

    $METACLASS->add_method( mop::method->new( name => 'name',       body => \&name       ) );
    $METACLASS->add_method( mop::method->new( name => 'version',    body => \&version    ) );
    $METACLASS->add_method( mop::method->new( name => 'authority',  body => \&authority  ) );

    $METACLASS->add_method( mop::method->new( name => 'roles',     body => \&roles     ) );
    $METACLASS->add_method( mop::method->new( name => 'add_role',  body => \&add_role  ) );
    $METACLASS->add_method( mop::method->new( name => 'does_role', body => \&does_role ) );

    $METACLASS->add_method( mop::method->new( name => 'attribute_class', body => \&attribute_class ) );
    $METACLASS->add_method( mop::method->new( name => 'attribute_map',   body => \&attribute_map   ) );
    $METACLASS->add_method( mop::method->new( name => 'attributes',      body => \&attributes      ) );
    $METACLASS->add_method( mop::method->new( name => 'get_attribute',   body => \&get_attribute   ) );
    $METACLASS->add_method( mop::method->new( name => 'add_attribute',   body => \&add_attribute   ) );
    $METACLASS->add_method( mop::method->new( name => 'has_attribute',   body => \&has_attribute   ) );
    $METACLASS->add_method( mop::method->new( name => 'remove_attribute', body => \&remove_attribute ) );

    $METACLASS->add_method( mop::method->new( name => 'method_class',  body => \&method_class  ) );
    $METACLASS->add_method( mop::method->new( name => 'method_map',    body => \&method_map    ) );
    $METACLASS->add_method( mop::method->new( name => 'methods',       body => \&methods       ) );
    $METACLASS->add_method( mop::method->new( name => 'get_method',    body => \&get_method    ) );
    $METACLASS->add_method( mop::method->new( name => 'add_method',    body => \&add_method    ) );
    $METACLASS->add_method( mop::method->new( name => 'has_method',    body => \&has_method    ) );
    $METACLASS->add_method( mop::method->new( name => 'remove_method', body => \&remove_method ) );

    $METACLASS->add_method( mop::method->new( name => 'required_methods',    body => \&required_methods    ) );
    $METACLASS->add_method( mop::method->new( name => 'required_method_map', body => \&required_method_map ) );
    $METACLASS->add_method( mop::method->new( name => 'add_required_method', body => \&add_required_method ) );
    $METACLASS->add_method( mop::method->new( name => 'remove_required_method', body => \&remove_required_method ) );
    $METACLASS->add_method( mop::method->new( name => 'requires_method',     body => \&requires_method     ) );


    $METACLASS->add_method( mop::method->new( name => 'FINALIZE', body => \&FINALIZE ) );

    $METACLASS;
}

1;

__END__

=pod

=head1 NAME

mop::role - A meta-object to represent roles

=head1 DESCRIPTION

TODO

=head1 METHODS

=over 4

=item C<BUILD>

=item C<clone(%overrides)>

=item C<name>

=item C<version>

=item C<authority>

=item C<roles>

=item C<add_role($role)>

=item C<does_role($role_name)>

=item C<attribute_class>

=item C<attribute_map>

=item C<attributes>

=item C<get_attribute($name)>

=item C<add_attribute($attribute)>

=item C<has_attribute($name)>

=item C<remove_attribute($name)>

=item C<method_class>

=item C<method_map>

=item C<methods>

=item C<get_method($name)>

=item C<add_method($method)>

=item C<has_method($name)>

=item C<remove_method($name)>

=item C<required_methods>

=item C<required_method_map>

=item C<add_required_method($name)>

=item C<remove_required_method($name)>

=item C<requires_method($name)>

=item C<FINALIZE>

=back

=head1 SEE ALSO

=head2 L<Role Details|mop::manual::details::roles>

=head1 BUGS

Since this module is still under development we would prefer to not
use the RT bug queue and instead use the built in issue tracker on
L<Github|http://www.github.com>.

=head2 L<Git Repository|https://github.com/stevan/p5-mop-redux>

=head2 L<Issue Tracker|https://github.com/stevan/p5-mop-redux/issues>

=head1 AUTHOR

Stevan Little <stevan.little@iinteractive.com>

Jesse Luehrs <doy@tozt.net>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2013 by Infinity Interactive.

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

=cut