package Coro::PatchSet::Socket;

use strict;
use Coro::Socket;

our $VERSION = '0.01';

{
	package Coro::Socket;
	
	sub new {
		my ($class, %arg) = @_;
	
		$arg{Proto}     ||= 'tcp';
		$arg{LocalHost} ||= delete $arg{LocalAddr};
		$arg{PeerHost}  ||= delete $arg{PeerAddr};
		defined ($arg{Type}) or $arg{Type} = $arg{Proto} eq "tcp" ? SOCK_STREAM : SOCK_DGRAM;

		socket my $fh, PF_INET, $arg{Type}, _proto ($arg{Proto})
			or return;
	
		my $self = bless Coro::Handle->new_from_fh (
			$fh,
			timeout       => $arg{Timeout},
			forward_class => $arg{forward_class},
			partial       => $arg{partial},
		), $class
			or return;

		${*$self}{io_socket_timeout} = $arg{Timeout};
		
		$self->configure (\%arg)
	}
	
	sub configure {
		my ($self, $arg) = @_;
		
		if ($arg->{ReuseAddr}) {
			$self->setsockopt (SOL_SOCKET, SO_REUSEADDR, 1)
				or croak "setsockopt(SO_REUSEADDR): $!";
		}
		
		if ($arg->{ReusePort}) {
			$self->setsockopt (SOL_SOCKET, SO_REUSEPORT, 1)
				or croak "setsockopt(SO_REUSEPORT): $!";
		}
		
		if ($arg->{Broadcast}) {
			$self->setsockopt (SOL_SOCKET, SO_BROADCAST, 1)
				or croak "setsockopt(SO_BROADCAST): $!";
		}
		
		if ($arg->{SO_RCVBUF}) {
			$self->setsockopt (SOL_SOCKET, SO_RCVBUF, $arg->{SO_RCVBUF})
				or croak "setsockopt(SO_RCVBUF): $!";
		}
		
		if ($arg->{SO_SNDBUF}) {
			$self->setsockopt (SOL_SOCKET, SO_SNDBUF, $arg->{SO_SNDBUF})
				or croak "setsockopt(SO_SNDBUF): $!";
		}
		
		if ($arg->{LocalPort} || $arg->{LocalHost}) {
			my @sa = _sa($arg->{LocalHost} || "0.0.0.0", $arg->{LocalPort} || 0, $arg->{Proto});
			$self->bind ($sa[0])
				or croak "bind($arg->{LocalHost}:$arg->{LocalPort}): $!";
		}
		
		if ($arg->{PeerHost}) {
			my @sa = _sa ($arg->{PeerHost}, $arg->{PeerPort}, $arg->{Proto});
		
			for (@sa) {
				$! = 0;

				if ($self->connect ($_)) {
					next unless writable $self;
					$! = unpack "i", $self->getsockopt (SOL_SOCKET, SO_ERROR);
				}
		
				$! or return $self;
		
				$!{ECONNREFUSED} or $!{ENETUNREACH} or $!{ETIMEDOUT} or $!{EHOSTUNREACH}
					or last;
			}
			
			return;
		}
		
		if (exists $arg->{Listen}) {
			$self->listen ($arg->{Listen})
				or return;
		}
		
		$self
	}
}

1;

__END__

=pod

=head1 NAME

Coro::PatchSet::Socket - fix Coro::Socket as much as possible

=head1 SYNOPSIS

    use Coro::PatchSet::Socket;
    # or
    # use Coro::PatchSet 'socket';
    use Coro;
    
    async { ... }

=head1 PATCHES

=head2 timeout

In the current Coro::Socket implementation internal C<io_socket_timeout> variable is not defined. But this variable
exists in the IO::Socket::INET objects, which Coro::Socket tries to emulate. And many modules relies on the value of this
variable. One of this is LWP::UserAgent, so without this variable timeout in the LWP requests will not work. This patch
defines this variable with the value specified in the C<Timeout> constructor option.

=head2 connect

In the current Coro::Socket implementation Coro::Socket->new(PeerAddr => $a, PeerPort => $p) always returns Coro::Socket object,
even if connection was not successfull. But in fact it should return undef if fail occured. So, after this patch Coro::Socket
constructor will always return proper value.

=head1 SEE ALSO

L<Coro::PatchSet>, L<Coro::PatchSet::Handle>

=head1 COPYRIGHT

Copyright Oleg G <oleg@cpan.org>.

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

=cut
