#!/usr/bin/perl

# parallel PARALLELISM CMD ARGS...
$prog=$0;
$prog =~ s/.*\///;

# end of input flag
$morinp=1;

# look for command line flags
$acnt=1;
$vflg=0;
while ($ARGV[0] =~ /^-/) {
	if ($ARGV[0] =~ /^-a/) {
		if ($ARGV[0] =~ /^-a[0-9]/) {
			$acnt=$ARGV[0];
			$acnt =~ s/^-a//;
		} else {
			$acnt=$ARGV[1];
			shift;
		}
		if ($acnt =~ /[^0-9]/ || $acnt == 0) {
			print STDERR "$prog: Bad -a option: $acnt\n";
			goto errusage;
		}
		shift;
		next;
	}
	if ($ARGV[0] eq "-v") {
		$vflg=1;
		shift;
		next;
	}
	print STDERR "$prog: unknown option: $ARGV[0]\n";
	goto errusage;
}

if ($#ARGV < 1) {
	print STDERR "$prog: arg count $#ARGV < 1\n";
errusage:
	print STDERR "Usage: $prog [-a<N>][-v] <parallelism> <cmd> <args>...\n";
	print STDERR "\t-a<N>\tnumber of args passed to processes\n";
	print STDERR "\t-v\tverbose\n";
	print STDERR "Hint: one line of stdin as last arg to cmd\n";
	print STDERR "Hint: use CMD 'exec' to interpret stdin as commands\n";
	print STDERR "Warning: $prog 0 CMD - can flood a system\n";
	exit 1;
}

# set parallelism
$parallelism=$ARGV[0];
if ($parallelism !~ /^[0-9]+$/) {
	print STDERR "$prog: $parallelism non numeric\n";
	goto errusage;
}
shift @ARGV;

# init array of PIDs
@pids=();

# fill up PID array with processes
for ($pidindex=0 ; $parallelism == 0 || $pidindex < $parallelism ; $pidindex++) {
	if (&runit($pidindex) <= 0) {
		# it ended - or there was an error
		if ($parallelism == 0) {
			# if full out - set current maximum
			$parallelism=$pidindex;
		}
		goto wait4all;
	}
}

# forever loop
for (;;) {
	# any process completed - restart new one
	$waitpid=wait;
	if ($waitpid < 0) {
		print STDERR "$prog: wait - out of sync:", $!, "\n";
		exit 1;
	}

	# find associated child in array
	for ($pidindex=0 ; $pidindex < $parallelism ; $pidindex++) {
		if ($waitpid == $pids[$pidindex]) {
			if ($vflg) {
				print STDERR "end pid[$pidindex]=$waitpid: $arg[$pidindex]\n";
			}
			# insert new process into that array element
			if (&runit($pidindex) <= 0) {
				# it ended - or there was an error
				goto wait4all;
			}
			last;
		}
	}
}

wait4all:
# now to scan the array waiting for all children to complete

# first time through there is no PID to find
$waitpid == -1;
for (;;) {
	$anyleft=0;
	if ($waitpid != -1) {
		# find associated child in array
		for ($pidindex=0 ; $pidindex < $parallelism ; $pidindex++) {
			if ($waitpid == $pids[$pidindex]) {
				if ($vflg) {
					print STDERR "end pid[$pidindex]=$waitpid: $arg[$pidindex]\n";
				}
				$pids[$pidindex]=0;
			} else {
				# if any PID set - need to rescan PID list
				$anyleft |= $pids[$pidindex];
			}
		}
	}

	if ($anyleft || $waitpid == -1) {
		$waitpid=wait;
		if ($waitpid < 0) {
			print STDERR "$prog: wait - out of sync:", $!, "\n";
			exit 1;
		}
	} else {
		# no more to wait on
		exit 0;
	}
}

# run a new parallel process - standard input is last arg to CMD
sub runit() {
	local ($pidindex) = @_;
	local ($lastarg, $newpid, @cmd);
	local ($argc);

	# get the input for the CMD
	unless ($morinp && ($lastarg=<STDIN>)) {
		if ($vflg) {
			print STDERR "$prog: EOF - wait tidy up\n";
		}
		$pids[$pidindex]=0;
		return 0;
	}
	$lastarg =~ s/[\n\r]+$//;
	$arg[$pidindex]=$lastarg;
	# set CMD to run
	if ($ARGV[0] eq "exec" && $#ARGV == 0) {
		@cmd = ();
	} else {
		@cmd = @ARGV;
	}
	push(@cmd, $lastarg);
	for ($argc=$acnt; $argc > 1; $argc--) {
		if ($lastarg=<STDIN>) {
			$lastarg =~ s/[\n\r]+$//;
			push(@cmd, $lastarg);
			$arg[$pidindex] .= " $lastarg";
		} else {
			$morinp=0;
			last;
		}
	}

	# make a child
	$newpid=fork();

	# an error?
	if ($newpid < 0) {
		print STDERR "$prog: fork error:", $!, "\n";
		$pids[$pidindex]=0;
		return $newpid;
	}

	# the parent?
	if ($newpid > 0) {
		$pids[$pidindex]=$newpid;
		return $newpid;
	}

	# child process
	$ENV{"PARALLELID"}=$pidindex;

	# make STDIN /dev/null as our STDIN is the input to parallel args
	close(0);
	open(ZERO, "/dev/null");

	if ($vflg) {
		print STDERR "start pid[$pidindex]=$$: $arg[$pidindex]\n";
	}
	exec @cmd;
	print STDERR "$prog: exec error:", join(" ", @cmd), "\nperror: ", $!, "\n";
	exit 0;
}
