Revision 2 (by moose, 2006/07/21 02:14:30) many additions
package IRC::Multiplex;

sub new {
	my $class = shift;
	my %parms = @_;
	my $self = {%parms};
	bless $self,$class;

	$self->{mux} = new IO::Multiplex;

	return $self;
}

sub add {
	my $self = shift;

	$self->{write_time} = gettimeofday();
	$mux->add($self->{fd});
	$mux->set_callback_object($this);
}

sub remove {
	my $self = shift;

	$mux->remove($self->{fd});
}

sub listen {
	my $self = shift;

	$mux->listen($fd);
	$mux->set_callback_object($this);
}

sub set_timeout {
	my $self = shift;
	my $time = shift;

	$mux->set_timeout($fd, $time);
}

sub write {
	my $self = shift;
	my $data = shift;

	my $now = gettimeofday();
	if($self->{write_time} + $self->{send_interval} < $now) {
		if(scalar @{$self->{write_buffer}}) {
			push @{$self->{write_buffer}}, $data;
			$data = shift @{$self->{write_buffer}};
		}

		$mux->write($self->{fd}, $data);
	}
	else {
		push @{$self->{write_buffer}}, $fd, $data;
	}
}

sub input {
	my $self  = shift;
	my $input = shift;
	my @lines = ();

	push @{$self->{buffer}}, $$input;
	$$input = '';

	$self->input() if $self->can('input');
}

package IRC;
use base Exporter;
use IRC::Config;
use IO::Multiplex;
use Time::HiRes;

=pod

MOVE the event handling crap here, and make "register_event" look more like Qt's connect:

	IRC::register_event($thing, EVENT, $dest, 'method')
	$thing->register_event($obj, EVENT, $dest, 'method')
	$thing->register_event(EVENT, $dest, 'method')

Need a Timer event class. tied to the mux_timeout maybe
ie:
	$event->trigger_in(5) # seconds
	$event->wait_for_me(); # waits till this event is triggered, does not block entire app
	$obj->register_timer(NOTICE, $dest, &meth, $interval, $times); # $times==undef/0 means inf

------------

[12:04 am] <juvinious> the plugins shouldn't be tied up themselves and should be queried every so often by the host,
if the plugin doesn't respond host resets it


Plugins, API...
---------------

- Process Pool where each plugin runs in a process
- Process Pool where each long running part like a http request happens in

---------------

First Idea:
-----------
Plugins are in the main process,
the Bot provides a IRC::Connection::API for other processes to connect and fiddle with things
the Bot provides a pool of worker processes to

Final Solution (I hope)
-----------------
This one can be handled by a I::C::API module
[12:21 am] <Tomasu> one Idea had the entire thing in a seperate process, so the initialization and registering of events and stuff happens outside of the bots process
[12:21 am] <Tomasu> and the plugin and bot would communicate through some form of IPC
[12:21 am] <Tomasu> but that could lead to a ton of seperate processes

This one can be done
[12:22 am] <Tomasu> then I thought, why not have the initialization in the bot, and then the IRC::Plugin would laucnh its handler in a forked process,

As can this one
[12:22 am] <Tomasu> then I thought, have it all in the bot, and only request to use a seperate process when needed.
[12:22 am] <Tomasu> like for a http request


=cut

=pod

=head1 TODO

=over 1

=item register_event alternatives

=item IRC::erorr

=item conf handling in IRC (done I think)

=item Timer stuff

=back

=cut

our $VERSION = 0.01;
our @EXPORT = qw/&irc_set_logfh &irc_set_conf/;
our $logfh;
our $conf;

my $mux = new IO::Multiplex;

sub new {
	my $class = shift;
	my %parms = @_;
	my $self = {%parms};
	bless $self,$class;

	$self->{buffer} = [];
	$self->{write_buffer} = [];
	$self->{write_times} = {};

#	$self->{send_interval}
#	$self->{fd}

	$self->rehash() if $self->can('rehash');

	return $self;
}

sub mux {
	my $self = shift;
	return $mux;
}

sub buffer_get {
	my $self = shift;
	return shift @{$self->{buffer}};
}

sub buffer_put {
	my $self = shift;
	unshift @{$self->{buffer}}, @_;
}

sub buffer_add {
	my $self = shift;
	push @{$self->{buffer}}, @_;
}

# in some rare cases, it may be possible that chunks of orphaned data can be prepended to incomming lines..
sub buffer_getline {
	my $self = shift;
	my $line = '';

	while(scalar @{$self->{buffer}}) {
		my $pos = index($self->{buffer}->[0], "\n");
		if($pos != -1) {
			if($pos && $pos+1 < length $self->{buffer}->[0]) {
				$line .= substr $self->{buffer}->[0], 0, $pos+1, '';
			}
			else {
				$line .= shift @{$self->{buffer}};
			}
			last;
		}
		else {
			$line .= shift @{$self->{buffer}};
		}
	}

	my $pos = index($line, "\n");
	if($pos == -1) {
		unshift @{$self->{buffer}}, $line;
		return;
	}

	return $line if $line ne '';
}

sub buffer_getlines {
	my $self = shift;
	my @arr;

	while(my $line = $self->buffer_getline()) {
		push @arr, $line;
	}

	return @arr;
}

sub rehash : method {
	my $self = shift;
#	$self->set_logfh() if !defined $IRC::logfh;
	return 1;
}

sub set_conf : method {
	my $self = shift;
	my $confh = shift;# || new IRC::Config;

	if($self eq 'IRC') {
		if(!defined $confh) {
			$IRC::conf = new IRC::Config;
		}
		else {
			$IRC::conf = $confh;
		}
	}
	else {
		if(!defined $confh) {
			delete $self->{'confh'}; # back to global config handle
		} else {
			$self->{'conf'} = $confh;
		}
	}
}

sub conf : method {
	my $self = shift;
	return( (exists $self->{conf}) ? $self->{conf} : $IRC::conf );
}

sub cfg_get : method {
	my $self = shift;
	my $sect = $self->conf->section(shift());
	my $key = shift;
#	my $default = shift; <--- $_[0]

	return( (defined $sect) ? $sect->get($key, $_[0]) : $_[0] );
}

sub cfg_set : method {
	my $self = shift;
	my $sect = $self->conf->section(shift());
	my $key = shift;
	my $value = shift;

	return $sect->set($key, $value);
}

sub set_logfh : method {
	my $self = shift;
	my $logfh = shift || \*STDERR;
	if($self eq 'IRC') {
		$IRC::logfh = $logfh;
	}
	else {
		$self->{'logfh'} = $logfh;
	}
}

sub logfh : method {
	my $self = shift;
	if(exists $self->{'logfh'}) {
		return $self->{'logfh'};
	}
	else {
		return $IRC::logfh;
	}
}

sub log : method {
	my $self = shift;
	($package, $filename, $line, $subroutine, $hasargs,
    $wantarray, $evaltext, $is_require, $hints, $bitmask) = caller(0);
	for my $l (@_) {
		print {$self->logfh} $filename . " " . $line . ": " . $l . "\n";
	}
}

sub events(;$) : method {
	my $self = shift;
	my $event = shift;
	my @list;
	my $id;

	if(defined $event) {
		if(ref($event) eq 'ARRAY') {
			# grab all events of types in the array
			for my $e (@$event) {
				if($e =~ m|^/(.*)/$|) {
					my $eves = $self->events();
					for my $evs (@$eves) {
						push @list, $evs if $evs->{'code'} =~ /^$1$/;
					}
				} else {
					push @list, @{$self->{'events'}{$e}} if exists $self->{'events'}{$e};
				}
			}
		}
		else {
			# grab all events of type $event
			if($event =~ m|^/(.*)/$|) {
				my $eves = $self->events();
				for my $evs (@$eves) {
					push @list, $evs if $evs->{'code'} =~ /^$1$/;
				}
			} else {
				push @list, @{$self->{'events'}{$event}} if exists $self->{'events'}{$event};
			}
		}
	} else {
		# grab all events.

		for my $values (values %{$self->{'events'}}) {
			push @list, @$values;
		}
	}

	return \@list;
}

=head2 possible implementation for register_event alternatives

make register_event take a array/hash ref as well as a plain EVENT desc

$arr[0] = EVENT
$arr[1] = From
$arr[2] = To
$arr[3] = text =~ /regex/

=cut

sub register_event_cmd : method { # EVENT :$cmd

}

sub register_event_match : method { # EVENT :/$match/

}

sub register_event_from : method { #  EVENT :(user/chan)

}

# 1: IRC::register_event($thing, EVENT, $dest, 'method')
# 2: $thing->register_event(EVENT, $dest, 'method')
# 3: $thing->register_event($obj, EVENT, $dest, 'method')

sub register_event : method {
	my $self = shift; # methods 1, and 2
	if($self->isa('IRC')) {
		# if it gets here, the user is plainly daft.
		IRC::log("Attempted to call IRC::register_event on a non IRC derived object");
		return;
	}

	# if called as in method 3
	if(ref($_[0])) {
		if(!$_[0]->isa('IRC')) {
			$self->log("Attempted to call \$obj->register_event on a non IRC derived object");
		}
		else {
			$self = shift; # reasign
		}
	}

	# event code/string
	my $ecode = shift;
	if(!defined $ecode || $ecode eq '') {
		$self->log("Attempt to bind to invalid event code/string (aka: undef or '')");
		return;
	}

	# object to send event to
	my $obj = shift;

	# b0rked user again
	if(!ref($obj) || !$obj->isa('IRC')) {
		$self->log("Attempt to register event to non IRC derived object.");
		return;
	}

	my $sub = shift;

	# bj0rked user or typo...
	if(!defined $sub || $sub eq '' || !$self->can($sub)) {
		$self->log("Attempt to bind event to non existing sub '$sub' in package " . ref($self));
		return;
	}

	#$self->log("Registering event: '$ecode'");
	return if !defined $ecode || !defined $sub || $ecode eq '' || $sub eq '';

	my $event = new IRC::Event($self, $ecode, $sub, @_);
	# don't check in the IRC::Event::  name space for existacne of event
	# that will break on new events being added to IRC, or custom events.
	push @{$self->{'events'}{$ecode}}, $event;

	return $event;
}

sub unregister_event($&) : method {
	my $self = shift;
	my $ecode = shift;
	my $sub = shift;
	return if !defined $ecode || !defined $sub || $ecode eq '' || $sub eq '';

	for (my $i=0; $i<@{$self->{'events'}{$ecode}}; ++$i) {
		my $eve = $self->{'events'}{$ecode}[$i];
		if($eve->subroutine == $sub) {
			# remove this event.
			splice @{$self->{'events'}{$ecode}}, $i, 1;
		}
	}
}

sub trigger_event : method {
	my $self = shift;
	my $event = shift;

	#$self->log("trigger_event: '$event' > '" . join(', ', @_) . "'");

	my $events = $self->events($event);
	for my $eve (@$events) {
		$eve->trigger($self, $eve, @_); # double dispatch, sorta, just not really.
	}
}

sub error : method {

}

=pod
sub mux_input {
	shift->log('virtual mux_input method called');
}

sub mux_close {
	shift->log('virtual mux_close method called');
}

sub mux_eof {
	shift->log('virtual mux_eof method called');
}

sub mux_outbuffer_empty {
	shift->log('virtual mux_outbuffer_empty method called');
}

sub mux_connection {
	shift->log('virtual mux_connection method called');
}

sub mux_timeout {
	shift->log('virtual mux_timeout method called');
}
=cut

1;
__END__


=pod

=head1 NAME

IRC - API Planning

=head2 MODULES

=over

=item * L<IRC::Event|IRC::Event>

=item * L<IRC::Connection|IRC::Connection>

=back

=head2 CLASS METHODS

=head3 new

=over
	Creates a new instance of the IRC class.
=back

=head2 INSTANCE METHODS

=head3 add

=cut