#                              -*- Mode: Perl -*- 
# $Basename: Base.pm $
# $Revision: 1.3 $
# Author          : Ulrich Pfeifer
# Created On      : Mon Mar 25 09:58:31 1996
# Last Modified By: Ulrich Pfeifer
# Last Modified On: Tue Apr 29 09:08:33 2003
# Language        : Perl
# 
# (C) Copyright 1996, Universit├Ąt Dortmund, all rights reserved.
# 

package Logfile::Base;
use Carp;
use vars qw($VERSION $nextfh);
use strict;

# $Format: "$\VERSION = sprintf '%5.3f', ($ProjectMajorVersion$ * 100 + ($ProjectMinorVersion$-1))/1000;"$
$VERSION = sprintf '%5.3f', (2 * 100 + (3-1))/1000;

$Logfile::MAXWIDTH = 40;
my ($HaveParseDate, $HaveGetDate, $HaveDateGetDate); 
$nextfh = 'fh000';

sub isafh { 
  my $f = shift; 
  ref $f eq 'GLOB' 
  or ref \$f eq 'GLOB' 
  or (ref $f) =~ /^IO::/ 
}

sub new {
    my $type = shift;
    my %par  = @_;
    my $self = {};
    my $file = $par{File};

    if (ref $par{Group}) {
        $self->{Group} = $par{Group};
    } else {
        $self->{Group} = [$par{Group}];
    }       
    if ($file) {
      if (isafh $file) {
        $self->{Fh} = $file;
      } else {
        *S = "${type}::".++$nextfh;
        $self->{Fh} = *S;
        if ($file =~ /\.gz$/) {
            open(S, "gzip -cd $file|") 
                or die "Could not open $file: $!\n";
        } else {
            open(S, "$file") 
                or die "Could not open $file: $!\n";
        }

      }
    } else {
        $self->{Fh} = *ARGV;
    }
    bless $self, $type || ref($type);
    $self->readfile;
    close S if $self->{File};
    $self;
}

sub norm { $_[2]; }             # dummy

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

    if (ref($group)) {
        join $;, @{$group};
    } else {
        $group;
    }
}

sub key {
    my ($self, $group, $rec) = @_;
    my $key = '';

    if (ref($group)) {
        $key = join $;, map($self->norm($_, $rec->{$_}), @{$group});
    } else {
        $key = $self->norm($group, $rec->{$group});
    }
    $key;
}

sub readfile {
    my $self  = shift;
    my $fh    = $self->{Fh};
    my @group = @{$self->{Group}};
    my $group;

    while (!eof($fh)) {
        my $rec = $self->next;
        last unless $rec;
        for $group (@group) {
            my $gname = $self->group($group);
            my $key = $self->key($group, $rec);

            if (defined $self->{$gname}->{$key}) {
                $self->{$gname}->{$key}->add($rec,$group); # !!
            } else {
                $self->{$gname}->{$key} = $rec->copy;
            }
        }
    }
}

sub report {
    my $self  = shift;
    my %par = @_;
    my $group = $self->group($par{Group});
    my $sort  = $par{Sort} || $group;
    my $rever = (($sort =~ /Date|Hour/) xor $par{Reverse});
    my $list  = $par{List};
    my ($keys, $key, $val, %keys);
    my $mklen  = length($group); 
    my $direction = ($rever)?'increasing':'decreasing';
    my (@list, %absolute);
    my @mklen = map(length($_), split($;, $group));

    croak "No index for $group\n" unless $self->{$group};

    if ($list) {
        if (ref($list)) {
            @list = @{$list};
        } else {
            @list = ($list);
        }
    } else {
        @list = qw(Records);
    }

    @absolute{@list} = (0) x @list;
    $sort =~ s/$;.*//;
    #print STDERR "sort = $sort\n";
    while (($key,$val) = each %{$self->{$group}}) {
        $keys{$key} = $val->{$sort};
        if ($key =~ /$;/) {
            my  @key = split $;, $key;
            for (0 .. $#key) {
                $mklen[$_] = length($key[$_])
                    if length($key[$_]) > $mklen[$_];
            }
            $mklen = $#mklen;
            grep ($mklen += $_, @mklen);
        } else {
            $mklen = length($key) if length($key) > $mklen;
        }
        for (@list) {
          $absolute{$_} += $val->{$_} if defined $val->{$_};
        }
    }
    # chop keys to $Logfile::MAXWIDTH chars maximum;
    grep (($_=($_>$Logfile::MAXWIDTH)?$Logfile::MAXWIDTH:$_), @mklen);
    if ($group =~ /$;/) {
        my @key =  split $;, $group;
        for (0 .. $#key) {
            printf "%-${mklen[$_]}s ", $key[$_];
        }
    } else {
        printf ("%-${mklen}s ", $group);
    }
    for (@list) {
        printf("%16s ", $_);
    }
    print "\n";
    print '=' x ($mklen + (@list * 17));
    print "\n";
    #for $key (keys %keys) {
    #    print STDERR "** $key $keys{$key}\n";
    #}
    for $key (sort {&srt($rever, $keys{$a}, $keys{$b})} 
              keys %keys) {
        my $val = $self->{$group}->{$key};
        if ($key =~ /$;/) {
            my @key =  split $;, $key;
            for (0 .. $#key) {
                printf "%-${mklen[$_]}s ", substr($key[$_],0,$mklen[$_]);
            }
        } else {
            printf "%-${mklen}s ", $key;
        }
        for $list (@list) {
            my $ba = (defined $val->{$list})?$val->{$list}:0;
            if ($absolute{$list} > 0) {
                my $br = $ba/$absolute{$list}*100;
                printf "%9d%6.2f%% ", $ba, $br;
            } else {
                printf "%15s ", $ba;
            }
        }
        print "\n";
        last if defined $par{Top} && --$par{Top} <= 0;
    }
    print "\f";
}

sub srt {
    my $rev = shift;
    my ($y,$x);
    if ($rev) {
        ($x,$y) = @_;
    } else {
        ($y,$x) = @_;
    }

    if ($x =~ /[^\d.]|^$/o or $y =~ /[^\d.]|^$/o) {
        lc $y cmp lc $x;
    } else {
        $x <=> $y;
    }
}

sub keys {
    my $self  = shift;
    my $group = shift;

    keys %{$self->{$group}};
}

sub all {
    my $self  = shift;
    my $group = shift;

    %{$self->{$group}};
}

package Logfile::Base::Record;

BEGIN {
  eval {require GetDate;};
  $HaveGetDate = ($@ eq "") and import GetDate 'getdate';
  unless ($HaveGetDate) {
    eval {require Date::GetDate};
    $HaveDateGetDate = ($@ eq "") and import GetDate 'getdate';
    unless ($HaveDateGetDate) {
      eval {
        require Time::ParseDate;
        sub parsedate { &Time::ParseDate::parsedate(@_) }
      };
      $HaveParseDate = ($@ eq "");
    }
  }
};

unless ($HaveGetDate or $HaveDateGetDate
        or $HaveParseDate) {
    eval join '', <DATA>;
    croak("Could not load my own date parsing: $@")
      if length($@);
}

use Net::Country;

sub new {
    my $type = shift;
    my %par  = @_;
    my $self = {};
    my ($sec,$min,$hours,$mday,$mon,$year, $time);

    %{$self} = %par;

    if ($par{Date}) {
        #print "$par{Date} => ";
        if ($HaveGetDate) {
            $par{Date} =~ s!(\d\d\d\d):!$1 !o;
            $par{Date} =~ s!/! !go;
            $time = getdate($par{Date});
        } elsif ($HaveDateGetDate) {
            $par{Date} =~ s!(\d\d\d\d):!$1 !o;
            $par{Date} =~ s!/! !go;
            $time = Date::GetDate::getdate($par{Date});
        } elsif ($HaveParseDate) {

            $time = parsedate($par{Date},
                                   FUZZY => 1,
                              NO_RELATIVE => 1);
        } else {
            $time = &Time::String::to_time($par{Date});
        }
	($sec,$min,$hours,$mday,$mon,$year) = localtime($time);
        #print "$par{Date} => (s>$sec,m>$min,h>$hours,m>$mday,m>$mon,y>$year)\n";
        $self->{Hour}  = sprintf "%02d", $self->{Hour}||$hours;
        $self->{Date}  = sprintf("%02d%02d%02d", $year%100, $mon+1, $mday);
    }
    if ($par{Host}) {
        my $host = $self->{Host}   = lc($par{Host});
        if ($host =~ /[^\d.]/) {
            if ($host =~ /\./) {
                $self->{Domain} = Net::Country::Name((split /\./, $host)[-1]);
            } else {
                $self->{Domain} = 'Local';
            }
        } else {
            $self->{Domain} = 'Unresolved';
        }
    }
    $self->{Records} = 1;

    bless $self, $type;
}

sub add {
    my $self   = shift;
    my $other  = shift;
    my $ignore = shift;

    for (keys %{$other}) {
        next if $_ eq $ignore;
        next unless defined $other->{$_};
        next unless length($other->{$_});
        next if $other->{$_} =~ /\D/;
        $self->{$_} += $other->{$_};
    }

    $self;
}

sub copy {
    my $self = shift;
    my %new  = %{$self};

    bless \%new, ref($self);
}

sub requests {$_[0]->{Records};}

1;

__DATA__

package Time::String;

use Time::Local;

my @moname = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
my %monnum;
my $monreg = '(' . join('|', @moname) . ')';

{ my $i = 0;
  for (@moname) {
      $monnum{lc($_)} = $i++;
  }
}

sub to_time {
    my $date = shift;
    my($sec,$min,$hours,$mday,$mon,$year) = (0)x3;

    #print "$date => ";
    if ($date =~ s!\b(\d+)/(\d+)/(\d+)\b! !) {
        ($mon, $mday, $year) = ($1, $2, $3);
        $mon--;
    } elsif ($date =~ s!\b(\d+)/(\w+)/(\d+)\b! !) {
        ($mday, $mon, $year) = ($1, $monnum{lc($2)}, $3);
    } elsif ($date =~ s!\b(\d+)\s+(\w+)\s+(\d+)\b! !) {
        ($mday, $mon, $year) = ($1, $monnum{lc($2)}, $3);
    } elsif ($date =~ s!\b$monreg\b(\s+(\d+))?! !io) {
        $mon = $monnum{lc($1)};
        $mday = $3;             # possibly not set
        if ($date =~ s/19(\d\d)/ /) {
            $year = $1;
        }
    }
    if ($date =~ s!\b(\d+):(\d+)(:(\d+))?! !) {
        ($hours, $min, $sec) = ($1, $2, $4);
    }
    $year -= 1900 if $year > 1900;
    
    #print "($sec,$min,$hours,$mday,$mon,$year);";

    my $gmtime = timegm($sec,$min,$hours,$mday,$mon,$year);
    if ($date =~ s!([-+]\d+)! !) {
        $gmtime += $1*36;
    }
    $gmtime;
}

1;