package DXUDP;

=head1 NAME

DXUDP - A Mojo compatible UDP thingy

=head1 VERSION

0.01

=head1 SYNOPSIS

    use DXUDP;
    my $handle = DXUDP->new;

    $handle->on(read => sub {
        my ($handle, $data) = @_;
        ...
    });

    $handle->on(error => sub {
        warn "DXUDP: $_[1]\n";
    });

    $handle->on(finish => sub {
        my($handle, $c, $error) = @_;
        warn "Connection: $error\n" if $error;
    });

    $handle->start;
    $handle->ioloop->start unless $handle->ioloop->is_running;

=head1 DESCRIPTION

A simple Mojo compatible UDP thingy

=cut

use Mojo::Base 'Mojo::EventEmitter';
use Mojo::IOLoop;
use Scalar::Util qw(weaken);

our $VERSION = '0.04';

=head1 EVENTS

=head2 error

    $self->on(error => sub {
        my($self, $str) = @_;
    });

This event is emitted when something goes wrong: Fail to L</listen> to socket,
read from socket or other internal errors.

=head2 finish

    $self->on(finish => sub {
        my($self, $c, $error) = @_;
    });

This event is emitted when the client finish, either successfully or due to an
error. C<$error> will be an empty string on success.

=head2 read

    $self->on(read => sub {
        my($self, $data) = @_;
    });

This event is emitted when a new read request arrives from a client.

=head1 ATTRIBUTES

=head2 ioloop

Holds an instance of L<Mojo::IOLoop>.

=cut

has ioloop => sub { Mojo::IOLoop->singleton };

=head2 inactive_timeout

How long a L<connection|Mojo::TFTPd::Connection> can stay idle before
being dropped. Default is 0 (no timeout).

=cut

has inactive_timeout => 0;


=head1 METHODS

=head2 start

Starts listening to the address and port set in L</Listen>. The L</error>
event will be emitted if the server fail to start.

=cut

sub start {
    my ($self, $args) = (shift, ref $_[0] ? $_[0] : {@_});
    my $reactor = $self->ioloop->reactor;
    my $socket;

	my $host = $args->{LocalAddr} || $args->{host} || '0.0.0.0';
	my $port = $args->{LocalPort} || $args->{port} || 1234;
	
    $socket = IO::Socket::IP->new(
                  LocalAddr => $host,
                  LocalPort => $port,
                  Proto => 'udp',
              );

    if(!$socket) {
        return $self->emit(error => "Can't create listen socket: $!");
    };

    Scalar::Util::weaken($self);

    $socket->blocking(0);
    $reactor->io($socket, sub { $self->_incoming });
    $reactor->watch($socket, 1, 0); # watch read events
    $self->{socket} = $socket;

    return $self;
}

sub _incoming {
    my $self = shift;
    my $socket = $self->{socket};
    my $read = $socket->recv(my $datagram, 65534); 

    if(!defined $read) {
        $self->emit(error => "Read: $!");
    }

	$self->emit(read => $datagram);
}	


sub DEMOLISH {
    my $self = shift;
    my $reactor = eval { $self->ioloop->reactor } or return; # may be undef during global destruction

    $reactor->remove($self->{socket}) if $self->{socket};
}

=head1 AUTHOR

Svetoslav Naydenov - C<harryl@cpan.org>

Jan Henning Thorsen - C<jhthorsen@cpan.org>

=cut

1;
