use strict;
use Win32::Event;
use Win32::IPC;

# declare locals
my( @EventList, @MyHoldEvents, @MyWaitEvents, @EventNames, $EventName, $Drive );
my( $VerboseFlag, $WaitMode, $WaitAnyMode, $SendMode, $HoldMode, $QueryMode );
my( $CIMode, $BadEventCount, $SessionName );
my( $Argument, $ArgCounter, $True, $False, $Event, $NumEvents, $Return );

# parse command line
foreach $Argument ( @ARGV ) {
    if ( $Argument =~ /^[\/\-]*\?$/ ) { &UsageAndQuit(); }
    # handle verbose flag -v
    if ( $Argument =~ /^[\/\-].*v/i ) {
	$VerboseFlag = "TRUE";
	$Argument =~ s/v//gi;
    }
    # handle case-insensitive flag -i
    if ( $Argument =~ /^[\/\-].*i/i ) {
	$CIMode = "TRUE";
	$Argument =~ s/i//gi;
    }
    # handle the event handling flags
    if ( $Argument =~ /^[\/\-]w$/i ) { $WaitMode = "TRUE"; }
    elsif ( $Argument =~ /^[\/\-]a/i ) { $WaitAnyMode = "TRUE"; }
    elsif ( $Argument =~ /^[\/\-]s/i ) { $SendMode = "TRUE"; }
    elsif ( $Argument =~ /^[\/\-]h/i ) { $HoldMode = "TRUE"; }
    elsif ( $Argument =~ /^[\/\-]q/i ) { $QueryMode = "TRUE"; }
    # if no flag set, it's an event name
    else { push( @EventList, $Argument ); }
}

$ArgCounter = 0;
if ( $WaitMode ) { $ArgCounter++; }
if ( $WaitAnyMode ) { $ArgCounter++; }
if ( $SendMode ) { $ArgCounter++; }
if ( $HoldMode ) { $ArgCounter++; }
if ( $QueryMode ) { $ArgCounter++; }
if ( $ArgCounter != 1 ) { &UsageAndQuit(); }
    

# first things first, open all events
undef( @MyHoldEvents );
undef( @MyWaitEvents );
undef( @EventNames );
if ( $CIMode eq "TRUE" ) {
    foreach $EventName ( @EventList ) {
	$EventName = "\L$EventName";
    }
}

# handle intl constraints for event names
# _ntroot
# lang
# _buildarch
# _buildtype
# seems like a lot, hopefully we won't go over a cmdevt name limit
# of 260 chars. this is order 30 chars, so shouldn't be a problem.
#
my( $NTRoot ) = $ENV{'_NTROOT'};
# remote the initial backslash
$NTRoot =~ s/^\\//;
# convert all other backslashes to dots
$NTRoot =~ s/\\/./g;
foreach $EventName ( @EventList ) {
    $EventName = $NTRoot . ".$ENV{'lang'}." .
	"$ENV{'_BuildArch'}.$ENV{'_BuildType'}.$EventName";
}


# handle terminal server bug (prepend all events with Global and drive letter
$SessionName = $ENV{'SESSIONNAME'};
if ( $SessionName ) {
    if($SessionName ne "Console") {

        $Drive = $ENV{'_NTDRIVE'};
        foreach $EventName ( @EventList ) {
            $EventName = "Global\\$Drive$EventName";
        }
    }
}

# handle the query mode early
if ( $QueryMode ) {
    $BadEventCount = 0;
    foreach $EventName ( @EventList ) {
	$Event = Win32::Event->open( "$EventName.wait" );
	unless ( defined( $Event ) ) { $BadEventCount++; }
    }
    if ( $BadEventCount == 1 ) {
	print( "There was $BadEventCount undefined event.\n" );
    } else {
	print( "There were $BadEventCount undefined events.\n" );
    }
    exit( $BadEventCount );
}

$True = "TRUE";
undef( $False );
foreach $EventName ( @EventList ) {
    $Event = Win32::Event->new( $True, $False, "$EventName.wait" );
    unless ( defined( $Event ) ) {
	print( "Failed to open event $EventName, exiting.\n" );
	exit( 1 );
    }
    push( @MyWaitEvents, $Event );
    $Event = Win32::Event->new( $True, $False, "$EventName.hold" );
    unless ( defined( $Event ) ) {
	print( "Failed to open event $EventName, exiting.\n" );
	exit( 1 );
    }
    push( @MyHoldEvents, $Event );
    push( @EventNames, $EventName );
}
$NumEvents = @MyWaitEvents;
if ( $NumEvents == 0 ) {
    print( "No events in event list, exiting ..." );
    exit( 1 );
}

if ( ( defined( $WaitMode ) || ( defined( $WaitAnyMode ) ) ) ) {
    &WaitRoutine();
}

if ( defined( $HoldMode ) ) { &HoldRoutine(); }

if ( defined( $SendMode ) ) { &SendRoutine(); }

exit( 0 );


sub UsageAndQuit
{
    print( "\n$0 [-w] [-a] [-s] [-h] [-q] [-?] name1 name2 ...\n" );
    print( "\n\t-w\tWait for all objects from name list\n" );
    print( "\t-a\tWait for any objects from name list\n" );
    print( "\t-s\tSend all objects in name list\n" );
    print( "\t-h\tHold until someone is waiting for all objects\n" );
    print( "\t\tin name list\n" );
    print( "\t-q\tQuery to see if listed events exist, exit code\n" );
    print( "\t\tis the number of uncreated events.\n" );
    print( "\t-?\tDisplay usage\n" );
    print( "\n\t$0 is a general process synchronization tool.\n" );
    print( "\tExample use: let's say we want to start four threads\n" );
    print( "\tand we want to make sure they don't finish before we\n" );
    print( "\tbegin listening for their exit. Then we just make sure\n" );
    print( "\tthat each thread begins with a call to $0 -h EventN\n" );
    print( "\tand finishes with a call to $0 -s EventN.\n" );
    print( "\tMeanwhile, our master thread can call $0 -w Event1\n" );
    print( "\tEvent2 Event3 Event4. As soon as the master issues this,\n" );
    print( "\tthe slave threads will resume normal execution.\n" );
    print( "\n" );

    exit( 1 );
}


sub WaitRoutine
{
    my( $Event, $WaitMore );
    my( @HaveReceived, $Return, $NumEvents, $i );

    $NumEvents = @MyWaitEvents;
    for ( $i = 0; $i < $NumEvents; $i++ ) {
	$HaveReceived[ $i ] = "FALSE";
    }

    # set all hold events
    foreach $Event ( @MyHoldEvents ) {
	$Event->set;
    }
    # reset all events
    foreach $Event ( @MyWaitEvents ) {
	$Event->reset;
    }

    if ( defined( $VerboseFlag ) ) {
	print( "Waiting for $NumEvents event" );
	if ( $NumEvents != 1 ) { print( "s ( " ); }
	else { print( " ( " ); }
	for ( $i = 0; $i < $NumEvents; $i++ ) {
	    print( "$EventNames[ $i ] " );
	}
	print( ") :" );
    }
    $WaitMore = "TRUE";
    while ( $WaitMore eq "TRUE" ) {
	$Return = &Win32::IPC::wait_any( \@MyWaitEvents );
	if ( $Return <= 0 ) {
	    print( "\nTimeout or abandoned mutex, exiting.\n" );
	    exit( 1 );
	}
	$Return--;
	$HaveReceived[ $Return ] = "TRUE";
	if ( defined( $VerboseFlag ) ) {
	    print( " $EventNames[ $Return ]" );
	}

	$WaitMore = "FALSE";
	for ( $i = 0; $i < $NumEvents; $i++ ) {
	    if ( $HaveReceived[ $i ] eq "FALSE" ) { $WaitMore = "TRUE"; }
	}
    }
    print( "\n" );
}


sub HoldRoutine
{
    $Return = &Win32::IPC::wait_all( \@MyHoldEvents );
    if ( $Return < 0 ) {
	print( "Encountered an abandoned mutex, exiting.\n" );
	exit( 1 );
    }
}

sub SendRoutine
{
    my( $Event );
    foreach $Event ( @MyWaitEvents ) {
	$Event->set;
    }
}