#line 1
package Class::Trigger;

use strict;
use vars qw($VERSION);
$VERSION = "0.13";

use Carp ();

my (%Triggers, %TriggerPoints);
my %Fetch_All_Triggers_Cache;

sub import {
    my $class = shift;
    my $pkg = caller(0);

    $TriggerPoints{$pkg} = { map { $_ => 1 } @_ } if @_;

    # export mixin methods
    no strict 'refs';
    my @methods = qw(add_trigger call_trigger last_trigger_results);
    *{"$pkg\::$_"} = \&{$_} for @methods;
}

sub add_trigger {
    my $proto = shift;

    my $triggers = __fetch_triggers($proto);

    my %params = @_;
    my @values = values %params;
    if (@_ > 2 && (grep { ref && ref eq 'CODE' } @values) == @values) {
        Carp::croak "mutiple trigger registration in one add_trigger() call is deprecated.";
    }

    if ($#_ == 1 && ref($_[1]) eq 'CODE') {
        @_ = (name => $_[0], callback => $_[1]);
    }

    my %args = ( name => undef, callback => undef, abortable => undef, @_ );
    my $when = $args{'name'};
    my $code = $args{'callback'};
    my $abortable = $args{'abortable'};
    __validate_triggerpoint( $proto, $when );
    Carp::croak('add_trigger() needs coderef') unless ref($code) eq 'CODE';
    push @{ $triggers->{$when} }, [ $code, $abortable ];

    # Clear the cache when class triggers are added.  Because triggers are 
    # inherited adding a trigger to one class may effect others.  Simplest
    # thing to do is to clear the whole thing.
    %Fetch_All_Triggers_Cache = () unless ref $proto;

    1;
}


sub last_trigger_results {
    my $self = shift;
    my $result_store = ref($self) ? $self : ${Class::Trigger::_trigger_results}->{$self};
    return $result_store->{'_class_trigger_results'};
}

sub call_trigger {
    my $self = shift;
    my $when = shift;

    my @return;

    my $result_store = ref($self) ? $self : ${Class::Trigger::_trigger_results}->{$self};

    $result_store->{'_class_trigger_results'} = [];

    if (my @triggers = __fetch_all_triggers($self, $when)) { # any triggers?
        for my $trigger (@triggers) {
            my @return = $trigger->[0]->($self, @_);
            push @{$result_store->{'_class_trigger_results'}}, \@return;
            return undef if ($trigger->[1] and not $return[0]); # only abort on false values.
        }
    }
    else {
        # if validation is enabled we can only add valid trigger points
        # so we only need to check in call_trigger() if there's no
        # trigger with the requested name.
        __validate_triggerpoint($self, $when);
    }

    return scalar @{$result_store->{'_class_trigger_results'}};
}

sub __fetch_all_triggers {
    my ($obj, $when, $list, $order) = @_;
    my $class = ref $obj || $obj;
    my $return;
    my $when_key = defined $when ? $when : '';
    
    return __cached_triggers($obj, $when)
        if $Fetch_All_Triggers_Cache{$class}{$when_key};
    
    unless ($list) {
        # Absence of the $list parameter conditions the creation of
        # the unrolled list of triggers. These keep track of the unique
        # set of triggers being collected for each class and the order
        # in which to return them (based on hierarchy; base class
        # triggers are returned ahead of descendant class triggers).
        $list = {};
        $order = [];
        $return = 1;
    }
    no strict 'refs';
    my @classes = @{$class . '::ISA'};
    push @classes, $class;
    foreach my $c (@classes) {
        next if $list->{$c};
        if (UNIVERSAL::can($c, 'call_trigger')) {
            $list->{$c} = [];
            __fetch_all_triggers($c, $when, $list, $order)
                unless $c eq $class;
            if (defined $when && $Triggers{$c}{$when}) {
                push @$order, $c;
                $list->{$c} = $Triggers{$c}{$when};
            }
        }
    }
    if ($return) {
        my @triggers;
        foreach my $class (@$order) {
            push @triggers, @{ $list->{$class} };
        }

        # Only cache the class triggers, object triggers would
        # necessitate a much larger cache and they're cheap to
        # get anyway.
        $Fetch_All_Triggers_Cache{$class}{$when_key} = \@triggers;

        return __cached_triggers($obj, $when);
    }
}


sub __cached_triggers {
    my($proto, $when) = @_;
    my $class = ref $proto || $proto;
    
    return @{ $Fetch_All_Triggers_Cache{$class}{$when || ''} },
           @{ __object_triggers($proto, $when) };
}


sub __object_triggers {
    my($obj, $when) = @_;
    
    return [] unless ref $obj && defined $when;
    return $obj->{__triggers}{$when} || [];
}


sub __validate_triggerpoint {
    return unless my $points = $TriggerPoints{ref $_[0] || $_[0]};
    my ($self, $when) = @_;
    Carp::croak("$when is not valid triggerpoint for ".(ref($self) ? ref($self) : $self))
        unless $points->{$when};
}

sub __fetch_triggers {
    my ($obj, $proto) = @_;
    # check object based triggers first
    return ref $obj ? $obj->{__triggers} ||= {} : $Triggers{$obj} ||= {};
}

1;
__END__

#line 380

