#!/usr/bin/perl

require "sys/wait.ph";

print "batcher v1.0 -- the ultimate batching program\n";
print "        by: Hani Jamjoom  <jamjoom\@umich.edu>\n\n";


if ( $#ARGV < 0 ) {
    print "USUAGE: batcher --cmd \"command line\" \n" .
	  "                --post \"command line\"\n\n" .
	  "                --opt       name val\n" .
	  "                --opt_range name start_val stop_val step_size\n" .
	  "                --opt_list  name val_or_string_between_quotes ... \n\n" .
	  "                --machines m1 m2 m3 ... \n" .
	  "                --outfile filename\n" .
	  "                --break_on_first\n" . 
	  "                --sync_machines\n" . 
	  "                --check \n";
    exit(0);
}
     
# PARSE OPTIONS

$machines_count = 1;
$break_on_first = 0;
$sync_machine   = 0;

while ( $#ARGV >= 0 ) {

    $in_arg = shift(@ARGV);

    if ( $in_arg eq "--cmd" ) {

	$in_cmd[$cmd_count] = shift(@ARGV);
	$cmd_count++;

    } elsif ( $in_arg eq "--opt" ) {

	$in_name = shift(@ARGV);
	$in_val  = shift(@ARGV);

	$opt_val{$in_name} = $in_val;

    } elsif ( $in_arg eq "--opt_range" ) {

	$in_name       = shift(@ARGV);
	$in_start_val  = shift(@ARGV);
	$in_stop_val   = shift(@ARGV);
	$in_step_size  = shift(@ARGV);

	$opt_range_start{$in_name} = $in_start_val;
	$opt_range_stop{$in_name}  = $in_stop_val;
	$opt_range_step{$in_name}  = $in_step_size;

    } elsif ( $in_arg eq "--opt_list" ) {

	$in_name = shift(@ARGV);

	$in_val  = "NOTHING"; 

	while ( (index($in_val, "--") == -1)  && ($#ARGV >= 0) ) {

	    $in_val = shift(@ARGV);
	    
	    if ( index($in_val, "--") == -1 ) { 
		$opt_list{$in_name} .= $in_val . "=";
	    } else {
		unshift(ARGV, $in_val);
	    }
	}

    } elsif ( $in_arg eq "--outfile" ) {

	$in_output = shift(@ARGV);

    } elsif ( $in_arg eq "--break_on_first" ) {

	$break_on_first = 1;

    } elsif ( $in_arg eq "--check" ) {

	$check = 1;

    } elsif ( $in_arg eq "--sync_machines" ) {

	$sync_machines = 1;

    } elsif ( $in_arg eq "--post" ) {

	$in_post = shift(@ARGV);

    } elsif ( $in_arg eq "--machines" ) {

	$in_val = "";
	$machines_count = 0;
	while ( (index($in_val, "--") == -1)  && ($#ARGV >= 0) ) {

	    $in_val = shift(@ARGV);
	    
	    if ( index($in_val, "--") == -1 ) { 
		$in_machines[$machines_count] = $in_val;
		$machines_count++;
	    } else {
		unshift(ARGV, $in_val);
	    }
	}

    
    } else {
	print "SYNTAX ERROR: Dude, you wrote somethig ($in_arg) that is not supported\n";
	exit(0);
    }
}

# Some Range Value Initializations

$depth_max  = 0;  # How far into the list did we change 
$depth_curr = 0;  

foreach $key (sort keys(%opt_range_start)) {
    $opt_current{$key} = $opt_range_start{$key};
}

foreach $key (sort keys(%opt_list)) {
    ($opt_current{$key}, $junk) = split(/=/, $opt_list{$key}, 2);
}

foreach $key (sort keys(%opt_val)) {
    $opt_current{$key} = $opt_val{$key};
}

@opt_range = sort keys(%opt_current);

# list machines

if ($machines_count > 1) {
    print "\nYou have specified the following ($machines_count) machines to use:\n     ";
    for ($i=0; $i<$machines_count;$i++) {
	print "$in_machines[$i]";
	if ($i < ($machines_count-1)) {
	    print ",";
	} else {
	    print "\n";
	}
    }
}

# check for review

if ( $check == 1 ) {
    print "    -- PLEASE REVIEW WHAT I AM ABOUT TO DO --\n\n";
}

$total_runs=0;

while ( !$done ) {

    $outfile  = $in_output;
    
    for ($i=0; $i<$cmd_count; $i++) {
	$cmd[$i] = $in_cmd[$i];
    }
 
    $total_runs++;
    
    # Perform variable sustitution

    foreach $key (sort keys(%opt_current)) {
	for ($i=0; $i<$cmd_count; $i++) {
	    $cmd[$i] =~ s/$key/$opt_current{$key}/g;
	}
	$outfile =~ s/$key/$opt_current{$key}/g;
    }
	
    for ($i=0; $i<$cmd_count; $i++) {
	$cmd[$i] =~ s/outfile/$outfile/g;
	$cmd[$i] =~ s/machine/$in_machines[$i]/g;
    }

    # Print Sample 
    
    if ( $check == 1 ) {
	
	if (!$sample ) {
	    print "Here is a sample execution:\n";
	    for ($i=0; $i<$cmd_count; $i++) {
		print "> $cmd[$i]\n";
		print "[IN PARALLEL:] " if ($cmd_count > 1); 
	    }
	    
	    print ".. Checking if files exist:\n";
	    $sample = 1;
	}

    }

    print "WARNING: $outfile exists\n" if (-e $outfile);
	
    # Determine next file to run

    $done = &next_file_to_run;
}

if ($check ==1) {

    print "\n> Do you want to me to proceed (y/n)? ";
    
    $in_char = getc;
    
    if ( $in_char ne 'y' ) {
	printf "Goodbye...\n";
	exit(0);
    }
}

# REAL WORK STARTS HERE...

$total_time = 0;
$curr_run   = 0;
$done = 0;
$outfiles = "";

for($i=0; $i<$machines_count; $i++) {
    $machines_state[$i] = 0;
    $machines_outfiles[$i] = "";
    $machines_times[$i] = 0;
    $machines_start[$i] = time();
    $machines_runs[$i] = 0;
}

while ( !$done ) {

    $curr_run++;

    # skip all the machine selection if non is specified.

    if ( $machines_count == 1 ) {
	$curr_machine = 0;
	$elapse = time() - $machines_start[0];
	$machines_time[0] += $elapse;
	goto _NEW_JOB;
    }

    # Go through machines and if all are busy, then 
    # wait for some to finish.  Once one finishes,
    # then start the next job.

_FIND_MACHINE:

    $finished_machines = 0;
    if ($sync_machines == 0 ) {
	$start_pos = $curr_machine;
    } else {
	$start_pos = 0;
    }

    $curr_machine = -1;
    for($i=$start_pos; $i<$machines_count; $i++) {
	if ($machines_state[$i] == 0) {
	    $curr_machine = $i;
	    goto _NEW_JOB;
	}
    }

_NO_MACHINE:

    for($i=0; $i<$machines_count; $i++) {
	if (($machines_pid[$i] > 0) && 
	    (waitpid($machines_pid[$i],&WNOHANG) != 0)) {
	    $machines_state[$i] = 0;
	    $curr_machine = $i;
	    $finished_machines++;
	    $elapse = time() - $machines_start[$i];
	    $machines_time[$i] += $elapse;
	   
	    $machines_pid[$i] = -1;
	}
    }

    if ( $curr_machine < 0 ) {
	sleep 1;
	goto _NO_MACHINE;
    }

    if ( $sync_machines ) {
	if ( $finished_machines == $machines_count ) {
	    $start_pos = 0;
	    goto _FIND_MACHINE;
	} else {
	    $finished_machines = 0;
	    goto _NO_MACHINE;
	}
    }

_NEW_JOB:

    # Keep Track of how fast is each machine
    $machines_start[$curr_machine] = time();
    $machines_state[$curr_machine] = 1;
    $machines_runs[$curr_machine]++;
   
    $pid == -1;

    if ( $machines_count > 1) {
	if ( $pid = fork) {

	    $outfile  = $in_output;
	    foreach $key (sort keys(%opt_current)) {
		$outfile =~ s/$key/$opt_current{$key}/g;
	    }

	    $outfile =~ s/machine/$in_machines[$curr_machine]/g;
	    
	    if ( index($outfiles,$outfile) == -1 ) {
		$outfiles .= " " . $outfile;
	    }
    
	    if ( index($machines_outfiles[$curr_machine],$outfile) == -1 ) {
		$machines_outfiles[$curr_machine] .= " " . $outfile;
	    }

	    $machines_pid[$curr_machine] = $pid;

	    goto _END_RUN;
	} elsif (($pid == 0) || ($machines_count == 0)) {
	    goto _THIS_RUN;
	} else {
	    die "Can't fork: $!\n";
	}
    }

_THIS_RUN:

    $total_time = 0;
    for($i=0;$i<$machines_count;$i++) {
	$total_time += $machines_time[$i];
    }

    $average_time = $total_time / $curr_run;
    $TTF = ($average_time) * ($total_runs - $curr_run);
    
    $outfile  = $in_output;
    for ($i=0; $i<$cmd_count; $i++) {
	$cmd[$i] = $in_cmd[$i];
    }
 
    # Perform variable sustitution

    foreach $key (sort keys(%opt_current)) {
	for ($i=0; $i<$cmd_count; $i++) {
	    $cmd[$i] =~ s/$key/$opt_current{$key}/g;
	}
	$outfile =~ s/$key/$opt_current{$key}/g;
    }
    
    $outfile =~ s/machine/$in_machines[$curr_machine]/g;

    for ($i=0; $i<$cmd_count; $i++) {
	$cmd[$i] =~ s/outfile/$outfile/g;
	$cmd[$i] =~ s/machine/$in_machines[$curr_machine]/g;
    }

    if ( index($outfiles,$outfile) == -1 ) {
	$outfiles .= " " . $outfile;
    }
    
    if ( index($machines_outfiles[$curr_machine],$outfile) == -1 ) {
	$machines_outfiles[$curr_machine] .= " " . $outfile;
    }

    #Start Running

    print "Executing ($curr_run/$total_runs):\n";
    printf("   + Average time = %0.2f sec, Time remaining = %0.2f sec\n", 
	   $average_time, $TTF);

    for ($i=0; $i<$cmd_count; $i++) {
	print "   + \$> $cmd[$i] \n";
    }

    for ($i=0; $i<$cmd_count; $i++) {
	if ($pid = fork) {
	    $pid_group[$i] = $pid;
	} elsif (defined $pid) {
	    `$cmd[$i]`;
	    exit(0);
	} else {
	    die "Can't fork: $!\n";
	}
    }

    $finished_count = 0;

_RUN_WAIT:

    # Who is done?

    for ($i=0; $i<$cmd_count; $i++) {
	if ( ($pid_group[$i] >0) &&  (waitpid($pid_group[$i],&WNOHANG) != 0) ) {
	    $pid_group[$i] = -1;
	    $finished_count++;
	}
    }

    # Wait for parallel commands to complete.  If break_on_fist is defined,
    # kill other commands.

    if ( !$break_on_first && ($finished_count < $cmd_count )) {
	sleep 1;
	goto _RUN_WAIT;
    } else {
	# At least one command must complete
	if ($finished_count == 0 ) {
	    sleep 1;
	    goto _RUN_WAIT;
	} 
	    
	for ($i=0; $i<$cmd_count;$i++) {
	    if ($pid_group[$i] > 0 ) {
		print "sending kill signal to $i\n";
		kill 9, $pid_group[$i];
	    } 
	}
    }

    # Child must exit.

    if ($machines_count > 1 ) {
	exit(0);
    }

  _END_RUN:

    # Determine next file to run

    $done = &next_file_to_run;
}

_FINAL_WAIT:

for($i=0; $i<$machines_count; $i++) {

    if (($machines_state[$i] == 1) && 
	(waitpid($machines_pid[$i],&WNOHANG) != 0)) {
	$machines_state[$i] = 0;
    }
}

for($i=0; $i<$machines_count; $i++) {

    if ($machines_state[$i] != 0) {
	sleep 1;
	goto _FINAL_WAIT;
    }
}

print "...... Post Processing\n";

for ($i=0; $i<$machines_count; $i++) {

    $outfile  = $in_output;
    $post     = $in_post;

# Perform variable sustitution

    foreach $key (sort keys(%opt_current)) {
	$outfile =~ s/$key/$opt_current{$key}/g;
    }

    foreach $key (sort keys(%opt_current)) {

	$post =~ s/$key/$opt_current{$key}/g;
	$outfile =~ s/$key/$opt_current{$key}/g;
	
    }

    $post =~ s/outfile/$outfile/g;
    $post =~ s/o_files/$outfiles/g;
    $post =~ s/m_files/$machines_outfiles[$i]/g;
    $post =~ s/machine/$in_machines[$i]/g;

    `$post`;
}

print "...... Bye\n";
    
exit(0);

sub next_file_to_run {

    local($i) = 0;
    local($done2)  = 0;

    while ( ($i <= $#opt_range) && !$done2 ) {
	
	local($key) = $opt_range[$i];

	&next_val($key);

	if ( $opt_current{$key} == -1 ) {
	    &next_val($key);
	    $i++;
	} else {
	    $done2 = 1;
	}
    }

    if ( $i > $#opt_range ) {
	$done = 1;
	return 1;
    }
    
    return 0;
}

sub next_val {

    local($i);

    local($key) = $_[0];
    
    if ( defined $opt_range_start{$key} ) {

	if ($opt_current{$key} == -1) {
	    $opt_current{$key} = $opt_range_start{$key};
	} else {
	    $opt_current{$key} += $opt_range_step{$key};

	    if ( $opt_current{$key} > $opt_range_stop{$key} ) {
		$opt_current{$key} = -1;
	    } 	    
	}

    } elsif ( defined $opt_list{$key} ) {

	@val = split(/=/, $opt_list{$key});

	if ($opt_current{$key} == -1) {
	    $opt_current{$key} = $val[0];
	} else {

	    for($i=0; $i <= $#val; $i++) {
		if ( ($opt_current{$key} eq $val[$i]) && ($i < $#val) ) {
		    $opt_current{$key} = $val[$i+1];
		    return;
		}
	    }

	    $opt_current{$key} = -1;
	}

    } elsif ( defined $opt_val{$key} ) {

	if ($opt_current{$key} == -1) {
	    $opt_current{$key} = $opt_val{$key};
	} else {
	    $opt_current{$key} = -1;
	}

    } else {
	print "ERROR: var name not defined\n";
	exit(0);
    }
}












