package Tk::AppWindow::BaseClasses::Callback;

=head1 NAME

Tk::AppWindow::BaseClasses::Callback - providing callbacks

=cut

use strict;
use warnings;
use Carp;

use vars qw($VERSION);
$VERSION="0.02";

use Data::Compare;
use Scalar::Util qw(blessed);

=head1 SYNOPSIS

 my $cb = Tk::AppWindow::BaseClasses::Callback->new('MethodName', $owner, @options);
 my $cb = Tk::AppWindow::BaseClasses::Callback->new(sub { do whatever }, @options);
 $cb->execute(@moreoptions);
 $cb->hookBefore('some_method', $obj, @param);
 $cb->hookBefore(\&some_sub, @param);
 $cb->unhookBefore('some_method', $obj, @param);
 $cb->unhookBefore(\&some_sub, @param);
 $cb->hookAfter('some_method', $obj, @param);
 $cb->hookAfter(\&some_sub, @param);
 $cb->unhooAfter('some_method', $obj, @param);
 $cb->unhookAfter(\&some_sub, @param);

=head1 DESCRIPTION

This module provides means to create universal callbacks.

After creation it can hook and unhook other callbacks to it.
Those hooked through the B<hookBefore> method will be called before the main callback.
Those hooked through the B<hookAfter> method will be called after the main callback.
Results are passed forward through the chain.

=head1 METHODS

=over 4

=cut

=item B<new>

There are two ways to create a new callback;

 my $c = Tk::AppWindow::BaseClasses::Callback->new('MethodName', $owner, @options);

When you call B<execute> the options you pass to it will be placed after $owner and before @options

 my $c = Tk::AppWindow::BaseClasses::Callback->new(\&SomeAnonymusSub, @options);

When you call B<execute> the options you pass to it will be placed after @options

=cut

sub new {
	my $proto = shift;
	my $class = ref($proto) || $proto;
	my $self = {};

	$self->{CMD} = [];
	$self->{HOOKSAFTER} = [];
	$self->{HOOKSBEFORE} = [];

	bless ($self, $class);
	$self->{CMD} = [@_]; #if $self->Check(@_);
	return $self;
}

sub Callback {
	my ($self, $cmd, @options) = @_;
	croak 'Command not defined' unless defined $cmd;
	my @call = @$cmd;
	my $sub = shift @call;
	my @opt = ();
	unless ((ref $sub) and ($sub =~/^CODE/)) {
		my $owner = shift @call;
		my $call = $owner->can($sub);
		unless (defined $call) {
			croak "Method $call not found on object $owner";
			return undef
		}
		return &$call($owner, @call,  @options);
	} else {
		return &$sub(@call, @options);
	}
}

sub Check {
	my $self = shift;
	my $call = shift;
	unless ((ref $call) and ($call =~/^CODE/)) {
		my $owner = shift;
		unless (defined $owner) {
			carp "no owner defined";
			return 0
		}
		unless ((blessed $owner) and ($owner =~ /^\S+\=/)) {
			carp "not an object";
			return 0
		}
		unless ($owner->can($call)) {
			carp "invalid method: $call";
			return 0;
		}
	}
	return 1;
}

=item B<execute>(I<@options>)

Runs the callback and returns the result. 

=cut

sub execute {
	my $self = shift;
	my @param = @_;

	my $before = $self->{HOOKSBEFORE};
	for (@$before) {
		@param = $self->Callback($_, @param);
	}
	
	my @result = $self->Callback($self->{CMD}, @param);

	my $after = $self->{HOOKSAFTER};
	for (@$after) {
		@result = $self->Callback($_, @result);
	}
	return if @result eq 0;
	return $result[0] if @result eq 1;
	return @result
}

=item B<hookAfter>I<(@callback)>

Adds a hook to the after section. The items in I<@callback> are exactly as creating a new instance.
The callback will be called after the main callback is fed what the main callback returns as parameters.

=cut

sub hookAfter {
	my $self = shift;
	my $hk = $self->{HOOKSAFTER};
	$self->Check(@_);
	push @$hk, [@_];
}

=item B<hookBefore>(I<@callback>)

Adds a hook to the before section. The items in I<@callback> are exactly as creating a new instance.
The callback will be called before the main callback and feeds it what it returns as parameters.

=cut

sub hookBefore {
	my $self = shift;
	my $hk = $self->{HOOKSBEFORE};
	$self->Check(@_);
	push @$hk, [@_];
}


=item B<unhookAfter>I<(@callback)>

Removes a hook from the after section. The items in I<@callback> are exactly as when adding the hook.
If multiple identical items are present it removes them alls.

=cut

sub unhookAfter {
	my $self = shift;
	my $hook = [ @_ ];
	my $found = 0;

	my $after = $self->{HOOKSAFTER};
	my @na = ();
	for (@$after) {
		unless (Compare($_, $hook)) {
			push @na, $_;
		} else {
			$found = 1;
# 			last;
		}
	}
	$self->{HOOKSAFTER } = \@na;
	carp "Hook not found" unless $found;
}


=item B<unhookBefore>I<(@callback)>

Removes a hook from the before section. The items in I<@callback> are exactly as when adding the hook.
If multiple identical items are present it removes them all.

=cut

sub unhookBefore {
	my $self = shift;
	my $hook = [ @_ ];
	my $found = 0;

	my $before = $self->{HOOKSBEFORE};
	my @nb = ();
	for (@$before) {
		unless (Compare($_, $hook)) {
			push @nb, $_;
		} else {
			$found = 1;
# 			last;
		}
	}
	$self->{HOOKSBEFORE } = \@nb;
	carp "Hook not found" unless $found;
}

=back

=head1 AUTHOR

Hans Jeuken (hanje at cpan dot org)

=head1 BUGS

Unknown. If you find any, please contact the author.

=cut

1;
__END__