#!/usr/bin/env perl

use strict;
use warnings;

use Proc::Daemon;
use Pod::Usage;
use Getopt::Long;
use Data::Dumper;

print STDERR "===========================================================\n";
print STDERR "= THIS IS MY-MOPPER                                       =\n";
print STDERR "===========================================================\n";

# $Id: mopper,v 1.9 2006/08/09 02:01:05 denneau Exp $
# Separate support code stub for testing.

sub _setenv {
    my ($k, $v) = @_;
    $ENV{$k} = $v;
}


sub _addenv {
    # Add the specified item to the environment PATH-style.
    # First check to see if the thing we're adding is already there; if so, don't
    # do anything.
    my ($k, $v) = @_;
    my $re = qr/$v/;            # save regexp

    if (!$ENV{$k}) {
        $ENV{$k} = $v;          # wasn't set, so set it
    }
    else {
        my @stuff = split /:/, $ENV{$k};
        foreach my $item (@stuff) {
            return if $item =~ $re;     # found it, so bail
        }
        $ENV{$k} = join ':', $v, @stuff;
    }
}


use subs qw(
    setup_env
    run_job
    catch_SIGUSR1
);


# Globals.
my %job_str = (
    DTCTL => './my-dtctl --once',
    LODCTL => 'lodctl --finish',
    PRECOV => 'precov --finish',
);


my $instance_name;
my $with_precov;
my $mops_home;
my $bg;
my $no_env;
my $no_run;
my $quiet;
my $help;
GetOptions(
    instance => \$instance_name,    # specify instance, or use $ENV{MOPS_DBINSTANCE}
    with_precov => \$with_precov,   # run precovery after LOD
    with_precover => \$with_precov, # handle alternate option name
    'mops_home=s' => \$mops_home,   # specify MOPS_HOME, otherwise $ENV{MOPS_HOME} || /usr/local/MOPS
    bg => \$bg,                 	# run in background as daemon
    no_env => \$no_env,             # DON'T set environment
    no_run => \$no_run,             # DON'T run, just setup env and quit (debugging)
    quiet => \$quiet,
    help => \$help,
) or pod2usage(2);
pod2usage(-verbose => 2) if $help;
$instance_name ||= shift || $ENV{MOPS_DBINSTANCE};
pod2usage(3) unless $instance_name;


print STDERR "Starting mopper.\n";

# Set up MOPS environment.
print STDERR "Setting up environment.\n";
unless ($no_env) {
    setup_env($instance_name);
}


# Now load instance to get logger object.
print STDERR "Starting logger.\n";
unshift @INC, "$ENV{MOPS_HOME}/lib/perl5"; # yech
require PS::MOPS::DC::Instance;     # now load, after %ENV set up
my $inst = PS::MOPS::DC::Instance->new(DBNAME => $instance_name);
my $mops_logger = $inst->getLogger;
if ($no_run) {
    # Debugging.
#    print Dumper($inst);
    exit;
}


# Daemon setup.
print STDERR "Ready to run simulation '$instance_name'.  Have a great day.\n";
Proc::Daemon::Init if $bg;          # daemonize if specified
$SIG{USR1} = \&catch_SIGUSR1;       # set up SIGUSR1 handler


# Main processing loop.  Easy peasy.
my $rv;
my $quit;

# Try LODCTL first, in case DT was done for not LOD.
run_job($job_str{LODCTL});
run_job($job_str{PRECOV}) if $with_precov;      # nightly precovery

# Now process DT, LOD pairs.
while(($rv = run_job($job_str{DTCTL})) == 0) {
    run_job($job_str{LODCTL});
    run_job($job_str{PRECOV}) if $with_precov;      # nightly precovery
}
exit;



sub catch_SIGUSR1 {
    # Just set the quit flag when USR1 received.
    $quit = 1;
}


sub run_job {
    # Run program in job_str.  Return the job's exit value, where 0 usually means
    # something was processed successfully.
    my $job_str = shift;
    eval {
        $0 = "MOPPER $instance_name : $job_str";          # what ps sees
        system($job_str);
        $mops_logger->logdie("graceful termination") if $quit;
    };

    $mops_logger->logdie($@) if $@;           # Perl error
    if ($? == -1) {         # exec failed
        $mops_logger->logdie("exec failed: $!");
    }
    elsif ($? & 127) {
        $mops_logger->logdie("child failed: $!");
    }
    else {
        return $rv = $? >> 8;   # process exit code
    }
}


sub setup_env {
    # Set up our environment for running MOPS simulations.
    my $inst_name = shift;

    my @envkeys = qw(
        MOPS_HOME
        MOPS_DBINSTANCE 
        PERL5LIB
        PATH 
        CAET_DATA 
        ORSA_HOME 
        LD_LIBRARY_PATH 
    );

    _setenv('MOPS_DBINSTANCE', $inst_name);
    _setenv('MOPS_HOME', $mops_home || $ENV{MOPS_HOME} || '/usr/local/MOPS');
    _addenv('PERL5LIB', "$ENV{MOPS_HOME}/lib/perl5");
    _addenv('PATH', "$ENV{MOPS_HOME}/bin");
    _addenv('PATH', "$ENV{MOPS_HOME}/var/$ENV{MOPS_DBINSTANCE}/bin");
    _addenv('LD_LIBRARY_PATH', "$ENV{MOPS_HOME}/lib");
    _setenv('CAET_DATA', "$ENV{MOPS_HOME}/data/caet_data");
    _setenv('ORSA_HOME', "$ENV{MOPS_HOME}/data/orsa");

    unless ($quiet) {
        print STDERR "$_=$ENV{$_}\n" foreach @envkeys;
    }
}


=head1 NAME

mopper - Run unattended MOPS simulations
 
=head1 SYNOPSIS

mopper [options] [INSTANCE_NAME]

  INSTANCE_NAME : name of simulation to run
  --with_precov : run PRECOV after LODCTL each night
  --mops_home DIR : use DIR as MOPS home directory; otherwise $ENV{MOPS_HOME} or /usr/local/MOPS
  --bg : run in background
  --no_env => don't set environment
  --no_run => don't run; just setup env and quit
  --quiet : less output
  --help : show usage

=head1 DESCRIPTION

Runs a MOPS instance unattended by establishing MOPS environment then
calling DTCTL and LODCTL repeatedly until there is no more data to process
by DTCTL.  Future versions of mopper will include PRECOV processing.

It is expected that jobs invoked by MOPPER conform to simple exit
code conventions, as follows:

  0 - successfully processed some data
  1 - no data to process
  2 - other MOPS failure 
  99 - internal error in job

MOPPER can be terminated in two ways via a signal.  For graceful termination,
send a SIGUSR1, which will set an internal flag to terminate MOPPER following
the completion of the current job.  Send SIGINT to attempt to shutdown MOPPER
immediately (currently unsupported).  The consistency of a simulation is not
guaranteed (yet) if it is terminated via SIGKILL.

=head1 ENVIRONMENT

MOPS execution requires the following environment variables to
be set up, so MOPPER sets them to appropriate values.

        MOPS_DBINSTANCE 
        MOPS_HOME (/usr/local/MOPS)
        PERL5LIB ($MOPS_HOME/lib/perl5)
        PATH ($MOPS_HOME/bin)
        CAET_DATA ($MOPS_HOME/data/caet_data)
        ORSA_HOME ($MOPS_HOME/data/orsa)
        LD_LIBRARY_PATH ($MOPS_HOME/lib)

The PATH, CAET_DATA, ORSA_HOME and LD_LIBRARY_PATH environment variables
are manipulated in PATH-fashion:  they new value is prepended to the
old one, separated by a colon ':'.  If the new value is found in the
old value, the environment variable is not modified.

=cut

