#!/usr/bin/perl -I.

use strict;

my $slowest = 4;

my $debug = 0;
my $c = 1;
$| = 1;
my $testcount = 100;

use Carp qw(verbose);
use Sys::Hostname;

my $startingport = 1025;

my $hostname = hostname;
my $addr = join('.',unpack('C4',gethostbyname($hostname)));
print STDERR "host = $hostname addr = $addr.\n" if $debug;

package T;

use IO::Event;
use IO::Socket::INET;
use Carp;

BEGIN {
	eval { require Time::HiRes };
	if ($@) {
		print "1..0 # Skipped: $@";
		exit;
	}
}

#
# basic idea...   the receiver reads something.  Once it
# has read it, it performs actions that cause more stuff
# to be sent.  The recevier stuff is called within ie_input
#

our (@tests) = (
	{
		# the first one is thrown away
	},
	{ #2
		send =>		sub {
			pusher()->print("woa baby\n");
		},
		acquire =>	sub {
			puller()->get()
		},
		compare => "woa baby",
		repeat => 1,
		desc => 'copy one line: print method & get method',
	},
	{ #3
		send =>		sub {
			pusher()->print("woa frog\n");
		},
		acquire =>	sub {
			puller()->getline()
		},
		compare => "woa frog\n",
		desc => 'copy one line: print method & getline method',
		repeat => 1,
	},
	{ #4
		send =>		sub {
			my $p = pusher();
			print $p "foo\nbar\n";
		},
		acquire =>	sub {
			my $p = puller();
			return <$p>;
		},
		compare => [ "foo\n", "bar\n" ],
		repeat => 1,
		array => 1,
		desc => 'copy two lines: print filehandle & <filehandle>',
	},
	{ #5
		send =>		sub {
			my $p = pusher();
			printf $p "%s\n%s\n", 'foo', 'baz';
		},
		acquire =>	sub {
			my $p = puller();
			return <$p>;
		},
		compare => [ "foo\n", "baz\n" ],
		repeat => 1,
		array => 1,
		desc => 'copy two lines: printf filehandle & <filehandle>',
	},
	{ #6
		send =>		sub {
			pusher()->print("abc123");
		},
		acquire =>	sub {
			my ($s, $ibr, $t) = @_;
			return '' unless length($$ibr) >= 6;
			my $p = puller();
			my $x;
			read($p, $x, 3);
			die unless length($x) == 3;
			read($p, $x, 3, 3);
			return $x;
		},
		compare => "abc123",
		repeat => 1,
		desc => 'copy 2x3 chars: print method & read filehandle',
	},
	{ #7
		send =>		sub {
			pusher()->print("a\nb\n\nc\n\n\nd\n\n\n\ne\n");
			$/ = '';
		},
		acquire =>	sub {
			my $p = puller();
			return <$p>;
		},
		compare => [ "a\nb\n\n", "c\n\n", "d\n\n", "e\n" ],
		repeat => 1,
		array => 1,
		desc => 'copy 4 sets many lines: print method & array context <filehandle>',
	},
	{ #8
		send =>		sub {
			$/ = '';
			pusher()->print("a\nb\n\nc\n\n\nd\n\n\n\ne\n");
		},
		acquire =>	sub {
			my $p = puller();
			my @l;
			while (<$p>) {
				push(@l, $_);
			}
			return @l;
		},
		compare => [ "a\nb\n\n", "c\n\n", "d\n\n", "e\n" ],
		repeat => 1,
		array => 1,
		desc => 'copy 4 sets many lines: print method & scalar context <filehandle>',
	},
	{ #9
		send =>		sub {
			pusher()->print("\n\n\na\nb\n\nc\n\n\nd\n\n\n\ne\n");
		},
		connect => 	sub {
			$/ = "xyz";
			puller()->input_record_separator('');
		},
		acquire =>	sub {
			my $p = puller();
			return <$p>;
		},
		compare => [ "a\nb\n\n", "c\n\n", "d\n\n", "e\n" ],
		repeat => 1,
		array => 1,
		desc => 'copy 4 sets many lines: print method & <filehandle> with $/ funny',
	},
	{ #10
		send =>		sub {
			pusher()->print("\n\na\nb\n\nc\n\n\nd\n\n\n\ne\n");
		},
		connect =>	sub {
			$/ = "xyz";
			puller()->input_record_separator('');
		},
		acquire =>	sub {
			my $p = puller();
			my @l;
			while (<$p>) {
				push(@l, $_);
			}
			return @l;
		},
		compare => [ "a\nb\n\n", "c\n\n", "d\n\n", "e\n" ],
		repeat => 1,
		array => 1,
		desc => 'copy 4 sets many lines: print method & scalar <filehandle> with $/ funny',
	},
	{ #11
		send =>		sub {
			pusher()->print("xyz124abc567");
		},
		connect =>	sub {
			$/ = "\n";
			puller()->input_record_separator(3);
		},
		acquire =>	sub {
			my $p = puller();
			my @l;
			while (<$p>) {
				push(@l, $_);
			}
			return @l;
		},
		compare => [ "xyz", "124", "abc", "567" ],
		repeat => 1,
		array => 1,
		desc => 'copy 4 sets many lines: print method & scalar <filehandle> with $/ == 3',
	},
	{ #12
		send =>		sub {
			pusher()->print("xyz124abc567");
		},
		connect =>	sub {
			$/ = "\n";
			puller()->input_record_separator(3);
		},
		acquire =>	sub {
			my $p = puller();
			return <$p>;
		},
		compare => [ "xyz", "124", "abc", "567" ],
		repeat => 1,
		array => 1,
		desc => 'copy 4 sets many lines: print method & <filehandle> with $/ == 3',
	},
	{ #13
		send =>		sub {
			pusher()->print("xyzYYY124YYYabcYYY567");
		},
		connect =>	sub {
			$/ = "\n";
			puller()->input_record_separator("YYY");
		},
		acquire =>	sub {
			my $p = puller();
			return <$p>;
		},
		compare => [ "xyzYYY", "124YYY", "abcYYY", "567" ],
		repeat => 1,
		array => 1,
		desc => 'copy 4 sets many lines: print method & <filehandle> with $/ == YYY',
	},
	{ #14
		send =>		sub {
			pusher()->print("xyzYYY124YYYYabcYYY567");
		},
		connect =>	sub {
			$/ = "\n";
			puller()->input_record_separator("YYY");
		},
		acquire =>	sub {
			my $p = puller();
			return <$p>;
		},
		compare => [ "xyzYYY", "124YYY", "YabcYYY", "567" ],
		repeat => 1,
		array => 1,
		desc => 'copy 4 sets many lines: print method & <filehandle> with $/ == YYY & extra Y',
	},
	{ #15
		send =>		sub {
			pusher()->print("xyzYYY124YYYYabcYYY567");
		},
		connect =>	sub {
			puller()->input_record_separator("YYY");
		},
		acquire =>	sub {
			my $p = puller();
			my @l;
			while (<$p>) {
				push(@l, $_);
			}
			return @l;
		},
		compare => [ "xyzYYY", "124YYY", "YabcYYY", "567" ],
		repeat => 1,
		array => 1,
		desc => 'copy 4 sets many lines: print method & scalar <filehandle> with $/ == YYY & extra Y',
	},
	{ #15
		send =>		sub {
			pusher()->print("my\ndog\nate\nmy...");
		},
		acquire =>	sub {
			my $p = puller();
			my @l;
			my $x;
			while (defined ($x = $p->get())) {
				push(@l, $x);
			}
			return @l;
		},
		compare => [ "my", "dog", "ate", "my..." ],
		repeat => 1,
		array => 1,
		desc => 'copy 4 lines: print method & get method',
	},
	{ #16
		send =>		sub {
			pusher()->print("aaabbbcccddde");
		},
		acquire =>	sub {
			my $p = puller();
			my @l;
			my $x;
			my $r = "12";
			while ($x = $p->sysread($r, 3)) {
				die unless length($r) == $x;
				push(@l, $r);
			}
			return @l;
		},
		compare => [ "aaa", "bbb", "ccc", "ddd", "e" ],
		repeat => 1,
		array => 1,
		desc => 'copy 5x3 chars: print method & sysread method',
	},
	{ #17
		send =>		sub {
			pusher()->print("aaabbbcccddde");
		},
		acquire =>	sub {
			my $p = puller();
			my @l;
			my $x;
			my $r = "12";
			while ($x = $p->sysread($r, 3, 1)) {
				die unless length($r) == $x+1;
				push(@l, $r);
			}
			return @l;
		},
		compare => [ "1aaa", "1bbb", "1ccc", "1ddd", "1e" ],
		repeat => 1,
		array => 1,
		desc => 'copy 5x3 chars: print method & sysread method with offset',
	},
	{ #18
		send =>		sub {
			pusher()->print("aaabbbcccddde");
		},
		acquire =>	sub {
			my $p = puller();
			my @l;
			my $x;
			my $r = "12";
			while ($x = sysread($p, $r, 3)) {
				die unless length($r) == $x;
				push(@l, $r);
			}
			return @l;
		},
		compare => [ "aaa", "bbb", "ccc", "ddd", "e" ],
		repeat => 1,
		array => 1,
		desc => 'copy 5x3 chars: print method & sysread filehandle',
	},
	{ #19
		send =>		sub {
			pusher()->print("aaabbbcccddde");
		},
		acquire =>	sub {
			my $p = puller();
			my @l;
			my $x;
			my $r = "12";
			while ($x = sysread($p, $r, 3, 1)) {
				die unless length($r) == $x+1;
				push(@l, $r);
			}
			return @l;
		},
		compare => [ "1aaa", "1bbb", "1ccc", "1ddd", "1e" ],
		repeat => 1,
		array => 1,
		desc => 'copy 5x3 chars: print method & sysread filehandle with offset',
	},
	{ #20
		send =>		sub {
			pusher()->print("aaabbbcccddde");
		},
		acquire =>	sub {
			my $p = puller();
			my $b;
			my $c;
			my @l;
			while ($c = $p->getc()) {
				if ($b && substr($b, 0, 1) eq $c) {
					$b .= $c;
				} elsif (! $b) {
					$b = $c;
				} else {
					$p->xungetc($c);
					push(@l, $b);
					undef $b;
				}
			}
			push(@l, $b) if defined $b;
			return @l;
		},
		compare => [ "aaa", "bbb", "ccc", "ddd", "e" ],
		repeat => 1,
		array => 1,
		desc => 'getc & xungetc',
	},
);

printf "1..%d\n", scalar(@tests);

# let's listen on a socket.  We'll expect to receive
# test numbers.  We'll print ok.

my $rp = T::pickport();
my $results = IO::Event::Socket::INET->new(
	Listen => 10,
	Proto => 'tcp',
	LocalPort => $rp,
	LocalAddr => $addr,
	Handler => 'Pull',
	Description => 'Listener',
);

die unless $results;
die unless $results->filehandle;

my $fh = $results->filehandle;
my $fn = $fh->fileno;

print STDERR "fh=$fh\n" if $debug;
print STDERR "fn=$fn\n" if $debug;

my $idle;
my $time = time;
my $waitingfor = $c;
my $ptime;

my $push_socket;
my $pull_socket;

Event->idle (
	cb => \&startup,
	reentrant => 0,
	repeat => 0,
);

okay($results, "start listening on results socket");

alarm($slowest);

print STDERR "about to loop\n" if $debug;

my $r = Event::loop();
okay($r == 7, "loop finshed ($r)");

exit(0);

sub pusher
{
	my ($np) = @_;
	$push_socket = $np if $np;
	return $push_socket;
}
sub puller
{
	my ($np) = @_;
	$pull_socket = $np if $np;
	return $pull_socket;
}


# support routine
sub pickport
{
	for (my $i = 0; $i < 1000; $i++) {
		my $s = new IO::Socket::INET (
			Listen => 1,
			LocalPort => $startingport,
		);
		if ($s) {
			$s->close();
			return $startingport++;
		}
		$startingport++;
	}
	die "could not find an open port";
}

# support routine
sub okay
{
        my ($cond, $message) = @_;
        if ($cond) {
                print "ok $c # $message\n";
        } else {
		my($package, $filename, $line, $subroutine, $hasargs, $wantarray, $evaltext, $is_require) = caller(0);
		print "not ok $c # $filename:$line $message\n";
        }
        $c++;
	if ($c > $testcount) {
		print STDERR "too many test results\n";
		exit(0);
	}
}

# default to oops
sub ie_input
{
	confess "we shoudn't be here";
}

sub startup
{
	IO::Event::Socket::INET->new (
		Proto => 'tcp',
		PeerPort => $rp,
		PeerAddr => $addr,
		Handler => 'Push',
	) or T::okay(0, "create pusher to $rp: $@");
}

sub sender
{
	shift(@tests);
	if (! @tests) {
		okay(1, "all done");
		exit(0);
	}
	my $t = $tests[0];
	$a = $t->{send};
	# okay(1, "keys = ".join(' ',keys %$t));
	if (ref $a) {
		eval { &$a() };
		if ($@) {
			T::okay(0, "send error $@");
			exit(0);
		}
	} else {
		pusher || confess "no pusher";
		print "# printing '$a' for new test\n" if $debug;
		pusher->print($a);
	}
	print "# starting $t->{desc}\n";
	pusher->close();
	alarm($slowest);
}

package Push;

sub ie_connected
{
	my ($self, $s) = @_;
	T::pusher($s);
	T::sender($s);
}

sub ie_input
{
	my ($self, $s, $br) = @_;
	print $s->getlines();
}

package Pull;

sub ie_connection
{
	my ($self, $s) = @_;
	T::puller($s->accept);
	my $t = $T::tests[0];
	my $c = $t->{connect};
	&$c if $c;
}

sub ie_input
{
#use Carp;
#print Carp::longmess("DEBUG... ie_input called\n");
	my ($self, $iput, $ibuf) = @_;
	my $t = $T::tests[0];
	my $acquire = $t->{acquire};
	my ($r, @r);
	if ($t->{array}) {
		@r = eval { &$acquire($iput, $ibuf, $t) };
	} else { 
		$r = eval { &$acquire($iput, $ibuf, $t) };
	}
	if ($@) {
		T::okay(0, "acquire error: $@ errno:$!");
		exit(0);
	}
	if ($t->{repeat}) {
		if ($t->{array}) {
			unshift(@r, @{$t->{prev}})
				if $t->{prev};
			$t->{prev} = [ @r ];
		} else {
			$r = $t->{prev}.$r
				if $t->{prev};
			$t->{prev} = $r;
		}
	}
	my $compare = $t->{compare};
	my $cr;
	if (ref $compare eq 'CODE') {
		if ($t->{array}) {
			$cr = eval { &$compare(@r) };
		} else {
			$cr = eval { &$compare($r) };
		}
		if ($@) {
			T::okay(0, "copmare error $@");
			exit(0);
		}
	} elsif ($t->{array}) {
		$r = join('><', @r);
		$compare = join('><', @$compare);
		$cr = length($r) < length($compare) 
			? -1
			: ($r eq $compare
				? 0
				: 1);
	} else {
		$cr = length($r) < length($compare) 
			? -1
			: ($r eq $compare
				? 0
				: 1);
	}
	my $dr = $r;
	$dr =~ s/\n/\\n/g;
	my $dcompare = $compare;
	$dcompare =~ s/\n/\\n/g;
	if ($t->{repeat} && $cr == -1 && ! $iput->eof) {
		print STDERR "waiting for more input:\n\t<$dr>\n\t<$dcompare>\n"
			if $debug;
		# we'll wait for more input
		print "# wait for more input\n";
		return;
	}
	my $desc = $t->{desc};
	if ($cr == 0) {
		T::okay(1, $desc);
	} else {
		T::okay(0, "test $desc failed: $cr: <$dr> <$dcompare>");
	}
	print "# next\n";
	if (@tests > 1) {
		T::startup;
	} else {
		exit 0;
	}
}

sub ie_eof
{
	print "# eof\n";
}

1;
