package Procmon;

use strict;
use Carp;
require Exporter;
use File::Basename;
use Cwd;

@Procmon::ISA = qw( Exporter );

$Procmon::PROCMON = 'procmon@epinions.com';
$Procmon::START_TIME = time;
@Procmon::OPT_PARAMETERS = qw( host path estimation_time description );
@Procmon::REQ_PARAMETERS = qw( name notify periodic_requirement group 
                start finish status );

foreach my $ct ( qw( /bin/crontab /usr/bin/crontab ) ) {
    if ( -x $ct ) {
        $Procmon::CRONTAB = $ct;
        last;
    }
}
croak "Could not locate crontab binary" unless $Procmon::CRONTAB;

foreach my $sm ( qw( /usr/sbin/sendmail /usr/lib/sendmail /bin/mail ) ) {
    if ( -x $sm ) {
        $Procmon::SENDMAIL = $sm;
        last;
    }
}
croak "Could not locate sendmail binary" unless $Procmon::SENDMAIL;

=head1 NAME

Procmon - Simple procmon interface

=head1 SYNOPSIS

    use Procmon;
    $pm = new Procmon;
    ...
    $pm->status( 'FAILURE' );
    $pm->mail_report;

=head1 DESCRIPTION

The module is a simple interface to send the required fields to procmon.
It will auto-populate all required fields if possible.

It can print just a start message, just a stop message, or a complete
report. It can also mail the complete report to the procmon system.

The following methods are supported:

=over 4

=item C<new>

Returns a new Procmon object.

=item C<name>

Set or return the program name reported to procmon. This is normally taken
from $0, and should be changed if the value is incorrect.

=item C<finish>

Set or return the program's finish time. Use a value understood by
procmon, such as c<scalar(localtime)>. This is automatically set by the
C<report>, C<report_finish> and C<mail_report>.

=item C<start>

Set or return the program's start time. This is automatically set when the
module is first loaded.

=item C<description>

Set or return the description sent to procmon. This field is optional and
blank by default.

=item C<periodic_requirement>

Set or return the periodic_requirement sent to procmon. This field is set
automatically from the first matching line in the current user's crontab.

=item C<estimation_time>

Set or return the estimation_time sent to procmon. This field is optional
and blank by default.

=item C<notify>

Set or return the notify line sent to procmon. This field is optional
and blank by default.

=item C<text>

Set or return the text line sent to procmon. This field is optional
and blank by default.

=item C<status>

Set or return the status line sent to procmon. This field is set to
'SUCCESS' by default. For a failure condition, it must be sent to 'FAILURE'
before using a report method.

=item C<filehandle>

Set or return the filehandle used by C<report>, C<report_finish> and
C<report_start>. C<STDOUT> is used by default.

=item C<path>

Set or return the path line sent to procmon. This field is required
and set to the current directory by default.

=item C<report>

Set the finish time if not set already and print the report.

=item C<report_start>

Print the report, excluding the finish time.

=item C<report_finish>

Print the report, excluding the start time.

=item C<mail_report>

Set the finish time and mail a report to procmon@epinions.com

=back

=cut

sub new {
    my $class = shift;
    my ( $name, $group, $fh ) = @_;
    my $ref = {};

    if ( ! $name ) {
        $name = basename( $0 );
    }

    $ref->{name} = $name;
    $ref->{start} = localtime( $Procmon::START_TIME );
    $ref->{group} = $group;
    $ref->{name} = $name;
    $ref->{path} = cwd();
    $ref->{host} = `hostname`; chomp $ref->{host};
    $ref->{filehandle} = $fh;
    $ref->{status} = 'SUCCESS';
    bless $ref, $class;

    $ref->{periodic_requirement} = $ref->get_crontab;

    return $ref;
}

sub finish {
    my $ref = shift;

    if ( @_ ) {
        $ref->{finish} = join ' ', @_;
        $ref->{finish} =~ s/\s*(\r|\n)\s*/ /gs;
    }
    return $ref->{finish};
}

sub name {
    my $ref = shift;

    if ( @_ ) {
        $ref->{name} = join ' ', @_;
        $ref->{name} =~ s/\s*(\r|\n)\s*/ /gs;
    }
    return $ref->{name};
}

sub start {
    my $ref = shift;

    if ( @_ ) {
        $ref->{start} = join ' ', @_;
        $ref->{start} =~ s/\s*(\r|\n)\s*/ /gs;
    }
    return $ref->{start};
}

sub description {
    my $ref = shift;

    if ( @_ ) {
        $ref->{description} = join ' ', @_;
        $ref->{description} =~ s/\s*(\r|\n)\s*/ /gs;
    }
    return $ref->{description};
}

sub periodic_requirement {
    my $ref = shift;

    if ( @_ ) {
        $ref->{periodic_requirement} = join ' ', @_;
        $ref->{periodic_requirement} =~ s/\s*(\r|\n)\s*/ /gs;
    }
    return $ref->{periodic_requirement};
}

sub estimation_time {
    my $ref = shift;

    if ( @_ ) {
        $ref->{estimation_time} = join ' ', @_;
        $ref->{estimation_time} =~ s/\s*(\r|\n)\s*/ /gs;
    }
    return $ref->{estimation_time};
}

sub notify {
    my $ref = shift;

    if ( @_ ) {
        $ref->{notify} = join ' ', @_;
        $ref->{notify} =~ s/\s*(\r|\n)\s*/ /gs;
    }
    return $ref->{notify};
}

sub group {
    my $ref = shift;

    if ( @_ ) {
        $ref->{group} = join ' ', @_;
        $ref->{group} =~ s/\s*(\r|\n)\s*/ /gs;
    }
    return $ref->{group};
}

sub text {
    my $ref = shift;

    if ( @_ ) {
        $ref->{text} = join ' ', @_;
        $ref->{text} =~ s/\s*(\r|\n)\s*/ /gs;
    }
    return $ref->{text};
}

sub status {
    my $ref = shift;

    if ( @_ ) {
        $ref->{status} = uc( join ' ', @_ );
        $ref->{status} =~ s/\s*(\r|\n)\s*/ /gs;
    }
    return $ref->{status};
}

sub filehandle {
    my $ref = shift;

    if ( @_ ) {
        ( $ref->{filehandle} ) = @_;
    }
    return $ref->{filehandle};
}

sub path {
    my $ref = shift;

    if ( @_ ) {
        $ref->{path} = join ' ', @_;
        $ref->{path} =~ s/\s*(\r|\n)\s*/ /gs;
    }
    return $ref->{path};
}

sub mail_report {
    my $ref = shift;
    my ( $exclude ) = @_;
    my ( $fh ) = $ref->filehandle;

    open REPORT, "|$Procmon::SENDMAIL -t"
            or croak "Can not execute $Procmon::SENDMAIL";
    
    print REPORT "To: $Procmon::PROCMON\n";
    print REPORT "Subject: Procmon report for " . $ref->name . "\n\n";

    $ref->filehandle( \*REPORT );
    $ref->report;

    close REPORT;
    $ref->filehandle( ( $fh ? $fh : undef ) );

    return 1;
}

sub report {
    my $ref = shift;
    my ( $status ) = @_;

    $ref->status( $status ) if $status;
    $ref->finish( scalar( localtime ) ) unless $ref->finish;
    $ref->print_report( );
 
}

sub report_start {
    my $ref = shift;

    $ref->print_report( { finish => 1, status => 1 } );
}

sub report_finish {
    my $ref = shift;
    my ( $status ) = @_;

    $ref->status( $status ) if $status;
    $ref->finish( scalar( localtime ) ) unless $ref->finish;
    $ref->print_report( { start => 1 } );
}

sub print_report {
    my $ref = shift;
    my ( $exclude ) = @_;
    my ( $fh );

    $fh = $ref->filehandle || \*STDOUT;
    
    foreach my $param ( @Procmon::REQ_PARAMETERS, @Procmon::OPT_PARAMETERS ) {
        next if $exclude->{$param};
        if ( defined $ref->{$param} ) {
            print $fh uc($param) . ': ' . $ref->{$param} . "\n";
        }
    }

    return 1;
}

sub get_crontab {
    my $ref = shift;
    my ( $match, $name, $cwd, $schedule );

    $name = $ref->name;
    $cwd  = $ref->cwd;

    open CRONTAB, "$Procmon::CRONTAB -l|" or die "Could not exec $Procmon::CRONTAB : $!";

    while ( my $line = <CRONTAB> ) {
# Ignore comments.
        next if $line =~ /^\s*#/;
        next if $line =~ /^\s*$/;
        chomp $line;
# We're starting a new command, hopefully
        if ( $line =~ /^[\d\*\/]+\s+[\d\*\/]+\s+[\d\*\/]+\s+[\d\*\/]+\s+[\d\*\/]+\s+/ ) { 
            $schedule = $line;
        }
        else {
            $schedule .= $line;
        }
# We'll, we've found at least one entry for this program ...
# This could be prone to error
        if ( $schedule =~ /\b$name\b/  && $schedule =~ /$cwd/ ) {
            $match = 1;
            last;
        }
    }
    close CRONTAB;
    return $schedule if $match;
    return undef;
}

1;
