package POE::Loop::EV;

# EV.pm (libev) event loop bridge
# $Id: EV.pm 27 2008-01-29 19:42:57Z andyg $

use strict;
use POE::Loop::PerlSignals;

our $VERSION = '0.13';

=for poe_tests
sub skip_tests {
    return "EV tests require the EV module" if (
        do { eval "use EV"; $@ }
    );
    return "wheel_readwrite test disabled for kqueue"
        if EV::backend() == EV::BACKEND_KQUEUE() && $_[0] eq 'wheel_readwrite';
}

=cut

# Everything plugs into POE::Kernel.
package # hide me from PAUSE
    POE::Kernel;

use EV;

# Loop debugging
sub EV_DEBUG () { $ENV{POE_EV_DEBUG} || 0 }

# Global EV timer object
my $_watcher_timer;

# Global list of EV filehandle objects, indexed by fd number
my @fileno_watcher;

my $_async_watcher;

my $DIE_MESSAGE;

############################################################################
# Initialization, Finalization, and the Loop itself
############################################################################

sub loop_initialize {
    my $self = shift;
    
    if ( EV_DEBUG ) {
        my $methods = {
            EV::BACKEND_SELECT()  => 'select',
            EV::BACKEND_POLL()    => 'poll',
            EV::BACKEND_EPOLL()   => 'epoll',
            EV::BACKEND_KQUEUE()  => 'kqueue',
            EV::BACKEND_DEVPOLL() => 'devpoll',
            EV::BACKEND_PORT()    => 'port',
        };
        
        warn "loop_initialize, EV is using method: " . $methods->{ EV::backend() } . "\n";
    }

    # Set up the global timer object
    $_watcher_timer = EV::periodic( 0, 0, 0, \&_loop_timer_callback );
    
    # Workaround so perl signals are handled
    $_async_watcher = EV::check(sub { });
    
    $EV::DIED = \&_die_handler;
}

# Timer callback to dispatch events.
sub _loop_timer_callback {
    my $self = $poe_kernel;
    
    EV_DEBUG && warn "_loop_timer_callback, at " . time() . "\n";

    $self->_data_ev_dispatch_due();
    $self->_test_if_kernel_is_idle();

    # Transferring control back to EV; this is idle time.
}

sub loop_finalize {
    my $self = shift;
    
    EV_DEBUG && warn "loop_finalize\n";
    
    foreach my $fd ( 0 .. $#fileno_watcher ) {
        next unless defined $fileno_watcher[ $fd ];
        foreach my $mode ( EV::READ, EV::WRITE ) {
            if ( defined $fileno_watcher[ $fd ]->[ $mode ] ) {
                POE::Kernel::_warn(
                    "Mode $mode watcher for fileno $fd is defined during loop finalize"
                );
            }
        }
    }
    
    $self->loop_ignore_all_signals();
    undef $_async_watcher;
}

sub loop_attach_uidestroy {
    # does nothing, no UI
}

sub loop_do_timeslice {
    # does nothing
}

sub loop_run {
    EV_DEBUG && warn "loop_run\n";
    
    EV::run();
    
    if ( defined $DIE_MESSAGE ) {
        my $message = $DIE_MESSAGE;
        undef $DIE_MESSAGE;
        die $message;
    }
}

sub loop_halt {
    EV_DEBUG && warn "loop_halt\n";
    
    $_watcher_timer->stop();
    undef $_watcher_timer;
    
    EV::break();
}

sub _die_handler {
    EV_DEBUG && warn "_die_handler( $@ )\n";
    
    # EV doesn't let you rethrow an error here, so we have
    # to stop the loop and get the error later
    $DIE_MESSAGE = $@;
    
    # This will cause the EV::run call in loop_run to return,
    # and cause the process to die.
    EV::break();
}

############################################################################
# Timer code
############################################################################

sub loop_resume_time_watcher {
    my ( $self, $next_time ) = @_;
    ( $_watcher_timer and $next_time ) or return;
    
    EV_DEBUG && warn "loop_resume_time_watcher( $next_time, in " . ( $next_time - time() ) . " )\n";

    $_watcher_timer->set($next_time);
    $_watcher_timer->start();
}

sub loop_reset_time_watcher {
    my ( $self, $next_time ) = @_;
    ( $_watcher_timer and $next_time ) or return;
    
    EV_DEBUG && warn "loop_reset_time_watcher( $next_time, in " . ( $next_time - time() ) . " )\n";
    
    $_watcher_timer->set($next_time);
    $_watcher_timer->start();
}

sub loop_pause_time_watcher {
    $_watcher_timer or return;
    
    EV_DEBUG && warn "loop_pause_time_watcher()\n";
    
    $_watcher_timer->stop();
}

############################################################################
# Filehandle code
############################################################################

# helper function, not a method
sub _mode_to_ev {
    return EV::READ  if $_[0] == MODE_RD;
    return EV::WRITE if $_[0] == MODE_WR;

    confess "POE::Loop::EV does not support MODE_EX"
        if $_[0] == MODE_EX;

    confess "Unknown mode $_[0]";
}

sub loop_watch_filehandle {
    my ( $self, $handle, $mode ) = @_;

    my $fileno  = fileno($handle);
    my $watcher = $fileno_watcher[ $fileno ]->[ $mode ];

    if ( defined $watcher ) {
        $watcher->stop();
        undef $fileno_watcher[ $fileno ]->[ $mode ];
    }
    
    EV_DEBUG && warn "loop_watch_filehandle( $handle ($fileno), $mode )\n";

    $fileno_watcher[ $fileno ]->[ $mode ] = EV::io(
        $fileno,
        _mode_to_ev($mode),
        \&_loop_filehandle_callback,
    );
}

sub loop_ignore_filehandle {
    my ( $self, $handle, $mode ) = @_;

    my $fileno  = fileno($handle);
    my $watcher = $fileno_watcher[ $fileno ]->[ $mode ];

    return if !defined $watcher;
    
    EV_DEBUG && warn "loop_ignore_filehandle( $handle ($fileno), $mode )\n";

    $watcher->stop();
    
    undef $fileno_watcher[ $fileno ]->[ $mode ];
}

sub loop_pause_filehandle {
    my ( $self, $handle, $mode ) = @_;

    my $fileno = fileno($handle);

    $fileno_watcher[ $fileno ]->[ $mode ]->stop();
    
    EV_DEBUG && warn "loop_pause_filehandle( $handle ($fileno), $mode )\n";
}

sub loop_resume_filehandle {
    my ( $self, $handle, $mode ) = @_;

    my $fileno = fileno($handle);
    
    $fileno_watcher[ $fileno ]->[ $mode ]->start();
    
    EV_DEBUG && warn "loop_resume_filehandle( $handle ($fileno), $mode )\n";
}

sub _loop_filehandle_callback {
    my ( $watcher, $ev_mode ) = @_;
    
    EV_DEBUG && warn "_loop_filehandle_callback( " . $watcher->fh . ", $ev_mode )\n";

    my $mode = ( $ev_mode == EV::READ )
        ? MODE_RD
        : ( $ev_mode == EV::WRITE )
            ? MODE_WR
            : confess "Invalid mode occured in POE::Loop::EV IO callback: $ev_mode";

    # ->fh is actually the fileno, since that's what we called EV::io with
    $poe_kernel->_data_handle_enqueue_ready( $mode, $watcher->fh );
    
    $poe_kernel->_test_if_kernel_is_idle();
}

1;

__END__

=head1 NAME

POE::Loop::EV - a bridge that supports EV from POE

=head1 SYNOPSIS

    use EV;
    use POE;
    
    ...
    
    POE::Kernel->run();

=head1 DESCRIPTION

This class is an implementation of the abstract POE::Loop interface.
It follows POE::Loop's public interface exactly.  Therefore, please
see L<POE::Loop> for its documentation.

=head1 CAVEATS

Certain EV backends do not support polling on normal filehandles, namely
epoll and kqueue.  You should avoid using regular filehandles with select_read,
select_write, ReadWrite, etc.

See the L<libev documentation|http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#PORTABILITY_NOTES>
for more information on portability issues with different EV backends.

=head1 SEE ALSO

L<POE>, L<POE::Loop>, L<EV>

=head1 AUTHOR

Andy Grundman <andy@hybridized.org>

=head1 CONTRIBUTORS

=over

=item *

Dan Book <dbook@cpan.org>

=back

=head1 THANKS

Brandon Black, for his L<POE::Loop::Event_Lib> module.

=head1 LICENSE

POE::Loop::EV is free software; you may redistribute it and/or modify it
under the same terms as Perl itself.

=cut
