#!/usr/bin/perl
$^W=1;
use strict;

use Getopt::Long;
use Expect;

# more generalized send/expect utility

# vi(1) :se tabstop=4

my $timeout=30; # starting default timeout for Expect;

# basename of program (from $0):
my $prog=$0; $prog =~ s;\A.*/;;s;

my $usage="usage: $prog [--help] [--verbose] -- command [argument ...] -- [--interact|--nointeract] [--timeoutok] { [--timeout timeout] send [expect] } ... ] ]";
my $helptext=<<""
$usage
	--help
		don't run, just display help text and exit
	--timeout timeout
		timeout in seconds, -1 to wait indefinitely
	--timeoutok
		treat expect timeouts as non-fatal
	--verbose
		include some non-error information to STDERR
	command
		command we'll execute
	argument
		argument(s) to command
	Environment:
		If preceded by ${prog}_ these:
			interact
			nointeract
			timeout
			timeoutok
			verbose
		set in the environment and not overridden by corresponding
		options, respectively are used for those corresponding
		options and their option arguments as applicable.

;

$usage="$usage\n";
                                                                                # detabify
$usage=~s/\t/    /og;
$helptext=~s/\t/    /og;

# various defaults:
Getopt::Long::Configure (
	"posix_default", # start with POSIX (compatible) defaults
	# "bundling", # then tweak as seems fitting # don't need/want bundling here
	"no_ignore_case", # don't ignore case
);
my $help=undef;
my $verbose=undef;
GetOptions (
	'help' => \$help,
	'verbose' => \$verbose,
) or die "$prog: bad option(s), aborting\n${usage}aborting";

if($help){
	print "$helptext" or
		exit 1;
	exit 0;
};

my @cmd_and_args=();

# push command and argument(s) until --; then swallow the --
my $arg=undef;
while(1){
	$arg=shift;
	last if (!defined($arg) || $arg eq '--');
	#$arg =~ s/^----/--/;
	push(@cmd_and_args,$arg);
}

@cmd_and_args && $arg eq '--' or
	die; ##### need better diagnostic here

my $exp=undef;

# Any remaining arguments are options/arguments for expect.

print STDERR (
	join(
		"\n",
		@cmd_and_args,
	),
	"\n",
) if $verbose;

# process potential further options for expect
my $interact=undef;
my $nointeract=undef;
my $timeoutok=undef;
GetOptions (
	"interact" => \$interact,
	"nointeract" => \$nointeract,
	"timeout=i" => \$timeout,
	"timeoutok" => \$timeoutok,
) or die "$prog: bad option(s), aborting\n${usage}aborting";

my $isinteract=undef;
if($interact){
	die "$prog: --nointeract and --interact are mutually exclusive, aborting\n${usage}aborting"
		if($nointeract)
	;
	$isinteract=1;
}
if($nointeract){
	die "$prog: --nointeract and --interact are mutually exclusive, aborting\n${usage}aborting"
		if($interact)
	;
	$isinteract=0;
}
if(!defined($isinteract)){
	if(exists($ENV{${prog} . '_interact'})){
		die "$prog: nointeract and interact are mutually exclusive, aborting\n${usage}aborting"
			if exists($ENV{${prog} . '_nointeract'})
		;
		$isinteract=1;
	}
	if(exists($ENV{${prog} . '_nointeract'})){
		die "$prog: nointeract and interact are mutually exclusive, aborting\n${usage}aborting"
			if exists($ENV{${prog} . '_interact'})
		;
		$isinteract=0;
	}
}

$isinteract=0 if !defined($isinteract); # default to not go interactive

print STDERR ('Expect-->spawn(',"\n",join("\n",@cmd_and_args),")\n")
	if $verbose
;
$exp = Expect->spawn(@cmd_and_args)
	or die "$prog: Cannot spawn $cmd_and_args[0]: $!\n";

sub e{
	my $expect='';
	$expect=$_[0] if defined($_[0]);
	print STDERR "timeout=$timeout, EXPECT: $expect\n" if $verbose;
	if($expect ne ''){
		$exp->expect(
			($timeout!=-1?$timeout:undef), #-1 --> forever
			[
				qr/$expect/m,
				sub {
					print STDERR "MATCHED: $expect\n" if $verbose;
				}
			],
			[
				'eof',
				sub {
					print STDERR "EOF: $expect\n" if $verbose;
					$exp->soft_close();
					sleep(1);
					$exp->hard_close();
					exit(1);
				}
			],
			[
				'timeout',
				sub {
					print STDERR "TIMEOUT: $expect\n" if $verbose;
					if(!$timeoutok){
						$exp->soft_close();
						sleep(1);
						$exp->hard_close();
						exit(1);
					}
				}
			],
		);
	}else{
		# missing or empty $expect, we expect eof
		$exp->expect(
			($timeout!=-1?$timeout:undef), #-1 --> forever
			[
				'eof',
				sub {
					print STDERR "EOF: $expect\n" if $verbose;
					$exp->soft_close();
					sleep(1);
					$exp->hard_close();
					exit;
				}
			],
			[
				'timeout',
				sub {
					print STDERR "TIMEOUT: $expect\n" if $verbose;
					if(!$timeoutok){
						$exp->soft_close();
						sleep(1);
						$exp->hard_close();
						exit(1);
					}
				}
			],
		);
	};
}

sub s{
	my $send = '';
	$send=join('',@_) if(@_);

	# generally apply perl's double quote interpolation to our string:
	$send =~ s/.*/qq{"$send"}/ees;

	if($verbose){
		print STDERR ('SEND:',$send,"\n");
	}
	print $exp ($send) if $send ne '';
};

while(@ARGV){
	#####print "ARGV- start $#ARGV\n"; for(0..$#ARGV){print "$_: $ARGV[$_]\n";}; print "-ARGV\n";
	$_=shift;
	#####print "ARGV- after shift $#ARGV\n"; for(0..$#ARGV){print "$_: $ARGV[$_]\n";}; print "-ARGV\n";
	if(/^--timeout$/){
		$timeout=shift;
		#####print "ARGV- after tshift1 $#ARGV\n"; for(0..$#ARGV){print "$_: $ARGV[$_]\n";}; print "-ARGV\n";
		defined($timeout) or
			die "$prog: --timeout missing option argument, aborting";
		$timeout+=0; # make it numeric
		$_=shift;
		#####print "ARGV- after tshift2 $#ARGV\n"; for(0..$#ARGV){print "$_: $ARGV[$_]\n";}; print "-ARGV\n";
	};
	my $send=$_;
	&s($send) if defined($send) && $send ne '';
	my $expect=shift;
	#####print "ARGV- after eshift $#ARGV\n"; for(0..$#ARGV){print "$_: $ARGV[$_]\n";}; print "-ARGV\n";
	$expect='' if !defined($expect);
	&e($expect);
}

if(defined($isinteract)&&$isinteract){
	# go interactive
	print STDERR "INTERACT:\n" if $verbose;
	$exp->interact();
}else{
	# don't go interactive
	$timeout=-1;
	&e('');
};
