package Method::Cached;

use 5.008_001;
use strict;
use warnings;
use Sub::Attribute;
use Method::Cached::Manager;
use Method::Cached::KeyRule;

our $VERSION = '0.05';

sub import {
    my $class  = shift;
    my $caller = caller 0;
    return unless $class eq __PACKAGE__;
    return if $caller->isa(__PACKAGE__);
    {
        no strict 'refs';
        push @{$caller . '::ISA'}, __PACKAGE__;
    }
}

sub Cached :ATTR_SUB {
    my ($package, $sym_ref, $code_ref, $attr, $args_code) = @_;
    my $name = $package . '::' . *{$sym_ref}{NAME};
    my @args = defined $args_code ? eval(<<__EVAL__) : (); ## no critic
        package $package;
        no strict 'subs';
        no warnings;
        local \$SIG{__WARN__} = sub{ die };
        ($args_code)
__EVAL__
    Method::Cached::Manager->set_method_setting($name, $attr, @args);
    {
        no strict 'refs';
        no warnings 'redefine';
        *{$name} = sub {
            unshift @_, $name, $code_ref, wantarray;
            goto &Method::Cached::LocalWrapper::_wrapper;
        };
    }
}

{
    package #
        Method::Cached::LocalWrapper;

    sub _wrapper {
        my ($name, $code_ref, $warray) = splice @_, 0, 3;
        my $method = Method::Cached::Manager->get_method_setting($name);
        my $domain = Method::Cached::Manager->get_domain_setting($method->{domain});
        my $rule   = $method->{key_rule} || $domain->{key_rule};
        my $key    = Method::Cached::KeyRule::regularize($rule, $name, [ @_ ]);
        my $key_f  = $key . ($warray ? ':l' : ':s');
        my $cache  = Method::Cached::Manager->get_instance($domain);
        my $ret    = $cache->get($key_f);
        unless ($ret) {
            $ret = [ $warray ? $code_ref->(@_) : scalar $code_ref->(@_) ];
            $cache->set($key_f, $ret, $method->{expires} || 0);
        }
        return $warray ? @{ $ret } : $ret->[0];
    }
}

1;

__END__

=head1 NAME

Method::Cached - The return value of the method is cached to your storage

=head1 SYNOPSIS

 package Foo;
 
 use Method::Cached;

 sub cached :Cached { time . rand }
 sub no_cached      { time . rand }
 
 package main;

 my $test1 = { cached => Foo->cached, no_cached => Foo->no_cached };
 
 sleep 1; # It is preferable that time passes in this test
 
 my $test2 = { cached => Foo->cached, no_cached => Foo->no_cached };
 
 is   $test1->{cached},    $test2->{cached};
 isnt $test1->{no_cached}, $test2->{no_cached};

=head1 DESCRIPTION

Method::Cached offers the following mechanisms:

The return value of the method is stored in storage, and
the value stored when being execute it next time is returned.

=head2 SETTING OF CACHED DOMAIN

In beginning logic or the start-up script:

 use Method::Cached::Manager
     -default => { class => 'Cache::FastMmap' },
     -domains => {
         'some-namespace' => { class => 'Cache::Memcached::Fast', args => [ ... ] },
     },
 ;

For more documentation on setting of cached domain, see L<Method::Cached::Manager>.

=head2 DEFINITION OF METHODS

This function is mounting used as an attribute of the method.

=over 4

=item B<:Cached ( DOMAIN_NAME, EXPIRES, [, KEY_RULE, ...] )>

The cached rule is defined specifying the domain name.

 sub message :Cached('some-namespace', 60 * 5, LIST) { ... }

=item B<:Cached ( EXPIRES, [, KEY_RULE, ...] )>

When the domain name is omitted, the domain of default is used.

 sub message :Cached(60 * 5, LIST) { ... }

=back

=head2 RULE TO GENERATE KEY

=over 4

=item B<LIST>

=item B<HASH>

=item B<SELF_SHIFT>

=item B<PER_OBJECT>

=back

=head2 OPTIONAL, RULE TO GENERATE KEY

 use Method::Cached::KeyRule::Serialize;

=over 4

=item B<SERIALIZE>

=item B<SELF_CODED>

=back

=head1 METHODS

=over 4

=item B<default_domain ($setting)>

=item B<set_domain (%domain_settings)>

=item B<get_domain ($domain_name)>

=back

=head1 AUTHOR

Satoshi Ohkubo E<lt>s.ohkubo@gmail.comE<gt>

=head1 LICENSE

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

=cut
