#!/usr/bin/perl

use strict;
use warnings;

=head1 NAME

psconfig_pscheduler_agent - Cooridnates various test definitions and manages them in
pScheduler

=head1 DESCRIPTION

This daemon does the following:
    * Reads test definitions from an include directory
    * Reads remote test definitions and combines with local settings
    * Manages creation, deletion and maintenance all test definitions in pScheduler
=cut

use FindBin qw($Bin);
use lib "$Bin/../lib";

use Getopt::Long;
use Linux::Inotify2;
use Log::Log4perl qw/:easy/;

use perfSONAR_PS::PSConfig::PScheduler::Agent;
use perfSONAR_PS::Utils::Logging;
use perfSONAR_PS::Utils::Daemon qw/daemonize setids lockPIDFile unlockPIDFile/;

##########
# set the process name
$0 = "psconfig_pscheduler_agent";

##########
# Parse command-line options
my $CONFIG_FILE;
my $LOGOUTPUT;
my $LOGGER_CONF;
my $PIDFILE;
my $DEBUGFLAG;
my $HELP;
my $RUNAS_USER;
my $RUNAS_GROUP;

my ( $status, $res );

$status = GetOptions(
    'config=s'  => \$CONFIG_FILE,
    'output=s'  => \$LOGOUTPUT,
    'logger=s'  => \$LOGGER_CONF,
    'pidfile=s' => \$PIDFILE,
    'verbose'   => \$DEBUGFLAG,
    'user=s'    => \$RUNAS_USER,
    'group=s'   => \$RUNAS_GROUP,
    'help'      => \$HELP
);

if(!$CONFIG_FILE){
    print STDERR "Error: no configuration file specified\n";
    exit( -1 );
}

if(!$PIDFILE){
    $PIDFILE = "/var/run/psconfig-pscheduler-agent.pid";
}

($status, $res) = lockPIDFile( $PIDFILE );
if($status != 0){
    print STDERR "Error: $res\n";
    exit( -1 );
}

my $fileHandle = $res;

##########
# Check if the daemon should run as a specific user/group and then switch to
# that user/group.
if($RUNAS_USER && $RUNAS_GROUP){
    if(setids(USER => $RUNAS_USER, GROUP => $RUNAS_GROUP) != 0){
        print STDERR "Error: Could not drop privileges\n";
        exit( -1 );
    }
}elsif($RUNAS_USER || $RUNAS_GROUP){
    # they need to specify both the user and group
    print STDERR "Error: You need to specify both the user and group if you specify either\n";
    exit( -1 );
}

##########
# Now that we've dropped privileges, create the logger. If we do it in reverse
# order, the daemon won't be able to write to the logger.
my $logf = new perfSONAR_PS::Utils::Logging();
my $logger;
if(!defined $LOGGER_CONF || $LOGGER_CONF eq q{}) {
    use Log::Log4perl qw(:easy);

    my $output_level = $INFO;
    if($DEBUGFLAG){
        $output_level = $DEBUG;
    }

    my %logger_opts = (
        level  => $output_level,
        layout => $logf->log4perl_format(),
    );

    if(defined $LOGOUTPUT and $LOGOUTPUT ne q{}){
        $logger_opts{file} = $LOGOUTPUT;
    }

    Log::Log4perl->easy_init(\%logger_opts);
    $logger = get_logger("perfSONAR_PS");
}else{
    Log::Log4perl->init( $LOGGER_CONF );
    $logger = get_logger("perfSONAR_PS");
}

##########
# Before daemonizing, set die and warn handlers so that any Perl errors or
# warnings make it into the logs.
my $insig = 0;
$SIG{__WARN__} = sub {
    $logger->warn($logf->format("Warned: ".join( '', @_ )));
    return;
};

$SIG{__DIE__} = sub {                       ## still dies upon return
    die @_ if $^S;                      ## see perldoc -f die perlfunc
    die @_ if $insig;                   ## protect against reentrance.
    $insig = 1;
    $logger->error($logf->format("Died: ".join( '', @_ )));
    $insig = 0;
    return;
};

if(!$DEBUGFLAG){
    ($status, $res) = daemonize();
    if($status != 0){
        $logger->error($logf->format("Couldn't daemonize: " . $res ));
        exit(-1);
    }
}

unlockPIDFile($fileHandle);

##########
# Create agent
my $agent = new  perfSONAR_PS::PSConfig::PScheduler::Agent();
$agent->debug($DEBUGFLAG);
$agent->init($CONFIG_FILE) or die("Error initializing agent: " . $agent->error());

##########
# Setup Inotify2 on config files and directories
# NOTE: Don't watch requesting agent file because it may not exist
# NOTE: These do not work in /vagrant directory on vagrant VM or the /etc/perfsonar/psconfig symlink
#   If you need to test, set config to something outside of /vagrant shared directory.
my $inotify = new Linux::Inotify2 or die "Unable to create new inotify object: $!" ;
$inotify->blocking(0);
$inotify->watch("$CONFIG_FILE", IN_MODIFY) or die "$CONFIG_FILE watcher creation failed" ;
$inotify->watch($agent->include_directory(), IN_MODIFY | IN_MOVE | IN_CREATE | IN_DELETE) or die $agent->include_directory() . " watcher creation failed" ;
$inotify->watch($agent->archive_directory(), IN_MODIFY | IN_MOVE | IN_CREATE | IN_DELETE) or die $agent->archive_directory() . " watcher creation failed" ;
$inotify->watch($agent->transform_directory(), IN_MODIFY | IN_MOVE | IN_CREATE | IN_DELETE) or die $agent->transform_directory() . " watcher creation failed" ;

##
# Start main program loop
$logger->info($logf->format("Starting pSConfig pScheduler agent"));
while(1){
    ###########
    #Initialize start
    my $start = time;   
    
    #############
    #refresh configs
    $logf->generate_guid(); #generate new log id for run
    $agent->logf()->guid($logf->guid()); #pass to agent so guid consistent
    $logger->info($logf->format("Running agent..."));
    $agent->run();
    $logger->info($logf->format("Agent completed running"));
    
    ###########
    # Sleep until its time to look for file updates or time to refesh
    my $end = time;
    my $until_next_refresh = $agent->check_interval_seconds() - ($end - $start);
    my $until_next_file_check = $agent->check_config_interval_seconds();
    my $sleep_time = $until_next_refresh < $until_next_file_check ? $until_next_refresh : $until_next_file_check;
    if($agent->will_retry_pscheduler()){
        $logger->info($logf->format("Retrying pscheduler in $sleep_time seconds..."));
    }else{
        $logger->info($logf->format("Time until next record refresh is $until_next_refresh seconds"));
    }
    $start = $end;
    while($until_next_refresh > 0){
        sleep($sleep_time);
        if($inotify->poll()){
            $logger->info($logf->format("Configuration file change detected, refreshing records."));
            last;
        }elsif($agent->will_retry_pscheduler()){
            $logger->info($logf->format("Retrying pscheduler after failure"));
            last;
        }else{
            $end = time;
            $until_next_refresh -= ($end - $start);
            $start = $end;
        }
    }
}

###########
# Exit the program
exit(0);