#!/usr/bin/perl
#
# psh - Perl Shell
#
# TODO: Use more obsure file handle names, or use lexical FileHandle objects,
# so that the user cannot get them and their names don't interfere with user
# code.
#
# Copyright (C) 1999 Gregor N. Purdy. All rights reserved.
# This script is free software. It may be copied or modified according
# to the same terms as Perl itself.
#

package psh;

use vars qw($VERSION);
$VERSION   = '0.003';

use Cwd;
use Cwd 'chdir';
use IPC::Open2;
use POSIX ":sys_wait_h";
use Getopt::Std;
my %opt;
#use POSIX qw(tcsetpgrp);

getopts('dwr:', \%opt);


#
# TODO: Figure out how to make SIGINT abandon the current
# input even when it is under the control of ReadLine...
#

$SIG{'INT'}   = \&signal_handler;
$SIG{'QUIT'}  = \&signal_handler;
$SIG{'USR1'}  = \&signal_handler;
$SIG{'USR2'}  = \&signal_handler;
$SIG{'CONT'}  = \&signal_handler;
$SIG{'STOP'}  = \&signal_handler;
$SIG{'TSTP'}  = \&signal_handler;
$SIG{'TTIN'}  = \&signal_handler;
$SIG{'TTOU'}  = \&signal_handler;
$SIG{'CHLD'}  = \&ignore_handler;
$SIG{'WINCH'} = \&handle_resize;


#
# Deal with debug mode:
#

if ($opt{'w'}) {
	print_out("Simulating -w switch and strict\n");

	$^W = 1;    # Simulate the -w command-line switch
	use strict; # Turned on when debugging
}

print_debug("Debugging!\n");


#
# Global Variables:
#
# $prompt is intended to be accessible to the user via $psh::prompt, 
# which can be set to a string, or to a subroutine ref that returns
# a prompt string.
#
# The other variables are private, lexical variables.
#

use vars qw($bin $news_file $cmd $prompt $smart @wday @mon %prompt_vars %built_ins %aliases
	$history_file $save_history $history_length);

%aliases                   = ();
$save_history              = 1;
$history_length            = 50;

%built_ins = (
	'alias' => \&builtin_alias,
	'bg'    => \&builtin_bg,
	'cd'    => \&builtin_cd,
	'exit'  => \&builtin_exit,
	'fg'    => \&builtin_fg,
    'jobs'  => \&builtin_jobs,
    'kill'  => \&builtin_kill,
	'which' => \&builtin_which,
);


#
# Private, Lexical Variables:
#

my $default_prompt         = '\s\$ ';
my $input;
my $currently_active       = 0;
my $flag_signal_sent       = 0;
my $signal_sent            = 0;
my %jobs                   = (),
my @jobs_order             = ();
my $readline_saves_history = 0;
my $last_dir               = '.'; # By default 'cd -' won't change directory at all.
my $term;                         # Term::ReadLine object.
my @completion_buffer      = ();


#
# Set up Term::ReadLine:
#

eval "use Term::ReadLine;";

if ($@) {
	$term = undef;
	print_debug("Term::ReadLine not available.\n");
} else {
	$term = Term::ReadLine->new('psh');
	$term->MinLine(0);   # We will handle history adding ourselves (undef causes trouble).
	$term->ornaments(0);
	print_debug("Using ", $term->ReadLine(), "\n");

	if ($term->ReadLine() eq "Term::ReadLine::Gnu") {
		$readline_saves_history = 1;
		$term->StifleHistory($history_length); # Limit history
		$term->Attribs->{attempted_completion_function} = \&custom_completion;
	}
}


#
# Set up Term::Size:
#

eval "use Term::Size 'chars'";

if ($@) {
	print_debug("Term::Size not available.\n");
} else {
	print_debug("Using &Term::Size::chars().\n");
}


#
# read_until()
#
# Get successive lines via calls to &$get() until one of those
# lines contains exactly the text $terminator. Used to implement
# the `<<EOF` multiline quoting construct;
#
# TODO: Undo any side effects of, e.g., m//.
#

sub read_until
{
	my ($get, $terminator) = @_;
	my $input;
	my $temp;

	$input = '';
	while (1) {
		$temp = &$get();
		last unless defined($temp);
		last if $temp =~ m/^$terminator$/;
		$input .= $temp;
	}

	return $input;
}


#
# abs_path()
#

sub abs_path
{
	my $dir = shift;

	$dir = '~' unless defined $dir and $dir ne '';

	if ($dir =~ m|^(~([a-zA-Z0-9-]*))(.*)$|) {
		my $user = $2; 
		my $rest = $3;

		my $home;

		if ($user eq '') { $home = $ENV{HOME}; }
		else             { $home = (getpwnam($user))[7]; }

		if ($home) { $dir = "$home$rest"; } # If user's home not found, leave it alone.
	}

	if (!$dir =~ m|^/|) { $dir = cwd . '/', $dir }
	
	return $dir;
}


#
# which()
#

sub which
{
	my $cmd      = shift;
	my @path     = split(':', $ENV{PATH});

	foreach my $dir (@path) {
		$dir = abs_path($dir);

		my $try = "$dir/$cmd";

		if ((-x $try) and (!-d _)) { return $try; }
	}

	return undef;
}


#
# builtin_cd()
#

sub builtin_cd
{
	my $in_dir = shift;
	my $dir = $in_dir;

	$dir = $last_dir if $dir eq '-';
	$dir = abs_path($dir);

	if ((-e $dir) and (-d _)) {
		if (-x _) {
			$last_dir = cwd;
			chdir $dir;
		} else {
			print_error("psh: $in_dir: Permission denied\n");
			return 1;
		}
	} else  {
		print_error("psh: $in_dir: No such directory\n");
		return 1;
	}

	return 0;
}


#
# builtin_kill()
#

sub builtin_kill
{
	my $sig;
	my $pid;

	if (@_ == 1) {
		$pid = shift;
		$sig = 'TERM';
	} elsif (@_ == 2) {
		($sig, $pid) = shift;
	} else {
		print_error("kill: usage: kill <sig> <pid>\n");
		return 1;
	}

	if ($pid =~ m|^%(\d+)$|) {
		my $temp = $1 - 1;

		if (($temp < 0) or ($temp >= scalar(@jobs_order))) {
			print "pid   = $pid\n";
			print "temp  = $temp\n";
			print "#jobs = ", scalar(@jobs_order), "\n";
			print_error("kill: No such job $pid\n");
			return 1;
		}

		$pid = $jobs_order[$pid];
	}

	if (kill($sig, $pid) != 1) {
		print_error("kill: Error sending signal $sig to process $pid\n");
		return 1;
	}

	return 0;
}


#
# builtin_which()
#

sub builtin_which
{
	my $cmd   = shift;
	if (!defined($cmd) or $cmd eq '') {
		print_error( "which: requires one argument\n");
	}

	my $which;
	if ($built_ins{$cmd}) { $which = '(built-in)'; }
	else                  { $which = which $cmd;   }

	if (!defined($which)) { print_error( "which: no $cmd in ($ENV{PATH})\n"); }

	print_out("$which\n");

	return 0;
}


#
# builtin_alias()
#
# TODO: Handles alias ls=ls -F fine, but doesn't do well with alias ls='ls -F'.
# Generally, we need work on quoting issues.
#

sub builtin_alias
{
	my $arg = shift;

	if(!defined($arg) or $arg eq '') {
		foreach $i (keys %aliases) {
			print_out("alias $i=".$aliases{$i}."\n");
		}
	} else {
		if ($arg=~/(\w+)\=(.+)/o) {
			$aliases{$1} = $2;
		}
	}

	return 0;
}


#
# builtin_fg()
#

sub builtin_fg
{
	my $arg = shift;

	$arg = -1 if (!defined($arg) or ($arg eq ''));

	restart_job(1, $arg - 1);
}


#
# builtin_bg()
#

sub builtin_bg
{
	my $arg = shift;

	$arg = -1 if (!defined($arg) or ($arg eq ''));

	restart_job(0, $arg - 1);
}


#
# builtin_jobs()
#

sub builtin_jobs {
	for($i = 0; $i <= $#jobs_order; $i++) {
		my $visindex = $i+1;
		my $pid      = $jobs_order[$i];
		my $tmp      = $jobs{$pid};

#		next unless $tmp;

		print_out( "[$visindex] $pid ".@$tmp[0]);

		if (@$tmp[1]) { print_out("\n");           }
		else          { print_out(" (stopped)\n"); }
	}
}


#
# builtin_exit()
#
# TODO: What if a string is passed in?
#

sub builtin_exit
{
	my $result = shift;
	$result = 0 unless defined($result);

	if ($save_history && $readline_saves_history) {
		$term->WriteHistory($psh::history_file);
	}

	exit $result;
}


#
# process()
#
# Process a source of lines until it returns undef.
#
# The $get argument is a subroutine ref, each call to which
# returns a line of input, or undef if there are no more lines.
#
# The $put argument is a subroutine ref, each call to which
# prints output. It is important for the output routine to
# ensure that output is flushed at each call, since it is
# used to print the prompt.
#
# The $prompt argument is a boolean which indicates whether or
# not to print the prompt. When sourcing files (like .pshrc),
# it is important to not print the prompt string, but for
# interactive use, it is important to print it.
#
# TODO: Undo any side effects, e.g. done by m//.
#


sub process
{
	my ($get, $put, $prompt) = @_;
	local $cmd;
	my $first_part="", $rest="";

#	print_out("[[PROCESSING GENERIC INPUT]]\n");

	while (1) {
		if ($prompt) {
			$input = &$get(prompt_string());
		} else {
			$input = &$get();
		}

		$cmd++;

		last unless defined($input);

		chomp $input;

		$input =~ /^\s*(\S+)(.*)/;
		$first_part= $1;
		$rest= $2;

		if ( $first_part && $aliases{$first_part} ) {
			$input= $aliases{$first_part}.$rest;
			$input =~ /^\s*(\S+).*/;
			$first_part= $1;
		}
		if      ($input =~ m/^\s*\#.*$/) {
			undef $input;
		} elsif ($input =~ m/^\s*$/) {
			undef $input;
		} elsif ($input =~ m/^\s*<<([a-zA-Z_0-9\-]*)\s*$/) {
			$input = read_until($get, $1);
		} elsif ($input =~ m/^\s*\.\s+(.+)\s*$/) {
			process_file($1);
			undef $input;
		} elsif ($input =~ m/^\s*!(.+)\s*$/) {
			my_system($1);
			undef $input;

		#
		# TODO: Really what we need is a perl
		# quoting mechanism that produces an array of arguments using
		# shell-like parsing rules. The idea would be you give it a
		# string which is expected to be a sequence of scalars without
		# commas between them. This is much like qw(), except that it
		# allows internal quoting. Note that the handling of the string
		# sent to system() probably has some of the necessary logic.
		#

		} elsif ($input =~ m/^\s*([a-zA-Z0-9_-]+)(\s+(.*)$|($))/) {
			my $cmd = $1;
			my $arg = $3;

			if (ref $psh::built_ins{$cmd} eq 'CODE') {
				print_debug("Using built-in '$cmd'.\n");
				&{$psh::built_ins{$cmd}}($arg);
				undef $input;
			} elsif ($psh::smart) {
				my $which = which($cmd);

				if (defined($which)) {
					print_debug("Using smart '$which'.\n");
					if( $arg) { my_system("$which $arg"); }
					else { my_system("$which"); }
					undef $input;
				}
			}
		} elsif ($input =~ m|^\s*\.?\/.*|o && $psh::smart ) {
			if( -x $first_part && ! -d _) {
				my_system($input);
				undef $input;
			}
		}

		next unless defined($input);

		#
		# TODO: Is this the best way to manage the package context?
		#
		# Consider:
		#
		#     my $pkg = package;
		#     package psh;
		#     ...
		#     package $pkg;
		#     eval ...
		#     package $psh;
		#
		# The idea here is to not force "package main" as it does now.
		#

		my $result;
		my $pid;
		local $SIG{INT}= sub { die; };
		package main;
		$result = eval $input;
		package psh;
		local $SIG{INT}= \&signal_handler;
		my $msg = $@;
		chomp $msg;

		if (!defined($result)) {
			# TODO: This *should* be an indication of trouble, too!
			# But, sub { ... }; and use ...; both have undef results from eval!
#			print_error( "Error ($msg) evaluating input: `$input'!\n");
		}
		elsif( $prompt && is_number($result))
		{
			print_out("Result: $result\n");
		}
		

		if ($msg) {
			print_error( "Error ($msg)!\n");
		}
	}
}


#
# process_file()
#

sub process_file
{
	my ($path) = @_;

#	print_out("[[PROCESSING FILE $path]]\n");

	if (!-r $path) {
		print_error( "$bin: Cannot read script `$path'\n");
		return;
	}

	if (!open(FILE, $path)) {
		print_error( "$bin: Cannot open script `$path'\n");
		return;
	}

	process(sub { return <FILE>; });

	close(FILE);

#	print_out("[[FINISHED PROCESSING FILE $path]]\n");
}


#
# prompt_string()
#
# Construct a prompt string.
#
# TODO: Should we have an entry for '\'?
#

%prompt_vars = (
	'd' => sub {
			my ($wday, $mon, $mday) = (localtime)[6, 4, 3];
			$wday = $wday[$wday];
			$mon  = $mon[$mon];
			return "$wday $mon $mday";
		},
	'h' => sub {
			my $host = `hostname -s`;
			chomp $host;
			return $host;
		},
	's' => sub {
			my $shell = $bin;
			$shell =~ s/^.*\///;
			return $shell;
		},
	'n' => sub { return "\n"; },
	't' => sub {
			my ($hour, $min, $sec) = (localtime)[2, 1, 0];
			return sprintf("%02d:%02d:%02d", $hour, $min, $sec);
		},
	'u' => sub {
			# Camel, 2e, p. 172: 'getlogin'.
			return getlogin || (getpwuid($>))[0] || "uid$>";
		},
	'w' => sub { return cwd; },
	'W' => sub { my $dir = cwd; $dir =~ s/^.*\///; return $dir },
	'#' => sub { return $cmd; },
	'$' => sub { return ($> ? '$' : '#'); }
);

sub prompt_string
{
	my $temp;

	#
	# First, get the prompt string from a subroutine or from the default:
	#

	if (ref($prompt) eq 'CODE') { # If it is a subroutine,
		$temp = &$prompt();
	} elsif (ref($prompt)) {      # If it isn't a scalar
		print_warning("psh: Warning: \$psh::prompt is neither a SCALAR nor a CODE reference.\n");
		$temp = $default_prompt;
	} else {
		$temp = $prompt;
	}

	#
	# Now, subject it to substitutions:
	#

	while ($temp =~ m/^(.*)\\(.)(.*)$/) {
		my $sub;

		my $var = $prompt_vars{$2};

		if (ref $var eq 'CODE') {
			$sub = &$var();
		} else {
			print_warning("psh: Warning: \$psh::prompt (`$temp') contains unknown escape sequence `\\$2'.\n");
			$sub = ''
		}

		{
			local $1;
			if ($sub =~ m/\\(.)/) {
				print_warning("psh: Warning: Expansion of `\\$2' in prompt string yielded\n");
							  
				print_warning("     string containing `$1'. Stripping escape sequences from\n");
				print_warning("     substitution.\n");
				$sub =~ s/\\(.)//g;
			}
		}

		$temp = $1 . $sub . $3
	}

	return $temp;
}


#
# iget()
#
# Interactive line getting routine. If we have a
# Term::ReadLine instance, use it and record the
# input into the history buffer. Otherwise, just
# grab an input line from STDIN.
#
# readline() returns a line WITHOUT a "\n" at the
# end, and <STDIN> returns one WITH a "\n", UNLESS
# the end of the input stream occurs after a non-
# newline character. So, first we chomp() the
# output of <STDIN> (if we aren't using readline()),
# and then we tack the newline back on in both
# cases. Other code later strips it off if necessary.
#

sub iget
{
	my $prompt = shift;
	my $line;

	if ($term) {
		$line = $term->readline($prompt);
	} else {
		print $prompt;
		$line = <STDIN>;
	}

	chomp $line;
	$line =~ s/^\s+//;
	$line =~ s/\s+$//;

	if($term and $line )
	{
		$term->addhistory($line);

		if ($save_history && !$readline_saves_history) {
			open(HIST, ">>$psh::history_file");
			print HIST $line."\n";
			close(HIST);
		}
	}

	reap_children(); # Check wether we have dead children

	return $line . "\n";         # This is expected by other code.
}


#
# news
#

sub news 
{
	if (-r $news_file) {
		return `cat $news_file`;
	} else {
		return '';
	}
}


#
# initialize()
#

sub initialize
{
	$|            = 1;                # Set ouput autoflush on
	$prompt       = $default_prompt;  # Set default prompt
	$smart        = 1;
	$cmd          = 1;

	$bin          = $0;
	$bin          =~ s/.*\///;

	$news_file    = "$bin.NEWS";
	$history_file = "$ENV{HOME}/.${bin}_history";

	#
	# TODO: Internationalize!
	#
	# Although, right now they can be overridden in the .pshrc file
	# via @psh::wday = qw(Dom Lun Mar Mie Jue Vie Sab).
	#

	@wday      = qw(Sun Mon Tue Wed Thu Fri Sat);
	@mon       = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
}


#
# process_rc()
#
# Search for and process .pshrc files.
#

sub process_rc
{
	my @rc;
	my $rc_name = ".pshrc";

#	print_out("[ LOOKING FOR .pshrc ]\n");

	if ($opt{'r'}) {
		push @rc, $opt{'r'};
	} else {
		if ($ENV{HOME}) { push @rc, "$ENV{HOME}/$rc_name"; }
		push @rc, "$rc_name" unless $ENV{HOME} eq cwd;
	}

	foreach my $rc (@rc) {
		if (-r $rc) {
#			print_out("[ PROCESSING $rc ]\n");
			process_file($rc);
		}
	}

	if ($save_history) {
		if ($readline_saves_history) {
			$term->ReadHistory($psh::history_file);
		} else {
			open(HIST, "<$psh::history_file") || return;

			while(<HIST>) {
				chomp;
				$term->addhistory($_);
			}

			close(HIST);
		}
	}
}


#
# process_args()
#
# Process files listed on command-line.
#

sub process_args
{
#	my @rc;
#	my $rc_name = ".pshrc";

#	print_out("[ PROCESSING @ARGV FILES ]\n");

	foreach my $arg (@ARGV) {
		if (-r $arg) {
#			print_out("[ PROCESSING $arg ]\n";
			process_file($arg);
		}
	}
}


#
# main_loop()
#
# Determine whether or not we are operating interactively,
# set up the input routine accordingly, and process the
# input.
#

sub main_loop
{
	my $interactive = (-t STDIN) and (-t STDOUT);
	my $get;

	#print_out("[[STARTING MAIN LOOP]]\n");

	if ($interactive) { $get = \&iget;                  }
	else              { $get = sub { return <STDIN>; }; }

	process($get, sub { print @_; }, $interactive);
}


sub is_number
{
	my ( $test)=@_;
	return $test=~/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/o;
}

sub print_warning
{
	print STDERR @_;
}

sub print_debug
{
	print STDERR @_ if $opt{'d'};
}

sub print_error
{
	print STDERR @_;
}

sub print_out
{
	print STDOUT @_;
}

#
# symbols()
#
# Print out the symbols of each type used by a package. Note: in testing,
# it bears out that the filehandles are present as scalars, and that arrays
# are also present as scalars. The former is not particularly surprising,
# since they are implemented as tied objects. But, use vars qw(@X) causes
# both @X and $X to show up in this display. This remains mysterious.
#

sub symbols
{
	my $pack = shift;
	my (@ref, @scalar, @array, @hash, @code, @glob, @handle);
	my @sym;

	{
		no strict qw(refs);
		@sym = keys %{*{"${pack}::"}};
	}

	for my $sym (sort @sym) {
		next unless $sym =~ m/^[a-zA-Z]/; # Skip some special variables
		next if     $sym =~ m/::$/;       # Skip all package hashes

		{
			no strict qw(refs);

			push @ref,    "\$$sym" if ref *{"${pack}::$sym"}{SCALAR} eq 'REF';
			push @scalar, "\$$sym" if ref *{"${pack}::$sym"}{SCALAR} eq 'SCALAR';
			push @array,  "\@$sym" if ref *{"${pack}::$sym"}{ARRAY}  eq 'ARRAY';
			push @hash,   "\%$sym" if ref *{"${pack}::$sym"}{HASH}   eq 'HASH';
			push @code,   "\&$sym" if ref *{"${pack}::$sym"}{CODE}   eq 'CODE';
#			push @glob,   "\*$sym" if ref *{"${pack}::$sym"}{GLOB}   eq 'GLOB';
			push @handle, "$sym"   if ref *{"${pack}::$sym"}{FILEHANDLE};
		}
	}

	print_out("Reference: ", join(' ', @ref),    "\n");
	print_out("Scalar:    ", join(' ', @scalar), "\n");
	print_out("Array:     ", join(' ', @array),  "\n");
	print_out("Hash:      ", join(' ', @hash),   "\n");
	print_out("Code:      ", join(' ', @code),   "\n");
#	print_out("Glob:      ", join(' ', @glob),   "\n");
	print_out("Handle:    ", join(' ', @handle), "\n");
}


#
# signal_handler()
#

sub signal_handler {
	my( $sig) = @_;
	print_debug( "Received signal SIG$sig, sending to $currently_active\n");
	$flag_signal_sent=1;
	$signal_sent= $sig;
   
	kill $sig, $currently_active if $currently_active;
	$SIG{$sig}=\&signal_handler;
}


#
# ignore_handler()
#

sub ignore_handler { }

#
# handle_resize()
#

sub handle_resize
{
	my ($cols, $rows) = (80, 24);

	eval {
		($cols,$rows)= &Term::Size::chars();
	};

	if(($cols > 0) && ($rows > 0)) {
		$ENV{COLUMNS} = $cols;
		$ENV{LINES}   = $rows;
	}
}


#
# my_system()
#
# Executes a program and allows jobcontrol
#

sub my_system {
	my($call) = @_;

	# TODO: This is an absolute hack... we need
	# a full parser for quoting and all special
	# characters soon!!
	
	my $fgflag=1;
	if( $call =~ /^(.*)\&\s*$/ )
	{
		$call= $1;
		$fgflag=0;
	}
	eval {
		open(TMPOUT,">-");
		open(TMPIN,"-");
		my $pid= open2(">&TMPOUT", "<&TMPIN", $call);
		my $status=-1;
		$currently_active=$pid;
		my @tmp=($call,1);
		$jobs{$pid}=\@tmp;
	    push(@jobs_order,$pid);
		if( $fgflag)
		{
			wait_for_system($pid);
		}
		else
		{
			my $visindex=$#jobs_order+1;
			print_out("[$visindex] Background $pid ".$tmp[0]."\n");
		}
	};

	print_debug("Status $@\n") if $@;
}


#
# wait_for_system()
#
# Waits for a program to be stopped/ended
#

sub wait_for_system {
	my($pid) = @_;

	$flag_signal_sent = 0;

	while(1) {
		if( waitpid( $pid, &WNOHANG)==$pid || $flag_signal_sent) {
			$currently_active=0;
			if( $flag_signal_sent) {
				if( $signal_sent eq "TSTP" ||
					$signal_sent eq "STOP" ) {
					my $tmp= $jobs{$pid};
					@$tmp[1]=0;
					my $visindex= get_job_number($pid);
					print_out( "[$visindex] Stopped $pid ".@$tmp[0]."\n");
					# mark job stopped
				} elsif( $signal_sent eq "INT") {
					delete_from_joblist($pid);
				}
			} else {
				delete_from_joblist($pid);
			}
			last;
		}
	}
}


#
# reap_children()
#
# Checks wether any children we spawned died
#

sub reap_children
{
	for($i = 0; $i <= $#jobs_order; $i++) {
		my $visindex = $i+1;
		my $pid      = $jobs_order[$i];
		my $tmp      = $jobs{$pid};

		if( waitpid( $pid, &WNOHANG)==$pid) {
			delete_from_joblist( $pid);
			print_out( "[$visindex] Done $pid ".@$tmp[0]."\n");
		}
	}
}


#
# delete_from_joblist()
#

sub delete_from_joblist
{
	my($pid) =@_;

	delete $jobs{$pid};

	my $position;
	my $i;

	for($i=0; $i <= $#jobs_order; $i++) {
		last if($jobs_order[$i] == $pid);
	}

	splice(@jobs_order,$i,1);
}


#
# restart_job()
#

sub restart_job
{
	my ($fg_flag, $job_to_start) = @_;

	if ($job_to_start < 0) { # Search for the last job which is not running
		for ($i = $#jobs_order; $i >= 0; $i--) {
			my $pid = $jobs_order[$i];
			my $tmp = $jobs{$pid};

			if(! @$tmp[1]) {
				$job_to_start = $i;
				last;
			}
		}

		print_debug("Found job $job_to_start\n");
	}

	if ($job_to_start >- 1) {
		my $pid = $jobs_order[$job_to_start];
		my $tmp = $jobs{$pid};

		if ($tmp) {
			my $command = @$tmp[0];
			return if(@$tmp[1]);
			my $visindex = get_job_number($pid);
			print_out("[$visindex] Restart $pid $command\n");
			kill 'CONT', $pid;
			@$tmp[1]=1;

			if($fg_flag) {
#				tcsetpgrp($pid); # TODO: Is this right?
				eval { wait_for_system($pid, $command); };
			}
		}
	}
}


#
# Tries to find executables for possible completions
# TODO: This is sloooow... but probably not only because
# of searching the whole path but also because of the way
# Term::ReadLine::Gnu works... hmm
#

sub try_executable_completion
{
	my ($cmd, $state) = @_;

	if (!$state)
	{
		my @path           = split(':', $ENV{PATH});
		@completion_buffer = ();

		foreach my $dir (@path) {
			chdir abs_path($dir);
			push( @completion_buffer, grep { -x } glob "$cmd*" );
		}

		chdir $last_dir;
	}

	return shift @completion_buffer;
}


#
# custom_completion()
#
# Main completion hook
#

sub custom_completion
{
	my ($text, $line, $start, $end) = @_;
	my $attribs                     = $term->Attribs;
	my @tmp;

	if (substr($line, $start, 1) eq "~") {
		return $term->completion_matches($text,
								  $attribs->{username_completion_function});
	}

	if(substr($line, 0, $start) =~ /^\s*$/) { # First word
		@tmp = $term->completion_matches($text, \&try_executable_completion);
		return @tmp if defined @tmp;
	} # Only return if executable match found something, otherwise try
	  # filename completion

	return $term->completion_matches($text,
		   $attribs->{filename_completion_function});
}


#
# get_job_number()
#
# Returns the visible number of a job or -1 if theres no such job
# TODO: Is there an array search function in perl except grep?
# I don't remember...
#

sub get_job_number
{
	my ($pid) = @_;

	for ($i = 0; $i <= $#jobs_order; $i++) {
		return $i + 1 if ($jobs_order[$i] == $pid);
	}

	return -1;
}


#
# MAIN:
#

initialize;
process_rc;

if (@ARGV) {
	process_args;
} else {
	main_loop;
}

exit 0;

#
# End of file.
#

