#!/usr/bin/perl
# notif-launcher -- program that launches new notifications enqueued to disk
# by enqueue.cgi

use strict;
use Getopt::Long;
use Date::Format;       #for current alerts
use NOCpulse::Config;
use NOCpulse::Log::Logger;

use NOCpulse::Notif::NotifIniInterface;
use NOCpulse::Notif::FileQueue;
use NOCpulse::Notif::Alert;
use NOCpulse::Notif::AlertFile;
use NOCpulse::Notif::AlertDB; #for current alerts
use NOCpulse::Notif::EscalatorInterface;

use constant DEF_UNIX_DATE_FMT => '%m-%d-%Y %H:%M:%S';

umask(002);

my $program='notif-launcher';
my $CONFIG = new NOCpulse::Config;
my $CONFIG_INTERVAL = 60; #check for a new config every minute

# Get command line options
my $logopt = {};
GetOptions('log=s%' => $logopt);

#Set up for graceful exit
my $bailout = 0;
$SIG{'INT'} = $SIG{'TERM'} = sub { $bailout = 1; };

# Setup logging
my $LOGDIR         = $CONFIG->get('notification','log_dir');
my $TICKETLOG      = "$LOGDIR/ticketlog";
my $verboselog     = "$LOGDIR/$program.log";
my $LOG_CONFIG     = "( '$program'                      => 3, 
                        'NOCpulse::Notif::Redirect'     => 1, 
                        'NOCpulse::Notif::Alert'        => 1, 
                        'NOCpulse::Notif::FileQueue'    => 1 )";

my ($Log,$stream) = &setup_logging();

# Temporary directory for in progress information
my $TMPDIR = $CONFIG->get('notification','tmp_dir');


# Open the incoming notification queue
my $ALERT_QUEUE_DIR = $CONFIG->get('notification', 'alert_queue_dir');

my $AlertQ = NOCpulse::Notif::FileQueue->new(
        'directory' => $ALERT_QUEUE_DIR,  
        'item_class' => 'NOCpulse::Notif::Alert' );

# Configuration
my $cfg_file     = $CONFIG->get('notification','config_dir') . '/static/notif.ini';
my $notify_cfg   = new Config::IniFiles(-file    => $cfg_file,
                                        -nocase  => 1);

my $SERVER_ID    = $notify_cfg->val('server','serverid');

my $SOCKET_FILENAME="$TMPDIR/escsock";   ## TBD -- replace with config file entry

use vars qw ( $lastcfgcheck $CUSTOMERS $FORMATS $SCHEDULES 
              $METHODS $GROUPS $REDIRECTS);

my $interface=new NOCpulse::Notif::NotifIniInterface;
while (not $interface->checkCustomersExist()) {
  $Log->log(1, "Customers not yet generated by generate-config, sleeping for a while.\n");
  sleep 5;
}
&loadConfig();


# Current Alerts: 

# 1) Connect to the database
my $db = NOCpulse::Notif::AlertDB->new;

# 2) Patch the old probe schema to the new, bug NP4121
my %probe_type_map = (
  'host'     => 'host',
  'service'  => 'check',
  'longlegs' => 'url'
);

# Prevent horrible skip loop
my %skip_list;
use constant SKIP_THRESHOLD => 10;

# Daemon loop

my $lastlog=0;
$Log->log(3,"Starting daemon loop\n");


my @files=NOCpulse::Notif::AlertFile->remove_locks($TMPDIR,$program);
if (scalar(@files)) {
  $Log->log(3,"Removed stale locks\n");
  $Log->log(9,"@files\n");
}


# Main loop
while (1) {

  # If a shutdown was requested, exit gracefully
  if ($bailout) {
     $Log->log(1,"System shutdown requested\n");
     my @files=NOCpulse::Notif::AlertFile->remove_locks($TMPDIR,$program,$$);
     if (scalar(@files)) {
      $Log->log(3,"Removed stale locks\n");
      $Log->log(9,"@files\n");
     }
     $Log->log(1,"System shutdown completed\n");
     exit(0);
  }

  # Check to make sure the escalator is up
  my $status;
  eval {
    my $escalator_if = NOCpulse::Notif::EscalatorInterface->new(
      socket_filename => $SOCKET_FILENAME);
    $status = $escalator_if->is_okay;
  };
  unless ($status) {
    $Log->log(1,"ERROR: Waiting for connection to escalator.  Escalator Interface error: $@");
    sleep 1;
    next;
  }

  # Load up new notifications
  my $alert = $AlertQ->peek;

  if ($alert) {
    my $id=$alert->ticket_id;

    $Log->log(9,"$id opened\n");

#    #open the individual notification log
    my $log_instance = NOCpulse::Log::LogManager->instance;

    #check for old stuff
    if ($alert->is_aged(time())) {
      $Log->log(1,"Dropped Aged: ", $alert->printString, "\n");
      $AlertQ->dequeue;
      next;
    } 

    #assign it to this server
    $alert->server_id($SERVER_ID);

    #note that the alert was started in the log
    $Log->log(1,'Started ', $alert->printString,"\n");

    #process redirects
    $alert->process_redirects;

    my $ref=$alert->destinations;
    if (@$ref) {
    # There are still recipients after processing the redirects
    
        #lock the alert and write it to disk 
        my $filename = "$TMPDIR/$id";
        my $alert_file = NOCpulse::Notif::AlertFile->new( file => $filename,
                                                          alert => $alert );

        unless ($alert_file->acquire_lock) {
          # This should, in theory, never happen
          $skip_list{$filename}++;
          if ($skip_list{$filename} > SKIP_THRESHOLD) {
            $Log->log(3,"Cannot acquire lock on $filename ... dropping\n");
            $AlertQ->dequeue;
          } else {
            $Log->log(3,"Cannot acquire lock on $filename ... skip for now\n");
            $AlertQ->skip;
          }
          next;
        }
        $alert_file->write;
    
        #register the alert if one is left to send
        my $alert_id;

        eval {
          my $escalator_if = NOCpulse::Notif::EscalatorInterface->new(
            socket_filename => $SOCKET_FILENAME);
          $alert_id = $escalator_if->register_alert($filename);
        };

        if ($@ || !$alert_id) {
          my $msg = $@ || "No alert id";
          $Log->log(1,"ERROR: Skipping $filename. Escalator Interface error: $@");
          $AlertQ->skip;
          $alert_file->close_file;
          next;
        }
        $alert->alert_id($alert_id);

        $Log->log(1,'Registered ', $alert->printString,"\n");

        #schedule initial sends with the escalator
        $Log->log(9,"Registering initial sends with the escalator:\n");
        my @sends = $alert->create_initial_sends;

        my @send_ids;
        eval {
          my $escalator_if = NOCpulse::Notif::EscalatorInterface->new(
            socket_filename => $SOCKET_FILENAME);
            @send_ids = $escalator_if->register_sends($alert,@sends);
        };

        if ($@ || !@send_ids) {
          $Log->log(1,"ERROR: Cannot create sends $filename.  Skipping alert.  Escalator Interface error: $@");
          $AlertQ->skip;
          $alert_file->close_file;
          next;
        }

        my @ids = @send_ids; 
        foreach my $send (@sends) {
            $Log->log(9,"\tAssigning send id\n");
            my $send_id = shift(@ids);
            if ($send_id) {
              $Log->log(9,"\tAssigned send id $send_id\n");
              $send->send_id($send_id);
              $send->scheduled_time(time());
              $Log->log(1,"Registered ", $send->printString, "\n");
            } else {
              $Log->log(1,"ERROR: Could not assign send id ", $send->printString, "\n");
            }
        }
        $Log->log(9,"\tSaving alert to file\n");
        $alert_file->close_file;

        my $error;
        eval {
          my $escalator_if = NOCpulse::Notif::EscalatorInterface->new(
            socket_filename => $SOCKET_FILENAME);
            @send_ids = $escalator_if->launch_sends($alert,@send_ids);
          $error = $escalator_if->error;
        };

        if ($@ || !@send_ids) {
          $Log->log(1,"ERROR: Cannot launch sends $filename.  Skipping alert.  Escalator Interface error: $@. $error\n");
          $AlertQ->skip;
          next;
        }
        foreach my $send (@sends) {
          $Log->log(1,"Queued ", $send->printString, "\n");
        }
    } else {
      $Log->log(1,'Completed ', $alert->printString, "\n");
    }   

    #remove the alert from the queue
    $AlertQ->dequeue;

    $Log->log(9,"$id launched\n");

  } else {
    # There is nothing to process, wait and try again
    if (time() - $lastlog >= 60) {
        #time to gripe
        $Log->log(1,"waiting for new notifs...\n");
        $lastlog=time();
    } 

    sleep(1);
 } 

  &checkConfig();
}

################
sub loadConfig {
################
  my $interface=new NOCpulse::Notif::NotifIniInterface;
  
  $Log->log(1,"Loading new configuration\n");

  $Log->log(3, "...loading Customers\n");
  $CUSTOMERS=$interface->buildCustomers();
  
  $Log->log(3, "...loading MessageFormats\n");
  $FORMATS=$interface->buildMessageFormats();
  
  $Log->log(3, "...loading Schedules\n");
  $SCHEDULES=$interface->buildSchedules();

  $Log->log(3, "...loading ContactMethods\n");
  $METHODS=$interface->buildContactMethods($FORMATS,$SCHEDULES);

  $Log->log(3, "...loading ContactGroups\n");
  $GROUPS=$interface->buildContactGroups($METHODS,$CUSTOMERS);

  $Log->log(3, "...loading Redirects\n");
  $REDIRECTS=$interface->buildRedirects($CUSTOMERS,$GROUPS,$METHODS);
}

################
sub loadRedirs {
################
  my $interface=new NOCpulse::Notif::NotifIniInterface;
  
  $Log->log(1,"Loading new redirect configuration\n");

  $Log->log(3, "...loading Redirects\n");
  $REDIRECTS=$interface->buildRedirects($CUSTOMERS,$GROUPS,$METHODS);

}

#################
sub checkConfig {
#################
  #Note the time so we can check the interval
  my $now=time();

  if ($now - $lastcfgcheck <= $CONFIG_INTERVAL) {
    # too soon
    return
  }
    
  $lastcfgcheck=$now;

  my $flagfile = $CONFIG->get('notification', 'config_reload_flag_file');

  if (-f $flagfile) { 
    unlink($flagfile) ||
       $Log->log(1, "\tCouldn't unlink $flagfile: $!");

  &loadConfig();
  }

  $flagfile = $CONFIG->get('notification', 'redirects_reload_flag_file');

  if (-f $flagfile) { 
    unlink($flagfile) ||
       $Log->log(1, "\tCouldn't unlink $flagfile: $!");

    &loadRedirs();
  }
}
 

###################
sub setup_logging {
###################
# Set up logging. The log_config property should be a string
# representing a Perl hash, e.g.
# $program => 2, 'NOCpulse::Notif::Escalator' => 4

    my $stream = NOCpulse::Log::LogManager->instance()->stream(FILE       => $verboselog,
                                                               APPEND     => 1,
                                                               TIMESTAMPS => 1);
    $stream->autoflush(1);

    NOCpulse::Log::LogManager->instance->configure(eval($LOG_CONFIG)) if $LOG_CONFIG;
    # The --log argument from the command line overrides the NOCpulse.ini setting.
    NOCpulse::Log::LogManager->instance->configure(%{$logopt}) if scalar(keys(%{$logopt}));

    my $Log = NOCpulse::Log::Logger->new($program);
    # Don't prefix messages with the caller because this is the top call level.
    $Log->show_method(0);

    return ($Log,$stream);
}

