package Mud::Connection::Handle;
use strict;
use Mud::CoreTools;
use Mud::Connection;
use vars qw(@ISA %TempErr %LostErr %BadErr);
@ISA = qw(Mud::Connection);

use IO::Socket;
use UNIVERSAL;
use Mud::Obj::Event;

###

use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
use POSIX qw(:errno_h);

use constant CONNECTION_TIMEOUT => 600;
use constant READ_BLOCK_SIZE => 4096;

# temporary errors - try again later
%TempErr = map {eval($_) => $_} qw(EAGAIN EWOULDBLOCK EINTR);
# other end disconnected errors - disconnect
%LostErr = map {eval($_) => $_} qw(ENOTCONN ECONNRESET EPIPE);
# something went wrong errors - disconnect
%BadErr  = map {eval($_) => $_} qw(EBADF EFBIG EINVAL EIO EISDIR ENOSPC ERANGE ENXIO);
# for some reason MacPerl occasionally has a system call fail with $! == 0
$TempErr{0} = 'no error??';

###

=head1 Description

Mud::Connection::Handle implements IO over filehandles of any non-tied variety,
including STDIN, STDOUT, and sockets.

=head1 Methods

=item CM new(READFH[, WRITEFH])

Creates a Mud::Connection::Handle object. READFH and WRITEFH are the filehandles
used for input and output, respectively. Either may be undef, and they may be
the same handle (e.g. a socket). If WRITEFH is not specified, it is assumed to
be the same as READFH.

=cut

sub new {
  my ($class, $r, $w) = @_;
  my $self = $class->SUPER::new();
  
  $w = $r if @_ < 3;
  
  $self->{fh_r} = $r;
  $self->{fh_w} = $w;
  
  #$self->{ip} = join '.', unpack 'C4', $sock->peeraddr;
  #$self->{port} = $sock->peerport;
  
  $r->timeout(CONNECTION_TIMEOUT) if eval {$r->isa('IO::Socket')};
  $w->timeout(CONNECTION_TIMEOUT) if eval {$w->isa('IO::Socket')};

  fcntl($r, F_SETFL, fcntl($r, F_GETFL, 0) | O_NONBLOCK) or croak "shouldn't happen: unblock read failed";
  fcntl($w, F_SETFL, fcntl($w, F_GETFL, 0) | O_NONBLOCK) or croak "shouldn't happen: unblock write failed";
  
  Mud::IOManager->add($r, 'read', Mud::Obj::Event->new(target => $self, method => 'perform_read', name => 'Filehandle Read'));
  
  return $self;
}

sub DESTROY {
  my ($self) = @_;
  Mud::IOManager->remove($self->{fh_r}, 'read') if $self->{fh_r};
  Mud::IOManager->remove($self->{fh_w}, 'write') if $self->{fh_w};
  $self->SUPER::destroy;
}

=item IM perform_read()

Reads data from the socket, if any is available, and passes it to <TBA>

=cut

sub perform_read {
  my ($self) = @_;
  
  mudlog "in perform_read";
  {
    my $res = sysread $self->{fh_r}, my($tx), READ_BLOCK_SIZE;
    defined $res or $self->socket_error('read'), return;
    length $tx or $self->lost_connection('socket closed'), return;
  
    #print Data::Dumper::Dumper($self)  . "\n";
  
    $self->po_proxy->handle_input($tx);
  }
}

=item IM socket_error(WAS_DOING)

Internal method.

=cut

# socket_error: based on the value of $!, returns 0 if the error is temporary,
# or disconnects the connection and returns 1 if the error will recur on
# another IO operation.
sub socket_error {
  my ($self, $was_doing) = @_;
  
  return 0 if $TempErr{0+$!};
  $self->lost_connection(($LostErr{0+$!} ? "lost connection: $LostErr{0+$!}"
                        : $BadErr{0+$!}  ? "internal $was_doing error: $BadErr{0+$!} $!"
                                         : "unknown $was_doing error: ".(0+$!)." $!"), 1)
}

=back

=cut

1;
__END__
