#!/usr/bin/perl

use strict;
use warnings;

=head1 NAME

perfsonar_meshconfig_guagent - Reads remote mesh and generates maddash configuration

=head1 DESCRIPTION

This daemon does the following:
    * Reads remote test definitions
    * Manages creation, deletion and maintenance all test definitions in MaDDash
=cut

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

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

use perfSONAR_PS::MeshConfig::GUIAgent;
use perfSONAR_PS::Utils::Daemon qw/daemonize setids lockPIDFile unlockPIDFile/;

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

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 ( not $CONFIG_FILE ) {
    print "Error: no configuration file specified\n";
    exit( -1 );
}

my %conf = Config::General->new( $CONFIG_FILE )->getall();

if ( not $PIDFILE ) {
    $PIDFILE = $conf{"pid_file"};
}

if ( not $PIDFILE ) {
    $PIDFILE = "/var/run/perfsonar-meshconfig-guiagent.pid";
}

( $status, $res ) = lockPIDFile( $PIDFILE );
if ( $status != 0 ) {
    print "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 ( not $RUNAS_GROUP ) {
    if ( $conf{"group"} ) {
        $RUNAS_GROUP = $conf{"group"};
    }
}

if ( not $RUNAS_USER ) {
    if ( $conf{"user"} ) {
        $RUNAS_USER = $conf{"user"};
    }
}

if ( $RUNAS_USER and $RUNAS_GROUP ) {
    if ( setids( USER => $RUNAS_USER, GROUP => $RUNAS_GROUP ) != 0 ) {
        print "Error: Couldn't drop privileges\n";
        exit( -1 );
    }
}
elsif ( $RUNAS_USER or $RUNAS_GROUP ) {

    # they need to specify both the user and group
    print "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 $logger;
if ( not defined $LOGGER_CONF or $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 => '%d (%P) %p> %F{1}:%L %M - %m%n',
    );

    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("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("Died: ".join( '', @_ ));
    $insig = 0;
    return;
};

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

unlockPIDFile( $fileHandle );

##
# Monitor config files for changes
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" ;

##
# Start main program loop
while(1){
    ##
    # Load configuration
    eval{
        %conf = Config::General->new( $CONFIG_FILE )->getall();
    };
    if($@){
        $logger->error("Error reading $CONFIG_FILE, proceeding with defaults. Caused by: $@");
        %conf = ();
    }
    
    unless ($conf{"check_interval"}) {
        $logger->debug( "No service check interval specified. Defaulting to 60 minutes" );
        $conf{"check_interval"} = 3600;
    }
    
    unless ($conf{"check_config_interval"}) {
        $logger->debug( "No configuration file check interval specified. Defaulting to 60 seconds" );
        $conf{"check_config_interval"} = 60;
    }
    my $until_next_file_check = $conf{"check_config_interval"};
    
    my $admin_emails;
    if ($conf{admin_email}) {
        unless (ref($conf{admin_email}) eq "ARRAY") {
            $conf{admin_email} = [ $conf{admin_email} ];
        }
    }

    if ($conf{mesh}) {
        unless (ref($conf{mesh}) eq "ARRAY") {
            $conf{mesh} = [ $conf{mesh} ];
        }
    }
    
    #untaint maddash file
    my $maddash_yaml = $1 if($conf{maddash_yaml} =~ /(.+)/);
    
    ##
    # Check remote test definitions
    my $start = time;
    eval{
        my $agent = perfSONAR_PS::MeshConfig::GUIAgent->new();
        $agent->init({
                meshes               => $conf{mesh},
                maddash_yaml         => $maddash_yaml,
                maddash_options      => $conf{maddash_options},
                from_address         => $conf{email_from_address},
                administrator_emails => $conf{admin_email},
                send_error_emails    => $conf{send_error_emails},
                send_error_emails_to_mesh => $conf{send_error_emails_to_mesh},
        });

        $agent->run();
        $inotify->poll(); #clear out any events caused by changes from above
    };
    if($@){
        $logger->error("Problem reading remote meshes: $@");
    }

    ##
    # Sleep until its time to look for file updates or time to refesh
    my $end = time;
    my $until_next_refresh = $conf{"check_interval"} - ($end - $start);
    $start = $end;
    $logger->debug("Time until next record refresh is $until_next_refresh seconds");
    while($until_next_refresh > 0){
        sleep($until_next_refresh < $until_next_file_check ? $until_next_refresh : $until_next_file_check);
        if($inotify->poll()){
            $logger->info("Configuration file change detected, refreshing records.");
            last;
        }else{
            $end = time;
            $until_next_refresh -= ($end - $start);
            $start = $end;
        }
    }
}

exit(0);
