package MIOManager;
use strict;
use vars qw(
  @ISA
  %SelectMasks
  %Notifiers
);
use MInitializable;
@ISA = qw(MInitializable);

use MCoreTools;

# This class does essentially the same thing as IO::Select, but it's designed
# to be more suited for the purpose of mpMUD. In particular:
#   * Sends MEvents instead of returning an array
#   * Manages read, write, and exception sets as one 'thing', rather
#    than requiring 3 separate IO::Select objects
#   * For efficiency and simplicity, does not use an instance, but instead
#    global variables ( we would only need one instance anyway! )

use constant MAC_SLEEP_TICKS => 5;
use constant IO_DEBUG => 0;
BEGIN {eval 'use Mac::Events qw(TickCount WaitNextEvent);' if IS_MACOS}

sub _initialize {
  %SelectMasks = (read => '', write => '', exception => '');
  %Notifiers = (read => {}, write => {}, exception => {});
}

# MIOManager->add($socket, 'read', MEvent->new(...));
sub add {
  my ($class, $fh, $type, $event) = @_;

  defined($fh) or croak "undef passed to MIOManager::add";
  defined(my $fn = fileno($fh)) or croak "bad filehandle passed to MIOManager::add";
  vec($SelectMasks{$type}, $fn, 1) = 1;
  $Notifiers{$type}{$fn} = $event;
  1;
}

# MIOManager->remove($socket, 'read');
sub remove {
  my ($class, $fh, $type) = @_;

  defined(my $fn = fileno($fh)) or croak "bad filehandle passed to MIOManager::remove";
  for ($type ? $type : keys(%SelectMasks)) {
    #print "DEBUG: removing $fn $_\n";
    vec($SelectMasks{$_}, $fn, 1) = 0;
    delete $Notifiers{$_}{$fn};
  }
  1;
}

# MIOManager->block(...);
sub block {
  my ($class, $timeout) = @_;

  print "IO: blocking for $timeout\n" if IO_DEBUG;

  my %bits;
  
  if (IS_MACOS) {
    my $done = TickCount() + $timeout * 60;
    my $result;
    while (TickCount() < $done && !$result && !$::Quit) {
      WaitNextEvent(MAC_SLEEP_TICKS);
      $result = select(
        $bits{read}  = $SelectMasks{read},
        $bits{write} = $SelectMasks{write},
        $bits{exception} = $SelectMasks{exception},
        0
      );
    }
    return 0 unless $result;
  } else {
    return 0 unless select(
      $bits{read}  = $SelectMasks{read},
      $bits{write} = $SelectMasks{write},
      $bits{exception} = $SelectMasks{exception},
      $timeout
    );
  }

  print "IO: select succeded: read:".unpack('b*', $bits{read})
                          ." write:".unpack('b*', $bits{write})
                          ." exception:".unpack('b*', $bits{exception})
                          ."\n"                                 if IO_DEBUG;
  
  foreach my $type (keys %SelectMasks) {
    foreach my $fileno (keys %{ $Notifiers{$type} }) {
      if (vec($bits{$type}, $fileno, 1)) {
        vec($bits{$type}, $fileno, 1) = 0;
        print "IO: got $type on $fileno\n" if IO_DEBUG;
    
        # test if there's still a notifier, in case a previous one called remove()
        # FIXME: ought to schedule event instead of running it directly
        # (i tried that once, but removed it - why?)
        $Notifiers{$type}{$fileno}->run if $Notifiers{$type}{$fileno};
      }
    } 
    if ($bits{$type} !~ /^\x00*$/) {
      warn "select() returned unknown filenos for $type";
    }
  }
  1;
}

1;
