#!/usr/bin/perl

BEGIN {
  (my $dir = $0) =~ s/\/?[^\/]+$//;
  die $@ if($@);
};

use strict;
use Time::HiRes qw( gettimeofday tv_interval sleep );
use POSIX qw( :sys_wait_h setsid );
use Getopt::Long;
use Data::Dumper;
use vars qw($config_file $debug $status_file $interface $port $config
            $status $update);

use Resmon::Config;
use Resmon::ExtComm;
use Resmon::Status;
use Resmon::Module;

GetOptions(
  "i=s" => \$interface,
  "p=i" => \$port,
  "c=s" => \$config_file,
  "d"   => \$debug,
  "f=s" => \$status_file,
  "u"   => \$update,
);

if ($update) {
    use Resmon::Updater;
    (my $resmondir = $0) =~ s/\/?[^\/]+$//;
    exit(Resmon::Updater::update($debug, $resmondir));
}

$config_file ||= "$0.conf";
die "Cannot open configuration file: $config_file" unless (-r $config_file);

sub configure {
  $config = Resmon::Config->new($config_file);
  $config->{statusfile} = $status_file if($status_file);
  $config->{port} = $port if($port);
  $config->{interface} = $interface if($interface);
}

sub reconfigure {
    my $modstatus = "";
    print STDERR "Reloading modules...\n";
    my $modules = $config->{Module};
    while ( my ($key, $value) = each(%$modules) ) {
        my $mod = $value->[0]; # Only need the first of each module
        # Called this way rather than $mod->reload_module() in order to deal
        # with modules that failed to load on startup and won't have a
        # reload_module method.
        my $errs = Resmon::Module::reload_module($mod);
        if ($errs) {
            my $modname = ref($mod) || $mod;
            $modname =~ s/Resmon::Module:://;
            $modstatus .= "$modname ";
            print STDERR " Failed to reload module $modname\n";
            print STDERR $errs;
            print STDERR " This module is no longer available.\n";
        }
    }
    print STDERR "Reloading configuration...\n";
    eval { configure(); };
    if ($@) {
        # The config object is recreated every time we reload, so we shouldn't
        # need to reset this BAD value to empty on a successful load.
        $config->{'configstatus'} = "BAD";
        print STDERR " Failed to reload: ";
        print STDERR $@;
        print STDERR " Continuing with old configuration\n";
    }

    $config->{'modstatus'} = $modstatus;
    $status->purge($config);
}

my $sighup = 0;
sub sighup_handler { $sighup = 1; }
$SIG{'HUP'} = \&sighup_handler;

configure();

my $sigint = 0;
sub sigint_handler { $sigint = 1; }
$SIG{'INT'} = \&sigint_handler;

my $rmlast = undef;
sub wait_interval {
  $rmlast = [gettimeofday] unless defined($rmlast);
  my $elapsed = $config->{interval} - tv_interval($rmlast);
  if($elapsed > 0) {
    sleep($elapsed);
  }
  $rmlast = [gettimeofday];
}

sub reap_zombies {
    my $kid;
    do {
        $kid = waitpid(-1, WNOHANG);
    } while $kid > 0;
}

unless($debug) {
  fork && exit;
  setsid;
  close(STDIN);
  close(STDOUT);
  close(STDERR);
  fork && exit;
}

my $list = [];
$status = Resmon::Status->new($config->{statusfile});
$status->open();
$status->serve_http_on($config->{interface}, $config->{port})
  if($config->{port});

while(1) {
  while(my($module_name, $mod_configs) = each %{$config->{Module}}) {
    my $coderef = undef;
    eval { $coderef = Resmon::Module::fetch_monitor($module_name); };
    foreach my $monobj (@$mod_configs) {
      my $check_rv = 'BAD',
      my $check_mess = 'no data';
      my $starttime = [gettimeofday];
      # Get old status if it hasn't expired
      my ($check_rv, $check_mess) = Resmon::Module::fresh_status_msg($monobj);
      # Otherwise, run the check
      if (!$check_rv) {
        my $timeout = $monobj->{'check_timeout'} || $config->{'timeout'};
        alarm($timeout);
        eval {
          local $SIG{ALRM} = sub { die "alarm\n" };
          if($coderef) {
            ($check_rv, $check_mess) = $coderef->($monobj);
          } else {
            ($check_rv, $check_mess) = $monobj->handler();
          }
        };
        alarm 0;
        # Store the last status for use by fresh_status_msg later
        # Also converts old style status messages
        ($check_rv, $check_mess) =
          Resmon::Module::set_status($monobj, $check_rv, $check_mess);
      }
      my $checkstat = $@;
      my $confighash = {};
      eval { $confighash = $monobj->config_as_hash(); };
      my $results = {
        configuration => $confighash,
        last_runtime_seconds => sprintf("%.6f", tv_interval($starttime)),
      };
      if($checkstat) {
        $results->{state} = 'BAD';
        $results->{message} = "Bad module or problem running handler code.";
        if ($checkstat eq "alarm\n") {
          $results->{message} = "Check timeout";
        }
      } else {
        $results->{state} = $check_rv;
        $results->{message} = $check_mess;
      }
      $status->store($module_name,$monobj->{'object'}, $results);
    }
  }
  $status->close();
  die "Exiting.\n" if($sigint);
  if ($sighup) {
    $sighup = 0;
    reconfigure();
  } else {
    reap_zombies();
    wait_interval();
    reap_zombies();
  }
  die "Exiting.\n" if($sigint);
  print "\n---- ".localtime(time)."----------\n"
   unless $status->open();
}

