###########################################################################
#                                                                         #
# Nagios::StatusLog, Nagios::(Service|Host|Program)::Status               #
# Written by Albert Tobey <tobeya@cpan.org>                               #
# Copyright 2003, Albert P Tobey                                          #
#                                                                         #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the   #
# Free Software Foundation; either version 2, or (at your option) any     #
# later version.                                                          #
#                                                                         #
# This program is distributed in the hope that it will be useful, but     #
# WITHOUT ANY WARRANTY; without even the implied warranty of              #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       #
# General Public License for more details.                                #
#                                                                         #
###########################################################################
package Nagios::StatusLog;
use Carp;
use strict qw( subs vars );
use warnings;
use Symbol;
our $Log_Cache_Enabled = 1;

# for an explanation of what happens in this block, look at Nagios::Object
BEGIN {
    my %_tags = (
        Service => [qw(host_name description status current_attempt state_type last_check next_check check_type checks_enabled accept_passive_service_checks event_handler_enabled last_state_change problem_has_been_acknowledged last_hard_state time_ok time_unknown time_warning time_critical last_notification current_notification_number notifications_enabled latency execution_time flap_detection_enabled is_flapping percent_state_change scheduled_downtime_depth failure_prediction_enabled process_performance_data obsess_over_service plugin_output)],

        Host => [qw( host_name status last_check last_state_change problem_has_been_acknowledged time_up time_down time_unreachable last_notification current_notification_number notifications_enabled event_handler_enabled checks_enabled flap_detection_enabled is_flapping percent_state_change scheduled_downtime_depth failure_prediction_enabled process_performance_data plugin_output )],

        Program => [qw( program_start nagios_pid daemon_mode last_command_check last_log_rotation enable_notifications execute_service_checks accept_passive_service_checks enable_event_handlers obsess_over_services enable_flap_detection enable_failure_prediction process_performance_data )]
    );

    GENESIS: {
        no warnings;
        # create the Nagios::*::Status packages at compile time
	    foreach my $key ( keys(%_tags) ) {
	        my $pkg = 'Nagios::'.$key.'::Status::';
	
	        # store the list of tags for this package to access later
	        do { ${"${pkg}tags"} = $_tags{$key} };

            # modify @ISA for each class
            my $isa = do { \@{"${pkg}ISA"} };
            push( @$isa, 'Nagios::StatusLog' );
	
	        foreach my $method ( @{$_tags{$key}} ) {
	            *{$pkg.$method} = sub { $_[0]->{$method} };
	        }
        }
    }
}

=head1 NAME

Nagios::StatusLog, Nagios::(Service|Host|Program)::Status

=head1 DESCRIPTION

Reads the Nagios status log and returns ::Status objects that can
be used to get status information about a host.

 my $log = Nagios::StatusLog->new( "/var/opt/nagios/status.log" );
 $localhost = $log->host( "localhost" );
 print "status of localhost is now ",$localhost->status(),"\n";
 $log->update();
 print "status of localhost is now ",$localhost->status(),"\n";

=head1 METHODS

=over 4

=item new()

Create a new Nagios::StatusLog instance.  The object will
be initialized for you (using $self->update()).
 Nagios::StatusLog->new( "/var/opt/nagios/status.log" );

=cut

sub new {
    my $type = shift;
    my $logfile = $_[0];
    if ( @_ == 2 && $_[0] =~ /^filename$/i ) {
       $logfile = $_[1];
    }
        
    if ( !defined($logfile) || !-r $logfile ) {
        die "could not open $logfile for reading: $!";
    }
    my $self = bless( { LOGFILE => $logfile }, $type );
    $self->update();
    return $self;
}

=item update()

Updates the internal data structures from the logfile.
 $log->update();

=cut

sub update ($) {
    my $self = shift;

    # break the line down into a hash, return a reference
    sub hashline ($ $ $) {
        my( $line, $no, $ar ) = @_;
        my @parts = split(/;/, $$line, $no+1);
        # create the hash using the constant array (defined at top
        # of this file) and the split line
        my %data = map { $ar->[$_] => $parts[$_] } 0..$no;
        return \%data;
    }

    my $log_fh = gensym;
    open( $log_fh, "<$self->{LOGFILE}" )
        || croak "could not open file $self->{LOGFILE} for reading: $!";
    my @LOG = <$log_fh>;
    close( $log_fh );

    for ( my $i=0; $i<@LOG; $i++ ) {
        chomp( $LOG[$i] );
        $LOG[$i] =~ s/#.*$//;
        next if ( !defined($LOG[$i]) || !length($LOG[$i]) );
        $LOG[$i] =~ m/^(\[\d+])\s+([A-Z]+);(.*)$/;
        my( $ts, $type, $line ) = ( $1, $2, $3 );

        # set some variables to switch between SERVICES|HOST|PROGRAM
        # $no must be the number of elements - 1 (because arrays start on 0)

        my( $ldata, $ref ) = ( {}, undef );
        if ( $type eq 'SERVICE' ) {
            # let the hashline() function do the work of creating the hashref
            $ldata = hashline( \$line, 30, Nagios::Service::Status->list_tags() );

            # if it already exists, we'll copy data after this if/else tree
            if ( !exists($self->{$type}{$ldata->{host_name}}{$ldata->{description}}) ) {
                $self->{$type}{$ldata->{host_name}}{$ldata->{description}} = $ldata;
            }

            # 1st time we've seen this combination, use the new svc hashref
            else {
                $ref = $self->{$type}{$ldata->{host_name}}{$ldata->{description}};
            }
        }
        elsif ( $type eq 'HOST' ) {
            $ldata = hashline( \$line, 19, Nagios::Host::Status->list_tags() );
            if ( !exists($self->{$type}{$ldata->{host_name}}) ) {
                $self->{$type}{$ldata->{host_name}} = $ldata;
            }
            else {
                $ref = $self->{$type}{$ldata->{host_name}};
            }
        }
        elsif ( $type eq 'PROGRAM' ) {
            $ldata = hashline( \$line, 12, Nagios::Program::Status->list_tags() );
            if ( !defined($self->{$type}) ) {
                $self->{$type} = $ldata;
            }
            else {
                $ref = $self->{$type};
            }
        }
        else { croak "unknown tag ($type) in logfile"; }

        # update existing data without changing the location the reference points to
        if ( defined($ref) ) {
            foreach my $key ( keys(%$ldata) ) { $ref->{$key} = $ldata->{$key}; }
        }
    }
    1;
}

sub list_tags {
    my $type = ref($_[0]) ? ref($_[0]) : $_[0];
    my $listref = ${"$type\::tags"};
    return wantarray ? @$listref : $listref;
}

=item service()

Returns a Nagios::Service::Status object.  Input arguments can be a host_name and description list, or a Nagios::Service object.
 my $svc_stat = $log->service( "localhost", "SSH" );
 my $svc_stat = $log->service( $localhost_ssh_svc_object );

Nagios::Service::Status has the following accessor methods:
 host_name
 description
 status 
 current_attempt
 state_type
 last_check next_check
 check_type
 checks_enabled
 accept_passive_service_checks
 event_handler_enabled
 last_state_change
 problem_has_been_acknowledged
 last_hard_state
 time_ok
 current_notification_number  
 time_warning
 time_critical
 process_performance_data
 notifications_enabled
 latency
 scheduled_downtime_depth 
 is_flapping
 plugin_output
 percent_state_change
 execution_time
 time_unknown
 failure_prediction_enabled
 last_notification
 obsess_over_service
 flap_detection_enabled 

=cut

sub service {
    my( $self, $host, $service ) = @_;

    if ( ref $host eq 'Nagios::Host' ) {
        $host = $host->host_name;
    }
    # allow just a service to be passed in
    if ( ref $host eq 'Nagios::Service' ) {
        $service = $host;
        $host = $service->host_name;
    }
    if ( ref $service eq 'Nagios::Service' ) {
        $service = $service->service_description;
    }

    confess "host \"$host\" does not seem to be valid"
        if ( !$self->{SERVICE}{$host} );
    confess "service \"$service\" does not seem to be valid on host \"$host\""
        if ( !$self->{SERVICE}{$host}{$service} );

    bless( $self->{SERVICE}{$host}{$service}, 'Nagios::Service::Status' );
}

=item list_services()

Returns an array of all service descriptions in the status log.  Services that
may be listed on more than one host are only listed once here.

 my @all_services = $log->list_services;

=cut

sub list_services {
    my $self = shift;
    my %list = ();
    foreach my $host ( keys %{$self->{SERVICE}} ) {
        foreach my $service ( keys %{$self->{SERVICE}{$host}} ) {
            $list{$service} = 1;
        }
    }
    return keys %list;
}

=item list_services_on_host()

Returns an array of services descriptions for a given host.

 my @host_services = $log->list_services_on_host($hostname);
 my @host_services = $log->list_services_on_host($nagios_object);

=cut

sub list_services_on_host {
    my( $self, $host ) = @_;
    if ( ref $host =~ /^Nagios::/ && $host->can('host_name') ) {
        $host = $host->host_name;
    }
    return keys %{$self->{SERVICE}{$host}};
}

=item host()

Returns a Nagios::Host::Status object.  Input can be a simple host_name, a Nagios::Host object, or a Nagios::Service object.
 my $hst_stat = $log->host( 'localhost' );
 my $hst_stat = $log->host( $host_object );
 my $hst_stat = $log->host( $svc_object );

Nagios::Host::Status has the following accessor methods:
 host_name
 status
 last_check
 last_state_change
 problem_has_been_acknowledged
 time_up
 time_down
 time_unreachable
 last_notification
 current_notification_number
 notifications_enabled
 event_handler_enabled
 checks_enabled
 flap_detection_enabled
 is_flapping
 percent_state_change
 scheduled_downtime_depth
 failure_prediction_enabled
 process_performance_data
 plugin_output

=cut

sub host {
    my( $self, $host ) = @_;

    if ( ref $host =~ /^Nagios::(Host|Service)$/ ) {
        $host = $host->host_name;
    }

    confess "host \"$host\" does not seem to be valid"
        if ( !$self->{HOST}{$host} );

    bless( $self->{HOST}{$host}, 'Nagios::Host::Status' );
}

=item list_hosts()

Returns a simple array of host names (no objects).

 my @hosts = $log->list_hosts;

=cut

sub list_hosts { keys %{$_[0]->{HOST}}; }

=item program()

Returns a Nagios::Program::Status object. No arguments.
 my $prog_st = $log->program;

Nagios::Program::Status has the following accessor methods:
 program_start
 nagios_pid
 daemon_mode
 last_command_check
 last_log_rotation
 enable_notifications
 execute_service_checks
 accept_passive_service_checks
 enable_event_handlers
 obsess_over_services
 enable_flap_detection
 enable_failure_prediction
 process_performance_data

=cut

sub program ($) { bless( $_[0]->{PROGRAM}, 'Nagios::Program::Status' ); }

sub write {
    my( $self, $filename ) = @_;
    my $ts = time;

    my $fh = gensym;
    open( $fh, ">$filename" )
        || die "could not open file \"$filename\" for writing: $!";

    print $fh, "[$ts] PROGRAM;", Nagios::Program::Status->csvline( $self->{PROGRAM} ), "\n";

    foreach my $host ( keys %{$self->{HOST}} ) {
        print $fh "[$ts] HOST;", Nagios::Host::Status->csvline( $self->{HOST}{$host} ), "\n";
    }
    foreach my $host ( keys %{$self->{SERVICE}} ) {
        foreach my $svc ( keys %{$self->{SERVICE}{$host}} ) {
            my $ref = $self->{SERVICE}{$host}{$svc};
            print $fh "[$ts] SERVICE;", Nagios::Service::Status->csvline( $ref ), "\n";
        }
    }

    close( $fh );
}

sub csvline {
    my $self = shift;
    my $data = shift || $self;
    join( ';', map { $data->{$_} } ($self->list_tags) ); 
}

=back

=head1 STRUCTURE

This module contains 4 packages: Nagios::StatusLog, Nagios::Host::Status,
Nagios::Service::Status, and Nagios::Program::Status.  The latter 3 of
them are mostly generated at compile-time in the BEGIN block.  The
accessor methods are real subroutines, not AUTOLOAD, so making a ton
of calls to this module should be fairly quick.  Also, update() is set
up to only do what it says - updating from a fresh logfile should not
invalidate your existing ::Status objects.

=head1 AUTHOR

Al Tobey <tobeya@tobert.org>

=head1 SEE ALSO

Nagios::Host Nagios::Service

=cut

package Nagios::Service::Status;

package Nagios::Host::Status;

package Nagios::Program::Status;

1;
