package Monitor::logfile;

require 5.001;
require Exporter;

use strict;
use vars qw(@ISA @EXPORT $VERSION);
use FileHandle;
use Socket;
use Platforms;

@ISA = qw(Exporter Monitor::Monitor);

my @hostnames;

# determine local system's names
{
    my( $name, $alias ) = gethostbyname( "localhost" );
    my( $name2, $alias2 ) = gethostbyname( $BigSister::common::hostname );
    my( $name3, $alias3 ) = gethostbyname( Platform::hostname(1));
    foreach my $this ($name, split( " ", $alias ), $name2, split( " ", $alias2 ), $name3, split( " ", $alias3 ) ) {
        $this = lc($this);
        push( @hostnames, $this ) unless( grep( $_ eq $this, @hostnames ));
    }
}
   

sub new
{
    my ($this, $logfile, $catchuptime, $catchupsize) = @_;
    my  $class = ref($this) || $this;
    my  $self = {};
    my ($pos,$time,$ctime);

    bless($self, $class);

    print "logfile::new( $logfile )\n" if( $BigSister::common::dl>2 );
    $self->{"file"} = $logfile;
    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime)
					     = $self->stat();
    $self->{"mtime"} = $mtime;
    $self->{"inode"} = $ino;
    $self->{"size"} = $size;
    $self->{"open"} = 0;

    my $fh = $self->{"fh"} = FileHandle->new();
    $self->open();
    # set our own defaults ...
    $self->Monitor::logfile::defaults();
    # ... but let them be overriden by a subclass' defaults
    $self->defaults();
    $self->{"catchuptime"} = $catchuptime if( defined $catchuptime );
    $self->{"catchupsize"} = $catchupsize if( defined $catchupsize );
    $catchupsize = $self->{"catchupsize"};
    $catchuptime = $self->{"catchuptime"};
    $catchupsize = ((defined $size)&&($size<$catchupsize))?$size:$catchupsize;
    if( $self->{"open"} ) {
	print "logfile: catching up $catchuptime sec (max. $catchupsize bytes)\n" if( $BigSister::common::dl>4 );
	$self->seek( -$catchupsize, 2 );
	if( $self->tell() ) {
	    $_ = $self->readline( );
	}
	$ctime = time-$catchuptime;
	while( 1 ) {
	    $pos = $self->tell();
	    $_ = $self->readline();
	    last unless defined $_;
	    ($time) = $self->decode( $_ );
	    next unless( defined $time );
	    last if( $time > $ctime );
	}
	$self->{"pos"} = $pos;
    }
    $self->{"pos"} = 0 if( $BigSister::common::logmode );

    $self->{"htable"} = [];
    $self->{"default"} = [];
    $self->{"defaults"} = {};
    $self->{"stati"} = {};
    $self->{"translations"} = {};
    $self->{"localhost"} = $uxmon::hostname;

    $self->create();

    return($self);
}


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

    my $oldlocalhost = $self->{"localhost"};
    return if( $oldlocalhost eq $name );
    $self->{"localhost"} = $name;
    $self->{"stati"}->{$name} = $self->{"stati"}->{$oldlocalhost};
    delete $self->{"stati"}->{$oldlocalhost};
}


# These methods may be overridden

sub open {
    my( $self ) = @_;

    if( $self->{"file"} ) {
	$self->{"open"} = open( $self->{"fh"}, "<".$self->{"file"} );
	$self->{"openerror"} = ($self->{"open"})?"":($self->{"file"}.": ".$!);
    }
    else {
	$self->{"open"} = 0;
	$self->{"openerror"} = "no file specified";
    }
    return( $self->{"open"} );
}


sub seek {
    my( $self, $pos, $whence ) = @_;

    return( seek( $self->{"fh"}, $pos, $whence ) );
}


sub tell {
    my( $self ) = @_;

    return( tell( $self->{"fh"} ) );
}


sub stat {
    my( $self ) = @_;

    return( stat( $self->{"file"} ) );
}


sub create {
    my( $self ) = @_;
    $self->{"stati"}->{$self->{"localhost"}} = {};
}


sub defaults {
    my( $self ) = @_;

    $self->{"catchuptime"} = 3600;
    $self->{"catchupsize"} = 400000;
    $self->{"defaultitem"} = "msgs";
    $self->{"defaultduration"} = 6;
}


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

    if( $source =~ /^[\d\.]+$/ ) {
	my $name = gethostbyaddr( inet_aton($source), AF_INET );
	$source = $name if( $name );
    }
    my $lcsource = lc( $source );
    if( grep( $lcsource eq $_, @hostnames ) ) {
        $source = $self->{"localhost"};
    }
    return( $source );
}


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

    print "logfile: decodeing ".$line."\n" if( $BigSister::common::dl>7 );
    return( undef, $line );
    # return( time, text, host, item )
}


sub readline {
    my( $self ) = @_;
    my( $data, $fh );

    $fh = $self->{"fh"};
    $data = <$fh>;
    $data || return undef;
    chomp $data;
    return( $data );
}

# ... while the methods below should be generic


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

    $self->{"translations"}->{$host} = $alias;
    $self->{"stati"}->{$alias} = {} unless( defined $self->{"stati"}->{$alias} );
}


sub default {
    my( $self, $host, $item, $status, $text ) = @_;

    $host = "*" unless $host;
    $item = $self->{"defaultitem"} unless( $item );
    $status = 3 unless( defined $status );
    $text = $self->{"defaultitem"}." OK" unless( defined $text );
    if( $host eq "*" ) {
	push( @{$self->{"default"}}, [ $item, $status, $text ] );
    }
    else {
	unless( defined $self->{"defaults"}->{$host} ) {
	    $self->{"defaults"}->{$host} = [];
	}
	push( @{$self->{"defaults"}->{$host}}, [ $item, $status, $text ] );
	$self->{"stati"}->{$host} = {} unless( defined $self->{"stati"}->{$host} );
    }
}


sub add_check {
    my( $self, $item, $pattern, $status, $duration, $func, $continue, $id ) = @_;
    my( $htable ) = $self->{"htable"};

    $item = $self->{"defaultitem"} unless( $item );
    $duration = $self->{"defaultduration"}*60 unless( $duration );
    $func = $self->{"defaultitem"}." FAILURE" unless( $func );
    $continue = 0 unless( $continue );
    print "logfile: adding check item:$item, status:$status, duration:$duration, $pattern\n" if( $BigSister::common::dl>4 );
    push( @$htable, [ $item, $pattern, $status, $duration, $func, $continue, $id ] );
}



sub log {
    my( $self ) = @_;

    $self->{"logmode"} = 1;
    $self->{"still_logging"} = 0;
    $self->check;
    $self->{"logmode"} = 0;
    return( $self->{"still_logging"} );
}


sub check {
    my( $self ) = @_;
    my $fh = $self->{"fh"};
    my $htable = $self->{"htable"};
    my( $readline );

    if( $self->{"open"} ) {
	my $check;
	my $id;
	my $text;
	my @ret;
	my( $item, $pattern, $status, $duration, $func, $continue, $quote );

	$self->seek( $self->{"pos"}, 0 );
	while( 1 ) {
	    $_ = $self->readline();
	    last unless defined $_;
	    $self->{"still_logging"} = 1;
	    print "logfile read: $_\n" if( $BigSister::common::dl>7 );
	    my( $time, $message, $source, $force_item ) = $self->decode( $_ );
	    $source = $self->{"localhost"} unless( defined $source );
	    $_ = $message;
	    $source = $self->normalize_host( $source );
	    ($source = $self->{"translations"}->{$source}) if( $self->{"translations"}->{$source} );
	    print "logfile: ".(scalar localtime($time))." (from $source): $_\n" if( $BigSister::common::dl>4 );
	    ($time = time) unless( $time );
	    $self->{"logtime"} = $time;

	    foreach $check (@$htable) {
		undef $id;
		( $item, $pattern, $status, $duration, $func, $continue ) = @$check;
		print "logfile: comparing $pattern\n" if( $BigSister::common::dl>9 );
		if( /$pattern/ism ) {
		    if( ref($func) ) {
			@ret = &$func;
			next unless( defined $ret[0] );
			$text = shift @ret;
			$item = $ret[0] if( defined $ret[0] );
			($item = $force_item) if( defined $force_item );
			$status = $ret[1] if( defined $ret[1] );
			$duration = $ret[2] if( defined $ret[2] );
			$continue = $ret[3] if( defined $ret[3] );
			$id = $ret[4] if( defined $ret[4] );
		    }
		    else {
			$text = $func;
		    }
		    $id = $text unless( $id );
		    if( $status == -1 ) {
			print "clearing $id\n" if( $BigSister::common::dl>1 );
			my $stati = $self->{"stati"}->{$source};
			my $key;
			$quote = 1;
			if( substr( $id, 0, 1 ) eq "+" ) {
			    $id = substr( $id, 1 );
			    $quote = 0;
			}
			foreach $key (keys %$stati) {
			    if( ($quote && ($key =~ /^\Q$id\E$/)) || ((! $quote) && ($key =~ /^$id$/)) ) {
				delete $stati->{$key};
			    }
			}
		    }
		    else {
			print "adding $id:$item, $status, $text for $duration secs\n" if( $BigSister::common::dl>1 );
			$self->{"stati"}->{$source}->{$id} = [ $item, $status, $time+$duration, $text ];
		    }
		    last unless( $continue );
		}
	    }
	    last if( $self->{"logmode"} );
	}
	$self->{"pos"} = $self->tell();
    }

    my $host;
    my $hosts = $self->{"stati"};
    if( $self->{"logmode"} ) {
	&uxmon::set_time($self->{"logtime"});
    }
    foreach $host (keys %$hosts) {
	my $key;
	my $stati = $self->{"stati"}->{$host};
	my $reports = 0;
	my $rep;
	my( $item, $status, $timegone, $text );
	my %reported = ();

	foreach $key (keys %$stati) {
	    $rep = $$stati{$key};
	    ($item, $status, $timegone, $text) = @$rep;
	    if( $timegone<time ) {
		delete $$stati{$key};
	    }
	    else {
	    	if ( $item =~ /^\S+\.\S+$/ )
		{
		    $self->report( $item, $status, $text );
		}
		else
		{
		    $self->report( $host.'.'.$item, $status, $text );
		};
		$reports++;
		$reported{$item} = 1;
	    }
	}
	my $defaults;
	if( defined $self->{"defaults"}->{$host} ) {
	    $defaults = $self->{"defaults"}->{$host};
	}
	else {
	    $defaults = $self->{"default"};
	}
	foreach my $default (@$defaults) 
	{
	    my $repItem = $host . "." . $default->[0];

	    if ( $default->[0] =~ /^\S+\.\S+$/ )
	    {
	    	$repItem = $default->[0];
	    };

	    if( not defined( $self->{"open"} ) )
	    {
		$self->report( $repItem, 1,
			       "Cannot open the log: " 
			       . ($self->{"openerror"}) ) 
	    }
	    elsif( not $reported{$default->[0]} ) 
	    {
		$self->report( $repItem, $default->[1], $default->[2] );
	    };
	}
    }

    if( $self->new_file ) { $self->check; }
}



sub new_file {
    my( $self ) = @_;
    my $fh = $self->{"fh"};

    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime)
					 = $self->stat();
    if( ($ino != $self->{"inode"}) || (! $self->{"open"}) ) {
	print "logfile: (re-)opening ".$self->{"file"}."\n";
	$self->{"mtime"} = $mtime;
	$self->{"inode"} = $ino;
	$self->{"size"} = $size;
	$self->{"open"} = $self->open( );
	$self->{"pos"} = 0;
	return $self->open();
    }
    return 0;
}


1;
