package Forks::Super::Job::Emulate;
use strict;
use warnings;
use base 'Forks::Super::Job';
use Carp;
use Forks::Super::Debug ':all';
use Forks::Super::Util ':all';

my $emulate_id = 0;

sub emulate {
    my $job = shift;
    $emulate_id += 4;
    $job->{real_pid} = $job->{pid} = $$ * 1000000 + $emulate_id;
    $job->{is_emulation} = 1;
    local $Forks::Super::Job::Ipc::USE_TIE_FH = 0;
    local $Forks::Super::Job::Ipc::USE_TIE_SH = 0;
    local $Forks::Super::Job::Ipc::USE_TIE_PH = 0;
    $_->() for @Forks::Super::Jobs::POSTFORK_CHILD;
    if (!$job->{cmd} && !$job->{exec} && !$job->{sub}) {
        croak "FSJ::Emulate: must use cmd/exec/sub option with emulate";
    }
    Forks::Super::Job::_postlaunch_parent1($job->{pid}, $job);
    {
        package Forks::Super::Job::Ipc;
        local (our(%IPC_FILES,@IPC_FILES,$IPC_DIR_DEDICATED,@SAFEOPEN,%SIG_OLD));
        local $Forks::Super::Debug::EM = 1;

        $SIG{ALRM} = sub { die "emulation timeout\n" };
        my $timeout = delete $job->{timeout};
        if ($timeout) {
            alarm $timeout;
        }
        $job->{is_child} = 1;
        eval {
            $job->_postlaunch_child;
        };
        alarm 0;
        if ($@ && $@ =~ /emulation timeout/) {
            $job->{status} = 255 << 8;
            $job->{error} = $@;
        }
        delete $job->{is_child};
#       Forks::Super::Job::Ipc::_close_child_fh($job);
    }
    $job->_postlaunch_parent2;
#    print STDERR "\n----------\n",$job->toString,"\n-----------------\n";

    $_->() for @Forks::Super::Jobs::POSTFORK_PARENT;
    return $job;
}

sub _postlaunch_child_to_exec {
    # exec must be interpreted as cmd in emulation mode
    my $job = shift;
    $job->{cmd} ||= delete $job->{exec};
    carp "Forks::Super: exec option changed to cmd in emulation mode";
    return $job->_postlaunch_child_to_cmd;
}

sub _postlaunch_child_to_cmd {
    my $job = shift;
    debug("Executing command [ @{$job->{cmd}} ] {EMULATION MODE}") if $job->{debug};

    my $c1;
    if (&IS_WIN32) {

	$c1 = Forks::Super::Job::OS::Win32::system1_win32_process(
	                      $job, @{$job->{cmd}});

    } else {

	my $this_pid = $$;
        my $retries = $job->{retries} || 0;
	my $exec_pid = Forks::Super::Job::_CORE_fork();
	while (!defined $exec_pid && $retries-- > 0) {
            warn "Forks::Super::Job::_postlaunch_child_to_cmd: ",
                "system fork call returned undef. Retrying ...\n";
            Forks::Super::Util::pause(1.0);
            $exec_pid = Forks::Super::Job::_CORE_fork();
        }
        if (!defined $exec_pid) {
            croak "Forks::Super::Job::_postlaunch_child_to_cmd: ",
                "Child process unable to create new fork to run cmd";
        }
        if ($$ != $this_pid) {
	    exec( "@{$job->{cmd}}" ) or
                Carp::confess 'exec for cmd-style fork failed ';
	}
        $job->{debug} && debug("  exec pid is $exec_pid");
	$job->set_signal_pid($exec_pid);
	$job->{exec_pid} = $exec_pid;

	$Forks::Super::Job::CHILD_EXEC_PID = $exec_pid; 
	# XXX - do something with this in _cleanup_child?

	my $z = CORE::waitpid $exec_pid, 0;
	$c1 = $?;
	debug("waitpid returned $z, exit code of $$ was $c1 ", $c1>>8)
	    if $job->{debug};
    }
    if ($job->{is_emulation}) {
        Forks::Super::Job::Ipc::_close_child_fh($job);
        Forks::Super::Sigchld::_preliminary_reap($job,$c1);
    } else {
        Forks::Super::Job::deinit_child();
        exit $c1 >> 8;
    }
}

sub _config_callback_child {
    my $job = shift;
    # no op in emulation mode
    return;
}


1;

=head1 NAME

Forks::Super::Job::Emulate - support emulation mode for Job object

=head1 DESCRIPTION

In I<emulation> mode (when a non-zero C<emulate> argument is passed
to L<fork|Forks::Super/"fork">), no background process is created.
Rather, the C<fork> call performs the background task in the main
process and returns a L<Forks::Super::Job|Forks::Super::Job> object
that resembles a completed background job.

=cut
    
__END__

emulation tests:

    job should be added to @Forks::Super::ALL_JOBS

    with external command
x        should run command
x        should set exit code
x        should set job status to COMPLETE and not REAPED
        should return STDOUT
        should return STDERR
        should accept STDIN
        ! but can not be interactive
        should treat {exec} like {cmd}
        can set cpu affinity and os priority of external command

    with subroutine call
x        should execute subroutine
x        should set exit code
x        should set job status to COMPLETE and not REAPED
x        should return STDOUT
        should return STDERR
        should accept STDIN
        ! but cannot be interactive
        can set cpu affinity and os priority while sub is running
        ! may not exit from sub call in emulation mode
        if sub call dies, should return error

    back in the parent
        signal (kill) should fail
        cpu affinity should be restored
        os priority should be restored
        can wait or waitpid on job one time
        overloaded FSJ functions work

    bg_qx
    bg_eval

    emulated process respects timeout
    emulated jobs can be named
    emulated jobs can be delayed for deterministic amount of time
    share respected, but easier to implement
