#! /usr/local/bin/perl -w
#
# $Id: qlas,v 1.17 2000/08/04 09:50:51 lev Exp $
#
# qico FIDO mailer log analyzer and statistic builder
# Copyright (c) 2000 Lev Serebryakov, lev@serebryakov.spb.ru or 2:5030/661.0
# Distributed under BSD license
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
#    must display the following acknowledgement:
#	This product includes software developed by Lev Serebryakov
# 4. Neither the name of the Lev Serebryakov nor the names of its contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# $Log: qlas,v $
# Revision 1.17  2000/08/04 09:50:51  lev
# Case-insesitive month parsing
#
# Revision 1.16  2000/07/27 20:39:33  lev
# intro: and caller-id: support, min_cps 0xFFF fix
#
# Revision 1.15  2000/07/04 20:18:40  lev
# Now understand calling to points
#
# Revision 1.14  2000/06/28 20:36:57  lev
# Fix graph bug after localtime()
#
# Revision 1.13  2000/06/28 16:44:10  lev
# Change gmtime() to localtime()
#
# Revision 1.12  2000/06/25 15:34:33  lev
# Once more
#
# Revision 1.11  2000/06/25 15:33:35  lev
# Stupid bug fixed
#
# Revision 1.10  2000/06/25 15:27:22  lev
# Add `--verbose' flag
#
# Revision 1.9  2000/06/09 18:26:08  lev
# Add config samples, TODO file
#
# Revision 1.8  2000/06/09 15:18:55  lev
# Normal time summary done
#
# Revision 1.7  2000/06/05 16:46:31  lev
# Fix stupid bug with errors processing
#
# Revision 1.6  2000/06/05 16:15:47  lev
# Add 'errors' time
#
# Revision 1.5  2000/06/04 20:05:14  lev
# Version 1.0 Finished.
#
# Revision 1.4  2000/06/04 19:11:32  lev
# Session lists, links summary
#
# Revision 1.3  2000/06/04 15:25:04  lev
# Config parser rewritten
# Busy graph ready
#
# Revision 1.2  2000/06/02 18:07:23  lev
# Analyzing done
#
# Revision 1.1.1.1  2000/05/23 20:22:20  lev
# Initial import of empty script
#
#
use strict;
use locale;
use Getopt::Long;
use Time::Local;
use integer;

my $GRAPH_H     = 20;
my $GRAPH_STEPS = 72;

my %OPTIONS = (
	'config' => '/usr/local/etc/qlas.cfg',
	'log' => undef,
	'device' => undef,
	'graph' => undef,
	'ssum' => undef,
	'slist' => undef,
	'topdown' => undef,
	'topup' => undef,
	'topcps' => undef,
	'downcps' => undef,
	'verbose' => 0
	);

my %CONFIG = (
	'verbose' =>         {'t' => 'n', 'v' => 0},
	'log' =>             {'t' => 's', 'v' => ''},
	'device' =>          {'t' => 's', 'v' => '.*'},
	'graph' =>           {'t' => 's', 'opt' => 'e', 'v' => 'qlas.graph.%'},
	'slist' =>           {'t' => 's', 'opt' => 'e', 'v' => 'qlas.session-list.%'},
	'ssum' =>            {'t' => 's', 'opt' => 'e', 'v' => 'qlas.session-sum'},
	'topdown' =>         {'t' => 's', 'opt' => 'e', 'v' => 'qlas.top.downloads'},
	'topdowncount' =>    {'t' => 'n', 'v' => 10},
	'topup' =>           {'t' => 's', 'opt' => 'e', 'v' => 'qlas.top.uploads'},
	'topupcount' =>      {'t' => 'n', 'v' => 10},
	'toponline' =>       {'t' => 's', 'opt' => 'e', 'v' => 'qlas.top.online'},
	'toponlinecount' =>  {'t' => 'n', 'v' => 10},
	'topcps' =>          {'t' => 's', 'opt' => 'e', 'v' => 'qlas.top.cps'},
	'topcpscount' =>     {'t' => 'n', 'v' => 10},
	'downcps' =>         {'t' => 's', 'opt' => 'e', 'v' => 'qlas.down.cps'},
	'downcpscount' =>    {'t' => 'n', 'v' => 10},

	'work-time' =>       {'t' => 'h', 'subt' => 't', 'v' => {}},
	'analyze-time' =>    {'t' => 'h', 'subt' => 't', 'v' => {}},
	'port-name' =>       {'t' => 'h', 'subt' => 's', 'v' => {}},

	'station-name' =>    {'t' => 's', 'v' => ''},

	'graph-call-char' => {'t' => 'c', 'v' => '*'},
	'graph-error-char' =>{'t' => 'c', 'v' => '+'},
	'graph-in-char' =>   {'t' => 'c', 'v' => '>'},
	'graph-out-char' =>  {'t' => 'c', 'v' => '<'},
	'graph-hl' =>        {'t' => 'c', 'v' => '-'},
	'graph-vl' =>        {'t' => 'c', 'v' => '|'},
	'graph-cross' =>     {'t' => 'c', 'v' => '+'},
	'graph-vtick' =>     {'t' => 'c', 'v' => '+'},
	'graph-htick' =>     {'t' => 'c', 'v' => '+'},
	'graph-back' =>      {'t' => 'c', 'v' => '.'},

	'table-out-vl' =>    {'t' => 'c', 'v' => '|'},
	'table-out-hl' =>    {'t' => 'c', 'v' => '='},
	'table-out-ur' =>    {'t' => 'c', 'v' => '+'},
	'table-out-ul' =>    {'t' => 'c', 'v' => '+'},
	'table-out-br' =>    {'t' => 'c', 'v' => '+'},
	'table-out-bl' =>    {'t' => 'c', 'v' => '+'},
	'table-in-vl' =>     {'t' => 'c', 'v' => '|'},
	'table-in-hl' =>     {'t' => 'c', 'v' => '-'},
	'table-in-cross' =>  {'t' => 'c', 'v' => '+'},
	'table-inin-b' =>    {'t' => 'c', 'v' => '-'},
	'table-inout-l' =>   {'t' => 'c', 'v' => '+'},
	'table-inout-r' =>   {'t' => 'c', 'v' => '+'},
	'table-inout-t' =>   {'t' => 'c', 'v' => '+'},
	'table-inout-b' =>   {'t' => 'c', 'v' => '+'},
	);

my %PORT = ();
my %LINKS = ();
my $TODAY = 0;
my $NOW = 0;

my $ANALYZE_START = 0;
my $ANALYZE_END   = 0;

my $STEP_L_SEC = (24*60*60) / $GRAPH_STEPS;
my $STEP_L_MIN = (24*60) / $GRAPH_STEPS;

	Getopt::Long::Configure("bundling","auto_abbrev");
	GetOptions(\%OPTIONS,'config|c=s','log|l=s','device|d=s','graph|g:s','ssum|session-sum|sum|s:s','slist|session-list|list|l:s','topdown|down:s','topup|up:s','topcps|cps:s','downcps|dcps:s','verbose|verb|v') or die "Could not parse command line\n";
	LoadConfig($OPTIONS{'config'});

	while(my ($k,$v) = each %OPTIONS) {
		$CONFIG{$k}->{'v'} = $v if defined $v;
	}

#### Prepare ports data #######################################################

	$NOW = time;
	$TODAY = GetDayBegin($NOW);

	$ANALYZE_START = $NOW + 86400;

#--- Parse Analyze time for each configured port ------------------------------

	while(my ($k,$v) = each %{$CONFIG{'analyze-time'}->{'v'}}) {
		die "Could not find port name for port $k\n" if not exists $CONFIG{'port-name'}->{'v'}->{$k};
		$PORT{$k} = {} if not exists $PORT{$k};
		$PORT{$k}->{'astart'} = 0;
		$PORT{$k}->{'aend'}   = 0;
		if	    ($v =~ /^(\d{1,2})-(\d{1,2})(\.(\d{1,2}):(\d{1,2}))?/) {
			$PORT{$k}->{'astart'} = ShortTime2UnixTime($1,$2);
			if(defined $3) { $PORT{$k}->{'astart'} += ($3 * 60 + $4) * 60; }
			$v =~ s/^(\d{1,2})-(\d{1,2})(\.(\d{1,2}):(\d{1,2}))?\s*//;
		} elsif ($v =~ /^(\d+)(\.(\d{1,2}):(\d{1,2}))?/) {
			$PORT{$k}->{'astart'} = $TODAY - $1 * 24 * 60 * 60;
			if(defined $2) { $PORT{$k}->{'astart'} += ($3 * 60 + $4) * 60; }
			$v =~ s/^(\d+)(\.(\d{1,2}):(\d{1,2}))?\s*//;
		} else {
			die "Bad analyze period start for port $k: '$v'\n";
		}


		$v =~ s/^\s+//;

		if	  ($v =~ /^,\s*(\d+)-(\d+)(\.(\d{1,2}):(\d{1,2}))?/) {
			$PORT{$k}->{'aend'} = ShortTime2UnixTime($1,$2);
			if(defined $3) { $PORT{$k}->{'aend'} += ($3 * 60 + $4) * 60; }
			$v =~ s/^,\s*(\d+)-(\d+)(\.(\d{1,2}):(\d{1,2}))?\s*//;
		} elsif ($v =~ /^,\s*(\d+)(\.(\d{1,2}):(\d{1,2}))?/) {
			$PORT{$k}->{'aend'} = $PORT{$k}->{'astart'} + $1 * 24 * 60 * 60;
			if(defined $2) { $PORT{$k}->{'aend'} += ($3 * 60 + $4) * 60; }
			$v =~ s/^,\s*(\d+)(\.(\d{1,2}):(\d{1,2}))?\s*//;
		} elsif (!$v) {
			$PORT{$k}->{'aend'} = $PORT{$k}->{'astart'} + 24 * 60 * 60;
		} else {
			die "Bad analyze period end for port $k: '$v'\n";
		}
		if($v) {
			die "Bad analyze period for port $k: '$v'\n";
		}

		$PORT{$k}->{'astartday'} = GetDayBegin($PORT{$k}->{'astart'});

		$ANALYZE_END   = max($ANALYZE_END,$PORT{$k}->{'aend'});
		$ANALYZE_START = min($ANALYZE_START,$PORT{$k}->{'astart'});
    }

#--- Parse Work Time for each configured port ---------------------------------

	while(my ($k,$v) = each %{$CONFIG{'work-time'}->{'v'}}) {
		$PORT{$k} = {} if not exists $PORT{$k};
		$PORT{$k}->{'work'} = [];
		while($v =~ /^(\d{1,2}):(\d{1,2})\s*-\s*(\d{1,2}):(\d{1,2})/) {
			die "Bad work time for port $k\n" if $1 > 23 or $2 > 59 or $3 > 23 or $4 > 59;
			my $s = ($1 * 60 + $2) * 60;
			my $e = ($3 * 60 + $4) * 60;
			if($e > $s) {
				push @{$PORT{$k}->{'work'}},$s,$e;
			} else {
				push @{$PORT{$k}->{'work'}},$s,24*60*60;
				push @{$PORT{$k}->{'work'}},0,$e;
			}
			$v =~ s/^(\d+):(\d+)\s*-\s*(\d+):(\d+)\s*,?\s*//;
		}
		my $tw = 0;
		for (my $i = 0; $i < $#{$PORT{$k}->{'work'}}; $i += 2) {
			$tw += $PORT{$k}->{'work'}->[$i+1] - $PORT{$k}->{'work'}->[$i];
			if ($i > 1) {
				die "Bad work time for port $k (overlapped)\n" if $PORT{$k}->{'work'}->[$i] < $PORT{$k}->{'work'}->[$i-1];
			}
		}
		die "Bad work time for port $k (more than 24h)" if $tw > 86400;
		die "Bad work time for port $k (invalid format)\n" if $v;
	}

	while(my ($k,$v) = each %PORT) {
		$v->{'state'} = '';		# Current parser state

		$v->{'call-start'} = 0;	# Start of calling
		$v->{'connect-start'} = 0; # Connect time
		$v->{'session-start'} = 0; # EMSI Recognition time

		ClearSession($v);

		$v->{'SESSIONS'}	    = [];  # All sessions list
		$v->{'call-time'}      = [];  # Seconds, used on calls
		$v->{'errors'}         = [];  # Seconds, used on faild calls, bad sessions, etc.
		$v->{'out-session'}    = [];  # Seconds, used on outgoing sessions
		$v->{'in-session'}     = [];  # Seconds, used on incoming sessions
		$v->{'total-works'}    = [];  # Seconds in this period (may be multiple days)
		for(my $i = 0; $i < $GRAPH_STEPS; $i++) { $v->{'errors'}->[$i] = $v->{'total-works'}->[$i] = $v->{'call-time'}->[$i] = $v->{'out-session'}->[$i] = $v->{'in-session'}->[$i] = 0; }

		AddTimeToArray($v->{'total-works'},$v->{'astart'},$v->{'aend'} - $v->{'astart'});

	}


#### Analyzing ################################################################

	print "Processing...\n" if $CONFIG{'verbose'}->{'v'};

	local *LOG;
	open(LOG,$CONFIG{'log'}->{'v'}) or die "Could not open log file '$CONFIG{'log'}->{'v'}': $!\n";


	my $pre = $CONFIG{'device'}->{'v'};
	my $start = 0;
LOGLINE:
	while(<LOG>) {
		chomp;
		next LOGLINE if not $_;
		next LOGLINE if not s/^(.{18}) ([^\[]+)\[\d+]: //;
		my $port = $2;
		my $linetime = LogTime2UnixTime($1);
		next LOGLINE if $port !~ /$pre/;
		my $cp = $PORT{$port};
		die "Not configured port: '$port'\n" if !$cp;
		next LOGLINE if $linetime > $cp->{'aend'} or $linetime < $cp->{'astart'};

		if(!$start) {
			$start = 1;
			print "Analyzing...\n" if $CONFIG{'verbose'}->{'v'};
		}


		if	  ($cp->{'state'} eq '') {
			if	    (/^calling .+?, \d+:\d+\/\d+(\.\d+)? (.+)/) {
				$cp->{'state'} = 'call';
				$cp->{'call-start'} = $linetime;
			} elsif (/^answering incoming call/) {
				$cp->{'state'} = 'answer';
				$cp->{'call-start'} = $linetime;
			} elsif (/^interrupted!/ or
					/^exiting with rc/ or
					/^can't exec/) {
				# EMPTY, SKIP
			} else {
				warn "Bad line: '$_' in 'empty' state (",scalar localtime($linetime),")\n";
			}
		} elsif ($cp->{'state'} eq 'call') {
			if	  (/^got (RING|BUSY)/) {
				$cp->{'state'} = '';
				AddTimeToArray($cp->{'call-time'},$cp->{'call-start'},$linetime - $cp->{'call-start'});
			} elsif (/^got (NO DIAL TONE|NO CARRIER|.+? signal)/ or /^hanging up/ or /^can't open port/) {
				$cp->{'state'} = '';
				AddTimeToArray($cp->{'errors'},$cp->{'call-start'},$linetime - $cp->{'call-start'});
			} elsif (/^\*\*\* CONNECT (\d+)/) {
				$cp->{'state'} = 'out-c';
				$cp->{'speed'} = $1;
				$cp->{'connect-start'} = $linetime;
			} elsif (/^resetting modem/ or /^modem reset failed/ or /^got (TIMEOUT|FAILURE)/) {
				# EMPTY, SKIP
			} else {
				warn "Bad line: '$_' in 'call' state (",scalar localtime($linetime),")\n";
			}
		} elsif ($cp->{'state'} eq 'answer') {
			if      (/^\*\*\* CONNECT (\d+)/) {
				$cp->{'state'} = 'in-c';
				$cp->{'speed'} = $1;
				$cp->{'connect-start'} = $linetime;
			} else {
				warn "Bad line: '$_' in 'answer' state (",scalar localtime($linetime),")\n";
			}
		} elsif ($cp->{'state'} eq 'out-c') {
			if	    (/^trying EMSI\.\.\./) {
				# EMPTY, SKIP
			} elsif (/^starting outbound EMSI session/) {
				ClearSession($cp);
				$cp->{'state'} = 'emsi';
				$cp->{'session-type'} = 'O';
				$cp->{'session-start'} = $linetime;
			} elsif (/^unable to establish EMSI session/) {
				$cp->{'state'} = 'emsi-error';
			} else {
				warn "Bad line: '$_' in 'out-connect' state (",scalar localtime($linetime),")\n";
			}
		} elsif ($cp->{'state'} eq 'in-c') {
		    if      (/^caller-id: (.+)/) {
		    	$cp->{'cid'} = $1;
			} elsif (/^starting inbound EMSI session/) {
				ClearSession($cp);
				$cp->{'state'} = 'emsi';
				$cp->{'session-type'} = 'I';
				$cp->{'session-start'} = $linetime;
			} elsif (/^exiting with rc/) {
				$cp->{'state'} = '';
			} elsif (/^unable to establish EMSI session/ or
					/^unsupported session type!/) {
				$cp->{'state'} = 'emsi-error';
			} elsif (/^interrupted!/) {
				# EMPTY, SKIP
			} else {
				warn "Bad line: '$_' in 'in-connect' state (",scalar localtime($linetime),")\n";
			}
		} elsif ($cp->{'state'} eq 'emsi-error') {
			if      (/^hanging up/ or
					/^exiting with rc/) {
				$cp->{'state'} = '';
				AddTimeToArray($cp->{'errors'},$cp->{'call-start'},$linetime - $cp->{'call-start'});
			} elsif (/^creating poll for/ or
					/^session with unknown failed/ or
					/^total:/ or
					/^starting (?!.+ EMSI session)/) {
				# EMPTY, SKIP
			} else {
				warn "Bad line: '$_' in 'emsi-error' state (",scalar localtime($linetime),")\n";
			}
		} elsif ($cp->{'state'} eq 'emsi') {
			$_ =~ s/^\s+//;
			if	    (/^aka:/ or
					/^intro:/ or
					/^system:/ or
					/^from:/ or
					/^sysop:/ or
					/^phone:/ or
					/^flags:/ or
					/^mailer:/ or
					/^time:/ or
					/^for us:/ or
					/^we have:/ or
					/^password not matched/ or
					/^\(got '.*' instead of '.*'\)/ or
					/^hydra .+? mode session/ or
					/^starting (?!.+ EMSI session)/ or
					/^total: / or
					/^interrupted!/ or
					/^hydra: too many errors/ or
					/^can't/ or
					/^creating poll for/ or
					/^exec '(.+?)'/) {
				# EMPTY, SKIP
			} elsif (/^address:/) {
				my ($address) = (/^address: (\d+:\d+\/\d+(?:\.\d+)?)/);
				if(!$address) {
					warn "Bad address in line: '$_' in 'out-emsi' state (",scalar localtime($linetime),")\n";
					$cp->{'state'} = 'skip-emsi';
				} else {
					$cp->{'address'} = $address;
				}
			} elsif (/^options:/) {
				($cp->{'proto'}) = (/options: (.+?)\//);
				$cp->{'prot'} = /PWD/;
				$cp->{'listed'} = /LST/;
			} elsif (/^hydra link options:/) {
				$cp->{'recd-start'} = $cp->{'sent-start'} = $linetime;
			} elsif (/^wazoo send/) {
				$cp->{'sent-start'} = $linetime;
			} elsif (/^wazoo receive/) {
				$cp->{'recd-start'} = $linetime;
			} elsif (/(recd|sent): (.+?), (\d+) bytes, \d+ cps \[(.+?)\]/) {
				my $t = FileType($2);
				my $p = $1;
				$cp->{$p.'-files'}->[$t]++ if $4 eq 'ok';
				$cp->{$p.'-bytes'}->[$t] += $3;
				$cp->{$p.'-time'} += ($linetime - $cp->{$p.'-start'});
				$cp->{$p.'-start'} = $linetime;
			} elsif (/(recd|sent): (.+?), (\d+) bytes \(from (\d+)\), \d+ cps \[(.+?)\]/) {
				my $t = FileType($2);
				my $p = $1;
				$cp->{$p.'-files'}->[$t]++ if $5 eq 'ok';
				$cp->{$p.'-bytes'}->[$t] += $3 - $4;
				$cp->{$p.'-time'} += ($linetime - $cp->{$p.'-start'});
				$cp->{$p.'-start'} = $linetime;
			} elsif (/^session with .+? failed/) {
				$cp->{'result'} = 0;
				$cp->{'session-time'} = $linetime - $cp->{'session-start'};
			} elsif (/^session with .+? successful/) {
				$cp->{'result'} = 1;
				$cp->{'session-time'} = $linetime - $cp->{'session-start'};
			} elsif (/^hanging up/ or /^exiting with rc/) {
				$cp->{'state'} = '';

				AddTimeToArray($cp->{'session-type'} eq 'I'?$cp->{'in-session'}:$cp->{'out-session'},
				$cp->{'call-start'},$linetime - $cp->{'call-start'});

				my $s = {
					'c-start' => $cp->{'connect-start'},
					'e-start' => $cp->{'emsi-start'},
					'c-len' => $linetime - $cp->{'connect-start'},
					'e-len' => $cp->{'session-time'},
					'node' => $cp->{'address'},
					'prot' => $cp->{'prot'},
					'list'       => $cp->{'listed'},
					'res'        => $cp->{'result'},
					'proto'      => $cp->{'proto'},
					'type'       => $cp->{'session-type'},

					'recd-files' => sum($cp->{'recd-files'}),
					'recd-bytes' => sum($cp->{'recd-bytes'}),
					'recd-time'  => $cp->{'recd-time'},

					'sent-files' => sum($cp->{'sent-files'}),
					'sent-bytes' => sum($cp->{'sent-bytes'}),
					'sent-time'  => $cp->{'sent-time'}
				};

				if($s->{'recd-time'}) { $s->{'recd-cps'} = $s->{'recd-bytes'} / $s->{'recd-time'} }
				else { $s->{'recd-cps'} = $s->{'recd-bytes'}; }

				if($s->{'sent-time'}) { $s->{'sent-cps'} = $s->{'sent-bytes'} / $s->{'sent-time'} }
				else { $s->{'sent-cps'} = $s->{'sent-bytes'}; }

				if($s->{'e-len'}) { $s->{'total-cps'} = ($s->{'sent-bytes'} + $s->{'recd-bytes'}) / $s->{'e-len'} }
				else { $s->{'total-cps'} = $s->{'sent-bytes'} + $s->{'recd-bytes'}; }
				push @{$cp->{'SESSIONS'}},$s;

				my $a = $s->{'node'};
				my $l;

				if (not exists $LINKS{$a}) {
					$l = {
						'outbound' => 0,
						'inbound' => 0,
						'total-time' => 0,

						'files-from' => [0, 0, 0, 0],
						'bytes-from' => [0, 0, 0, 0],
						'time-from' => 0,
						'max-cps-from' => 0,
						'min-cps-from' => 0xFFFF,

						'files-to' => [0, 0, 0, 0],
						'bytes-to' => [0, 0, 0, 0],
						'time-to' => 0,
						'max-cps-to' => 0,
						'min-cps-to' => 0xFFFF
					};
					$LINKS{$a} = $l;
				} else {
					$l = $LINKS{$a};
				}

				if($s->{'type'} eq 'I') { $l->{'inbound'}++; }
				else { $l->{'outbound'}++; }

				$l->{'total-time'} += $s->{'c-len'};

				add($l->{'files-from'},$cp->{'recd-files'});
				add($l->{'bytes-from'},$cp->{'recd-bytes'});
				$l->{'time-from'} += $s->{'recd-time'};
				if($l->{'max-cps-from'} < $s->{'recd-cps'}) { $l->{'max-cps-from'} = $s->{'recd-cps'}; }
				if($l->{'min-cps-from'} > $s->{'recd-cps'} and $s->{'recd-cps'} > 0) { $l->{'min-cps-from'} = $s->{'recd-cps'}; }

				add($l->{'files-to'},$cp->{'sent-files'});
				add($l->{'bytes-to'},$cp->{'sent-bytes'});
				$l->{'time-to'} += $s->{'sent-time'};
				if($l->{'max-cps-to'} < $s->{'sent-cps'}) { $l->{'max-cps-to'} = $s->{'sent-cps'}; }
				if($l->{'min-cps-to'} > $s->{'sent-cps'} and $s->{'sent-cps'} > 0) { $l->{'min-cps-to'} = $s->{'sent-cps'}; }
			} else {
				warn "Bad line: '$_' in 'emsi session' state (",scalar localtime($linetime),")\n";
			}
		}
	}
	close(LOG);

#### Reporting ################################################################

	print "Reporting...\n" if $CONFIG{'verbose'}->{'v'};
	local *F;

PORTREPORT:
	while (my ($n,$d) = each %PORT) {
		next PORTREPORT if $n !~ /$pre/;
		my $fn;

#--- Busy graph (if needed) ---------------------------------------------------

		$fn = $CONFIG{'graph'}->{'v'};
		if($fn) {
			my @graph = ();
			my $s = '';
			for (my $i = 0; $i < $GRAPH_STEPS; $i++) {
				my ($c,$in,$out,$e);

				if($d->{'total-works'}->[$i]) {
					$c = ($d->{'call-time'}->[$i] * $GRAPH_H) / $d->{'total-works'}->[$i];
					$out = ($d->{'out-session'}->[$i] * $GRAPH_H) / $d->{'total-works'}->[$i];
					$in = ($d->{'in-session'}->[$i] * $GRAPH_H) / $d->{'total-works'}->[$i];
					$e = ($d->{'errors'}->[$i] * $GRAPH_H) / $d->{'total-works'}->[$i];
				} else {
					$e = $c = $in = $out = 0;
				}
				$s = '';

				$s .= ($CONFIG{'graph-in-char'}->{'v'} x $in);
				$s .= ($CONFIG{'graph-out-char'}->{'v'} x $out);
				$s .= ($CONFIG{'graph-call-char'}->{'v'} x $c);
				$s .= ($CONFIG{'graph-error-char'}->{'v'} x $e);
				$s .= ($CONFIG{'graph-back'}->{'v'} x ($GRAPH_H - $in - $out - $c - $e));
				push @graph,$s;
			}

			$fn =~ s/%/$n/g;
			open(F,'>'.$fn) or die "Could not open GRAPH file '$fn': $!\n";

			print F "     ",CenterLine("Busy graph for",$GRAPH_STEPS+1),"\n";
			print F "     ",CenterLine($CONFIG{'port-name'}->{'v'}->{$n},$GRAPH_STEPS+1),"\n";
			print F "     ",CenterLine(localtime($d->{'astart'}).' -- '.localtime($d->{'aend'}),$GRAPH_STEPS+1),"\n";

			print F "     ",$CONFIG{'graph-vl'}->{'v'},"\n";
			for (my $i = 0; $i < $GRAPH_H; $i++) {
				my $line = $GRAPH_H - $i;
				if ((($line * 100) / $GRAPH_H) % 25 == 0) {
					print F sprintf(" %3d%%",($line * 100) / $GRAPH_H),$CONFIG{'graph-vtick'}->{'v'};
				} else {
					print F "     ",$CONFIG{'graph-vl'}->{'v'};
				}
				my $s = '';
				for (my $l = 0; $l < $GRAPH_STEPS; $l++) { $s .= substr($graph[$l],$line-1,1); }
				print F $s,"\n";
			}

			my ($h,$m) = (0,0);
			my @ls = ("     ".$CONFIG{'graph-cross'}->{'v'},"      ","      ");
			my $flag = 1;

			for (my $i = 0; $i < $GRAPH_STEPS; $i++) {
				$ls[0] .= $CONFIG{$m?'graph-hl':'graph-htick'}->{'v'};
				if ($m == 0) {
					if ($h % 2 == 0) { $ls[1] .= $h; $ls[1] .= ' ' if $h < 10; $ls[2] .= '  '; }
					else { $ls[2] .= $h; $ls[2] .= ' ' if $h < 10; $ls[1] .=  $CONFIG{'graph-vl'}->{'v'}.' '; }
					$flag = 1;
				} else {
					if($flag) { $flag = 0; }
					else { $ls[1] .= ' '; $ls[2] .= ' '; }
				}
				$m += (1440 / $GRAPH_STEPS);
				while ($m > 59) { $m -= 60; $h++; }
			}
			print F join("\n",@ls),"\n";



			my $twt = 0;
			my $tft = 0;
			my $wt = 0;
			my $ft = 0;

            $twt = SumTimesWithinGaps($d->{'total-works'},$d->{'work'});
			$tft = $d->{'aend'} - $d->{'astart'};

			my $w = max(($GRAPH_STEPS - 17) / 2,18);
			print F "\n";
			print F "     ",CenterLine("Time summary",$w*2+17),"\n";
			print F "     ",$CONFIG{'table-out-ul'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 15,
				$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x $w,
				$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x $w,
				$CONFIG{'table-out-ur'}->{'v'},"\n";
			print F "     ",$CONFIG{'table-out-vl'}->{'v'},' ' x 15,
				$CONFIG{'table-in-vl'}->{'v'},CenterLine('Full time',$w,1),
				$CONFIG{'table-in-vl'}->{'v'},CenterLine('Work time',$w,1),
				$CONFIG{'table-out-vl'}->{'v'},"\n";
			print F "     ",$CONFIG{'table-inout-l'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 15,
				$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x $w,
				$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x $w,
				$CONFIG{'table-inout-r'}->{'v'},"\n";

			# -- Real data

			print F "     ",$CONFIG{'table-out-vl'}->{'v'},' '.RightLine('Total time: ',14),
				$CONFIG{'table-in-vl'}->{'v'},CenterLine(MakeFullTime($tft,-1),$w,1),
				$CONFIG{'table-in-vl'}->{'v'},CenterLine(MakeFullTime($twt,-1),$w,1),
				$CONFIG{'table-out-vl'}->{'v'},"\n";


			$ft = sum($d->{'call-time'}) + sum($d->{'errors'}) + sum($d->{'out-session'}) + sum($d->{'in-session'});
			$wt = SumTimesWithinGaps($d->{'call-time'},$d->{'work'}) +
				SumTimesWithinGaps($d->{'errors'},$d->{'work'}) +
				SumTimesWithinGaps($d->{'out-session'},$d->{'work'}) +
				SumTimesWithinGaps($d->{'in-session'},$d->{'work'});
			print F "     ",$CONFIG{'table-out-vl'}->{'v'},' '.RightLine('Busy time: ',14),
				$CONFIG{'table-in-vl'}->{'v'},CenterLine(MakeFullTime($ft,$tft),$w,1),
				$CONFIG{'table-in-vl'}->{'v'},CenterLine(MakeFullTime($wt,$twt),$w,1),
				$CONFIG{'table-out-vl'}->{'v'},"\n";

			$ft = sum($d->{'call-time'});
			$wt = SumTimesWithinGaps($d->{'call-time'},$d->{'work'});
			print F "     ",$CONFIG{'table-out-vl'}->{'v'},$CONFIG{'graph-call-char'}->{'v'}.RightLine('Call time: ',14),
				$CONFIG{'table-in-vl'}->{'v'},CenterLine(MakeFullTime($ft,$tft),$w,1),
				$CONFIG{'table-in-vl'}->{'v'},CenterLine(MakeFullTime($wt,$twt),$w,1),
				$CONFIG{'table-out-vl'}->{'v'},"\n";

			$ft = sum($d->{'errors'});
			$wt = SumTimesWithinGaps($d->{'errors'},$d->{'work'});
			print F "     ",$CONFIG{'table-out-vl'}->{'v'},$CONFIG{'graph-error-char'}->{'v'}.RightLine('Errors time: ',14),
				$CONFIG{'table-in-vl'}->{'v'},CenterLine(MakeFullTime($ft,$tft),$w,1),
				$CONFIG{'table-in-vl'}->{'v'},CenterLine(MakeFullTime($wt,$twt),$w,1),
				$CONFIG{'table-out-vl'}->{'v'},"\n";

			$ft = sum($d->{'in-session'});
			$wt = SumTimesWithinGaps($d->{'in-session'},$d->{'work'});
			print F "     ",$CONFIG{'table-out-vl'}->{'v'},$CONFIG{'graph-in-char'}->{'v'}.RightLine('In time: ',14),
				$CONFIG{'table-in-vl'}->{'v'},CenterLine(MakeFullTime($ft,$tft),$w,1),
				$CONFIG{'table-in-vl'}->{'v'},CenterLine(MakeFullTime($wt,$twt),$w,1),
				$CONFIG{'table-out-vl'}->{'v'},"\n";

			$ft = sum($d->{'out-session'});
			$wt = SumTimesWithinGaps($d->{'out-session'},$d->{'work'});
			print F "     ",$CONFIG{'table-out-vl'}->{'v'},$CONFIG{'graph-out-char'}->{'v'}.RightLine('Out time: ',14),
				$CONFIG{'table-in-vl'}->{'v'},CenterLine(MakeFullTime($ft,$tft),$w,1),
				$CONFIG{'table-in-vl'}->{'v'},CenterLine(MakeFullTime($wt,$twt),$w,1),
				$CONFIG{'table-out-vl'}->{'v'},"\n";
			# -- Real data end

			print F "     ",$CONFIG{'table-out-bl'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 15,
				$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x $w,
				$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x $w,
				$CONFIG{'table-out-br'}->{'v'},"\n";

			close(F);

#--- Session list (if needed) -------------------------------------------------
			$fn = $CONFIG{'slist'}->{'v'};
			if($fn) {
				$fn =~ s/%/$n/g;
				open(F,'>'.$fn) or die "Could not open Session List file '$fn': $!\n";

				print F CenterLine("Full session list for",79),"\n";
				print F CenterLine($CONFIG{'port-name'}->{'v'}->{$n},79),"\n";
				print F CenterLine(localtime($d->{'astart'}).' -- '.localtime($d->{'aend'}),79),"\n\n";

				print F $CONFIG{'table-out-ul'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 3,
					$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 14,
					$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 16,
					$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 8,
					$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 8,
					$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 8,
					$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x (4+1+4+1+4),
					$CONFIG{'table-out-ur'}->{'v'},"\n";
				print F $CONFIG{'table-out-vl'}->{'v'},' ' x 3,
					$CONFIG{'table-in-vl'}->{'v'},' ' x 14,
					$CONFIG{'table-in-vl'}->{'v'},' ' x 16,
					$CONFIG{'table-in-vl'}->{'v'},CenterLine('Time',8,1),
					$CONFIG{'table-in-vl'}->{'v'},CenterLine('Out',8,1),
					$CONFIG{'table-in-vl'}->{'v'},CenterLine('In',8,1),
					$CONFIG{'table-in-vl'}->{'v'},CenterLine('CPS',(4+1+4+1+4),1),
					$CONFIG{'table-out-vl'}->{'v'},"\n";
				print F $CONFIG{'table-out-vl'}->{'v'},'PTR',
					$CONFIG{'table-in-vl'}->{'v'},CenterLine('Date/Time',14,1),
					$CONFIG{'table-in-vl'}->{'v'},CenterLine('Address',16,1),
					$CONFIG{'table-in-vl'}->{'v'},CenterLine('online',8,1),
					$CONFIG{'table-in-vl'}->{'v'},CenterLine('bytes',8,1),
					$CONFIG{'table-in-vl'}->{'v'},CenterLine('bytes',8,1),
					$CONFIG{'table-in-vl'}->{'v'},'Snt.',
					$CONFIG{'table-in-vl'}->{'v'},'Rcd.',
					$CONFIG{'table-in-vl'}->{'v'},'Avg.',
					$CONFIG{'table-out-vl'}->{'v'},"\n";
				print F $CONFIG{'table-inout-l'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 3,
					$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 14,
					$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 16,
					$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 8,
					$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 8,
					$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 8,
					$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 4,
					$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 4,
					$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 4,
					$CONFIG{'table-inout-r'}->{'v'},"\n";

				my $total_sent_bytes = 0;
				my $total_sent_time = 0;
				my $total_recd_bytes = 0;
				my $total_recd_time = 0;
				my $total_time = 0;

				for (my $i = 0; $i <= $#{$d->{'SESSIONS'}}; $i++) {
					my $S = $d->{'SESSIONS'}->[$i];

					print F $CONFIG{'table-out-vl'}->{'v'},
						$S->{'prot'}?'*':' ',
						$S->{'type'},
						$S->{'res'}?'+':'-',
						$CONFIG{'table-in-vl'}->{'v'},GetShortDate($S->{'c-start'}),
						$CONFIG{'table-in-vl'}->{'v'},RightLine($S->{'node'},16),
						$CONFIG{'table-in-vl'}->{'v'},MakeTimeOnLine($S->{'c-len'}),
			 			$CONFIG{'table-in-vl'}->{'v'},RightLine(MakeSize($S->{'sent-bytes'}),8),
			 			$CONFIG{'table-in-vl'}->{'v'},RightLine(MakeSize($S->{'recd-bytes'}),8),
		 				$CONFIG{'table-in-vl'}->{'v'},RightLine($S->{'sent-cps'},4),
		 				$CONFIG{'table-in-vl'}->{'v'},RightLine($S->{'recd-cps'},4),
		 				$CONFIG{'table-in-vl'}->{'v'},RightLine($S->{'total-cps'},4),
		 				$CONFIG{'table-out-vl'}->{'v'},"\n";
		
					$total_sent_bytes += $S->{'sent-bytes'};
					$total_sent_time += $S->{'sent-time'};
					$total_recd_bytes += $S->{'recd-bytes'};
					$total_recd_time += $S->{'recd-time'};
					$total_time += $S->{'e-len'};
				}

				print F $CONFIG{'table-inout-l'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 3,
					$CONFIG{'table-inin-b'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 14,
					$CONFIG{'table-inin-b'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 16,
					$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 8,
					$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 8,
					$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 8,
					$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 4,
					$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 4,
					$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 4,
					$CONFIG{'table-inout-r'}->{'v'},"\n";


				print F $CONFIG{'table-out-vl'}->{'v'},RightLine(sprintf("Total %d sessions: ",$#{$d->{'SESSIONS'}}+1),35),
					$CONFIG{'table-in-vl'}->{'v'},MakeTimeOnLine($total_time),
					$CONFIG{'table-in-vl'}->{'v'},RightLine(MakeSize($total_sent_bytes),8),
					$CONFIG{'table-in-vl'}->{'v'},RightLine(MakeSize($total_recd_bytes),8),
					$CONFIG{'table-in-vl'}->{'v'},RightLine($total_sent_time?$total_sent_bytes/$total_sent_time:$total_sent_bytes,4),
					$CONFIG{'table-in-vl'}->{'v'},RightLine($total_recd_time?$total_recd_bytes/$total_recd_time:$total_recd_bytes,4),
					$CONFIG{'table-in-vl'}->{'v'},RightLine($total_time?($total_sent_bytes+$total_recd_bytes)/$total_time:($total_sent_bytes+$total_recd_bytes),4),
					$CONFIG{'table-out-vl'}->{'v'},"\n";

				print F $CONFIG{'table-out-bl'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 3,
					$CONFIG{'table-out-hl'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 14,
					$CONFIG{'table-out-hl'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 16,
					$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 8,
					$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 8,
					$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 8,
					$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 4,
					$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 4,
					$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 4,
					$CONFIG{'table-out-br'}->{'v'},"\n";
				close F;
			}
		}
	}

#--- Convert links from hash to array with additional field -------------------

   	my @LINKS_ARRAY;
	while(my ($k,$v) = each %LINKS) {
		if (defined $k and $k) {
			$v->{'address'} = $k;

			$v->{'total-bytes-to'} = sum($v->{'bytes-to'});
			$v->{'total-files-to'} = sum($v->{'files-to'});
            $v->{'cps-to'} = $v->{'time-to'}?$v->{'total-bytes-to'}/$v->{'time-to'}:$v->{'total-bytes-to'};


			$v->{'total-bytes-from'} = sum($v->{'bytes-from'});
			$v->{'total-files-from'} = sum($v->{'files-from'});
			$v->{'cps-from'} = $v->{'time-from'}?$v->{'total-bytes-from'}/$v->{'time-from'}:$v->{'total-bytes-from'};

			$v->{'total-bytes'} = $v->{'total-bytes-to'} + $v->{'total-bytes-from'};
			$v->{'total-files'} = $v->{'total-files-to'} + $v->{'total-files-from'};

			$v->{'cps'} = $v->{'total-time'}?$v->{'total-bytes'}/$v->{'total-time'}:$v->{'total-bytes'};
			$v->{'min-cps-to'} = 0 if $v->{'min-cps-to'} == 0xFFFF;
			$v->{'min-cps-from'} = 0 if $v->{'min-cps-from'} == 0xFFFF;
			$v->{'min-cps'} = min0($v->{'min-cps-from'},$v->{'min-cps-to'});
			$v->{'max-cps'} = max($v->{'max-cps-from'},$v->{'max-cps-to'});
			push @LINKS_ARRAY,$v;
		}
	}

#--- Session summary (if needed) ----------------------------------------------

	if ($CONFIG{'ssum'}->{'v'}) {

		my $total_files_in  = 0;
		my $total_bytes_in  = 0;
		my $total_files_out = 0;
		my $total_bytes_out = 0;
		my $min_cps = 0xFFFF;
		my $max_cps = 0;
		my $avg_cps = 0;
		my $total_time = 0;
		my $total_in = 0;
		my $total_out = 0;

		@LINKS_ARRAY = sort {
			my ($a1,$a2,$a3,$a4) = ($a->{'address'} =~ /(\d+):(\d+)\/(\d+)(?:\.(\d+))?/);
			my ($b1,$b2,$b3,$b4) = ($b->{'address'} =~ /(\d+):(\d+)\/(\d+)(?:\.(\d+))?/);
			$a4 = 0 if not defined $a4;
			$b4 = 0 if not defined $b4;
			$a1 <=> $b1 || $a2 <=> $b2 || $a3 <=> $b3 || $a4 <=> $b4;
		} @LINKS_ARRAY;

		open(F,'>'.$CONFIG{'ssum'}->{'v'}) or die "Could not open Session Summary file '".$CONFIG{'ssum'}->{'v'}."': $!\n";

		print F CenterLine("Links statistic for ".$CONFIG{'station-name'}->{'v'},79),"\n";
		print F CenterLine(localtime($ANALYZE_START).' -- '.localtime($ANALYZE_END),79),"\n\n";

		print F $CONFIG{'table-out-ul'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 16,
			$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 8,
			$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x (3+1+3+1+3),
			$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 11,
			$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 11,
			$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x (4+1+4+1+4),
			$CONFIG{'table-out-ur'}->{'v'},"\n";
		print F $CONFIG{'table-out-vl'}->{'v'},' ' x 16, 
			$CONFIG{'table-in-vl'}->{'v'},CenterLine('Time',8,1),
			$CONFIG{'table-in-vl'}->{'v'},CenterLine('Sessions',(3+1+3+1+3),1),
			$CONFIG{'table-in-vl'}->{'v'},CenterLine('Out',11,1),
			$CONFIG{'table-in-vl'}->{'v'},CenterLine('In',11,1),
			$CONFIG{'table-in-vl'}->{'v'},CenterLine('CPS',(4+1+4+1+4),1),
			$CONFIG{'table-out-vl'}->{'v'},"\n";
		print F $CONFIG{'table-out-vl'}->{'v'},CenterLine('Address',16,1),
			$CONFIG{'table-in-vl'}->{'v'},CenterLine('online',8,1),
			$CONFIG{'table-in-vl'}->{'v'},'Out',
			$CONFIG{'table-in-vl'}->{'v'},'In ',
			$CONFIG{'table-in-vl'}->{'v'},'Sum',
			$CONFIG{'table-in-vl'}->{'v'},CenterLine('files/bytes',11,1),
			$CONFIG{'table-in-vl'}->{'v'},CenterLine('files/bytes',11,1),
			$CONFIG{'table-in-vl'}->{'v'},'Min.',
			$CONFIG{'table-in-vl'}->{'v'},'Max.',
			$CONFIG{'table-in-vl'}->{'v'},'Avg.',
			$CONFIG{'table-out-vl'}->{'v'},"\n";
		print F $CONFIG{'table-inout-l'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 16,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 8,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 3,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 3,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 3,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 11,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 11,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 4,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 4,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 4,
			$CONFIG{'table-inout-r'}->{'v'},"\n";

		for (my $i = 0; $i <= $#LINKS_ARRAY; $i++) {
			my $l = $LINKS_ARRAY[$i];
			my ($cps_to,$cps_from,$cps) = (0,0,0);

			print F $CONFIG{'table-out-vl'}->{'v'},RightLine($l->{'address'},16),
				$CONFIG{'table-in-vl'}->{'v'},MakeTimeOnLine($l->{'total-time'}),
				$CONFIG{'table-in-vl'}->{'v'},RightLine($l->{'outbound'},3),
				$CONFIG{'table-in-vl'}->{'v'},RightLine($l->{'inbound'},3),
				$CONFIG{'table-in-vl'}->{'v'},RightLine($l->{'inbound'}+$l->{'outbound'},3),
				$CONFIG{'table-in-vl'}->{'v'},RightLine($l->{'total-files-to'}.'/'.MakeSize($l->{'total-bytes-to'},1000000),11),
				$CONFIG{'table-in-vl'}->{'v'},RightLine($l->{'total-files-from'}.'/'.MakeSize($l->{'total-bytes-from'},1000000),11),
				$CONFIG{'table-in-vl'}->{'v'},RightLine($l->{'min-cps'},4),
				$CONFIG{'table-in-vl'}->{'v'},RightLine($l->{'max-cps'},4),
				$CONFIG{'table-in-vl'}->{'v'},RightLine($l->{'cps'},4),
				$CONFIG{'table-out-vl'}->{'v'},"\n";

				$total_bytes_in += $l->{'total-bytes-from'};
				$total_files_in += $l->{'total-files-from'};
				$total_bytes_out += $l->{'total-bytes-to'};
				$total_files_out += $l->{'total-files-to'};
				$min_cps = min0($min_cps,$l->{'min-cps'});
				$max_cps = max($max_cps,$l->{'max-cps'});
				$total_time += $l->{'total-time'};
				$total_in += $l->{'inbound'};
				$total_out += $l->{'outbound'};
		}

		$min_cps = 0 if $min_cps == 0xFFFF;
		$avg_cps = $total_time?($total_bytes_in+$total_bytes_out)/$total_time:($total_bytes_in+$total_bytes_out);

		print F $CONFIG{'table-inout-l'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 16,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 8,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 3,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 3,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 3,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 11,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 11,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 4,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 4,
			$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 4,
			$CONFIG{'table-inout-r'}->{'v'},"\n";

		print F $CONFIG{'table-out-vl'}->{'v'},RightLine(sprintf('Total %d links:',$#LINKS_ARRAY+1),16),
			$CONFIG{'table-in-vl'}->{'v'},MakeTimeOnLine($total_time),
			$CONFIG{'table-in-vl'}->{'v'},RightLine($total_in,3),
			$CONFIG{'table-in-vl'}->{'v'},RightLine($total_out,3),
			$CONFIG{'table-in-vl'}->{'v'},RightLine($total_in+$total_out,3),
			$CONFIG{'table-in-vl'}->{'v'},RightLine(($total_files_out).'/'.MakeSize($total_bytes_out,1000000),11),
			$CONFIG{'table-in-vl'}->{'v'},RightLine(($total_files_in).'/'.MakeSize($total_bytes_in,1000000),11),
			$CONFIG{'table-in-vl'}->{'v'},RightLine($min_cps,4),
			$CONFIG{'table-in-vl'}->{'v'},RightLine($max_cps,4),
			$CONFIG{'table-in-vl'}->{'v'},RightLine($avg_cps,4),
			$CONFIG{'table-out-vl'}->{'v'},"\n";
		print F $CONFIG{'table-out-bl'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 16,
			$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 8,
			$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 3,
			$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 3,
			$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 3,
			$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 11,
			$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 11,
			$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 4,
			$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 4,
			$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 4,
			$CONFIG{'table-out-br'}->{'v'},"\n";

		close(F);
	}


	if ($CONFIG{'topcps'}->{'v'} and $CONFIG{'topcpscount'}->{'v'}) {

		@LINKS_ARRAY = sort {
			$b->{'cps'} <=> $a->{'cps'};
		} @LINKS_ARRAY;

		open(F,'>'.$CONFIG{'topcps'}->{'v'}) or die "Could not open TOP CPS file '".$CONFIG{'topcps'}->{'v'}."': $!\n";

		print F CenterLine("Top ".$CONFIG{'topcpscount'}->{'v'}." CPS on ".$CONFIG{'station-name'}->{'v'},79),"\n";
		print F CenterLine(localtime($ANALYZE_START).' -- '.localtime($ANALYZE_END),79),"\n\n";

		ReportTopDownCPS(*F{IO},\@LINKS_ARRAY,$CONFIG{'topcpscount'}->{'v'});
	
		close(F);
	}

	if ($CONFIG{'downcps'}->{'v'} and $CONFIG{'downcpscount'}->{'v'}) {

		@LINKS_ARRAY = sort {
			$a->{'cps'} <=> $b->{'cps'};
		} @LINKS_ARRAY;

		open(F,'>'.$CONFIG{'downcps'}->{'v'}) or die "Could not open DOWN CPS file '".$CONFIG{'downcps'}->{'v'}."': $!\n";

		print F CenterLine("Down ".$CONFIG{'topcpscount'}->{'v'}." CPS on ".$CONFIG{'station-name'}->{'v'},79),"\n";
		print F CenterLine(localtime($ANALYZE_START).' -- '.localtime($ANALYZE_END),79),"\n\n";

		ReportTopDownCPS(*F{IO},\@LINKS_ARRAY,$CONFIG{'downcpscount'}->{'v'});
	
		close(F);
	}

	if ($CONFIG{'topdown'}->{'v'} and $CONFIG{'topdowncount'}->{'v'}) {

		@LINKS_ARRAY = sort {
			$b->{'total-bytes-to'} <=> $a->{'total-bytes-to'};
		} @LINKS_ARRAY;

		open(F,'>'.$CONFIG{'topdown'}->{'v'}) or die "Could not open Top Download file '".$CONFIG{'topdown'}->{'v'}."': $!\n";

		print F CenterLine("Top ".$CONFIG{'topdowncount'}->{'v'}." of downloaders on ".$CONFIG{'station-name'}->{'v'},79),"\n";
		print F CenterLine(localtime($ANALYZE_START).' -- '.localtime($ANALYZE_END),79),"\n\n";

		ReportTopUpDown(*F{IO},\@LINKS_ARRAY,$CONFIG{'topdowncount'}->{'v'},'to');
	
		close(F);
	}

	if ($CONFIG{'topup'}->{'v'} and $CONFIG{'topupcount'}->{'v'}) {

		@LINKS_ARRAY = sort {
			$b->{'total-bytes-from'} <=> $a->{'total-bytes-from'};
		} @LINKS_ARRAY;

		open(F,'>'.$CONFIG{'topup'}->{'v'}) or die "Could not open Top Upload file '".$CONFIG{'topup'}->{'v'}."': $!\n";

		print F CenterLine("Top ".$CONFIG{'topupcount'}->{'v'}." of uploaders on ".$CONFIG{'station-name'}->{'v'},79),"\n";
		print F CenterLine(localtime($ANALYZE_START).' -- '.localtime($ANALYZE_END),79),"\n\n";

		ReportTopUpDown(*F{IO},\@LINKS_ARRAY,$CONFIG{'topupcount'}->{'v'},'from');
	
		close(F);
	}

	if ($CONFIG{'toponline'}->{'v'} and $CONFIG{'toponlinecount'}->{'v'}) {

		my $very_total_time = 0;

		while (my ($k,$v) = each %PORT) {
			$very_total_time += sum($v->{'in-session'}) + sum($v->{'out-session'});
		}

		@LINKS_ARRAY = sort {
			$b->{'total-time'} <=> $a->{'total-time'};
		} @LINKS_ARRAY;

		open(F,'>'.$CONFIG{'toponline'}->{'v'}) or die "Could not open Top Online file '".$CONFIG{'toponline'}->{'v'}."': $!\n";

		print F CenterLine("Top ".$CONFIG{'topupcount'}->{'v'}." of on-liners on ".$CONFIG{'station-name'}->{'v'},79),"\n";
		print F CenterLine(localtime($ANALYZE_START).' -- '.localtime($ANALYZE_END),79),"\n\n";

		ReportTopOnLine(*F{IO},\@LINKS_ARRAY,$CONFIG{'toponlinecount'}->{'v'},$very_total_time);
	
		close(F);
	}

	print "Done\n" if $CONFIG{'verbose'}->{'v'};

exit(0);

###############################################################################
#### Main program end #########################################################
###############################################################################

#### Report-related functions #################################################

sub CenterLine {
	my ($s,$l,$f,$c) = @_;
	$c = ' ' unless defined $c and $c;
	die "Bad string!\n" if not defined $s;
	die "Bad length!\n" if not defined $l;
	$s = ($c x (($l - length($s)) / 2)).$s;
	if (defined $f and $f) { $s .= $c x ($l - length($s)); }
	return $s;
}

sub RightLine {
	my ($s,$l,$c) = @_;
	$c = ' ' unless defined $c and $c;
	return ($c x ($l - length($s))).$s;
}

sub LeftLine {
	my ($s,$l,$c) = @_;
	$c = ' ' unless defined $c and $c;
	return $s.($c x ($l - length($s)));
}


sub MakeSize {
	my @postfixes = ('','K','M','G','T');
	my $post = 0;

	my ($sz,$max) = @_;
	$max = 100000000 unless $max;
	while($sz > $max) { $sz = $sz / 1024; $post++; }
	return $sz.$postfixes[$post];
}

sub MakeTimeOnLine {
	my $t = shift;
	return sprintf("%2d:%02d:%02d",$t / 3600,($t % 3600) / 60,$t % 60);
}

sub MakeFullTime {
	my $t = shift;
	my $f = shift || -1;
	my $s = '';
	if ($f > -1) {
		return sprintf('%3d:%02d:%02d, %3d%%',$t / 3600,($t % 3600) / 60,$t % 60,$f?($t*100)/$f:100);
	} else {
		my $d = $t / 86400;
		$t %= 86400;
		if      ($d == 1) {
			$s = "1 day, ".$s;
		} elsif ($d > 1) {
			$s = "$d days, ".$s;
		}
		return $s.sprintf('%3d:%02d:%02d',$t / 3600,($t % 3600) / 60,$t % 60);
	}
}

sub ReportTopDownCPS {
	my ($F,$links,$number) = @_;
	my $cnt = $#{$links} + 1;

	print $F $CONFIG{'table-out-ul'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 4,
		$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 61,
		$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 10,
		$CONFIG{'table-out-ur'}->{'v'},"\n";
	print $F $CONFIG{'table-out-vl'}->{'v'},' NN ',
		$CONFIG{'table-in-vl'}->{'v'},CenterLine('Address',61,1),
		$CONFIG{'table-in-vl'}->{'v'},CenterLine('Avg. CPS',10,1),
		$CONFIG{'table-out-vl'}->{'v'},"\n";
	print $F $CONFIG{'table-inout-l'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 4,
		$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 61,
		$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 10,
		$CONFIG{'table-inout-r'}->{'v'},"\n";

    for (my ($i,$cnt) = (0,0); $i <= $#{$links} and $cnt < $number; $i++) {
    	my $l = $links->[$i];
    	if ($l->{'cps'}) {
			print $F $CONFIG{'table-out-vl'}->{'v'},CenterLine(RightLine($cnt+1,2),4,1),
				$CONFIG{'table-in-vl'}->{'v'},LeftLine(' '.$l->{'address'},61),
				$CONFIG{'table-in-vl'}->{'v'},RightLine($l->{'cps'}.' ',10),
				$CONFIG{'table-out-vl'}->{'v'},"\n";
			$cnt++;
		}
	}
	print $F $CONFIG{'table-out-bl'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 4,
		$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 61,
		$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 10,
		$CONFIG{'table-out-br'}->{'v'},"\n";
}


sub ReportTopUpDown {
	my ($F,$links,$number,$word) = @_;
	my $cnt = $#{$links} + 1;

	print $F $CONFIG{'table-out-ul'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 4,
		$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 56,
		$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 15,
		$CONFIG{'table-out-ur'}->{'v'},"\n";
	print $F $CONFIG{'table-out-vl'}->{'v'},' NN ',
		$CONFIG{'table-in-vl'}->{'v'},CenterLine('Address',56,1),
		$CONFIG{'table-in-vl'}->{'v'},CenterLine('Files/Bytes',15,1),
		$CONFIG{'table-out-vl'}->{'v'},"\n";
	print $F $CONFIG{'table-inout-l'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 4,
		$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 56,
		$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 15,
		$CONFIG{'table-inout-r'}->{'v'},"\n";

    for (my $i = 0; $i <= min($#{$links},$number-1); $i++) {
    	my $l = $links->[$i];
		print $F $CONFIG{'table-out-vl'}->{'v'},CenterLine(RightLine($i+1,2),4,1),
			$CONFIG{'table-in-vl'}->{'v'},LeftLine(' '.$l->{'address'},56),
			$CONFIG{'table-in-vl'}->{'v'},RightLine($l->{'total-files-'.$word}.'/'.MakeSize($l->{'total-bytes-'.$word}.' '),15),
			$CONFIG{'table-out-vl'}->{'v'},"\n";
	}
	print $F $CONFIG{'table-out-bl'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 4,
		$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 56,
		$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 15,
		$CONFIG{'table-out-br'}->{'v'},"\n";
}

sub ReportTopOnLine {
	my ($F,$links,$number,$very_total_time) = @_;
	my $cnt = $#{$links} + 1;

	print $F $CONFIG{'table-out-ul'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 4,
		$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 51,
		$CONFIG{'table-inout-t'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 20,
		$CONFIG{'table-out-ur'}->{'v'},"\n";
	print $F $CONFIG{'table-out-vl'}->{'v'},' NN ',
		$CONFIG{'table-in-vl'}->{'v'},CenterLine('Address',51,1),
		$CONFIG{'table-in-vl'}->{'v'},CenterLine('Time Online',20,1),
		$CONFIG{'table-out-vl'}->{'v'},"\n";
	print $F $CONFIG{'table-inout-l'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 4,
		$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 51,
		$CONFIG{'table-in-cross'}->{'v'},$CONFIG{'table-in-hl'}->{'v'} x 20,
		$CONFIG{'table-inout-r'}->{'v'},"\n";

    for (my $i = 0; $i <= min($#{$links},$number-1); $i++) {
    	my $l = $links->[$i];
		print $F $CONFIG{'table-out-vl'}->{'v'},CenterLine(RightLine($i+1,2),4,1),
			$CONFIG{'table-in-vl'}->{'v'},LeftLine(' '.$l->{'address'},51),
			$CONFIG{'table-in-vl'}->{'v'},RightLine(sprintf("%s, %3d%% ",MakeTimeOnLine($l->{'total-time'}),($l->{'total-time'} * 100) / $very_total_time),20),
			$CONFIG{'table-out-vl'}->{'v'},"\n";
	}
	print $F $CONFIG{'table-out-bl'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 4,
		$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 51,
		$CONFIG{'table-inout-b'}->{'v'},$CONFIG{'table-out-hl'}->{'v'} x 20,
		$CONFIG{'table-out-br'}->{'v'},"\n";
}

#### Config-related functions #################################################

sub ParseTimeWord {
	my ($arg) = @_;
	$arg =~ /^\s*([-0-9\.,:]+)\s*?(\s+#.*)?$/ or die "Bad time specification: '$arg'\n";
	return $1;
}

sub ParseString {
	my ($arg,$opt) = @_;

	if($opt eq 'e') {
		$arg =~ /^\s*((?:\\.|[^"\s])*|"(?:\\.|[^"])*")\s*?(\s+#.*)?$/ or die "Bad string: '$arg'\n";
		my $s = defined $1?$1:'';
		$s =~ s/^"(.+?)"$/$1/;
		$s =~ s/\\(.)/$1/g;
		return $s;
	} else {
		$arg =~ /^\s*((?:\\.|[^"\s])+|"(?:\\.|[^"])+")\s*?(\s+#.*)?$/ or die "Bad string: '$arg'\n";
		my $s = $1;
		$s =~ s/^"(.+?)"$/$1/;
		$s =~ s/\\(.)/$1/g;
		return $s;
	}
}

sub ParseChar {
	my ($arg) = @_;
	$arg =~ /^\s*'(.)'\s*?(\s+#.*)?$/ or die "Bad character: '$arg'\n";
	return $1;
}

sub ParseNumber {
	my ($arg) = @_;
	$arg =~ /^\s*(\d+)\s*?(\s+#.*)?$/ or die "Bad number: '$arg'\n";
	return $1;
}

sub ParseHash {
	my ($arg,$cp,$key) = @_;
	$arg =~ /^\s*(\S+)\s+(.+)$/ or die "Bad value for port: '$arg'\n";
	my $port = $1;
	$arg = $2;

	if	    ($cp->{'subt'} eq 's') {
		$cp->{'v'}->{$port} = ParseString($arg,exists $cp->{'opt'}?$cp->{'opt'}:'');
	} elsif ($cp->{'subt'} eq 'n') {
		$cp->{'v'}->{$port} = ParseNumber($arg);
	} elsif ($cp->{'subt'} eq 'c') {
		$cp->{'v'}->{$port} = ParseChar($arg);
	} elsif ($cp->{'subt'} eq 't') {
		$cp->{'v'}->{$port} = ParseTimeWord($arg);
	} else {
		die "Bad sub-type for '$key' (internal error)\n";
	}
}

sub LoadConfig  {
	my $c = $_[0];
	local *C;

	open(C,'<'.$c) or die "Could not open config file '$c': $!\n";

CONFLINE:
	while(<C>) {
		chomp;
		s/^\s+//;
		s/\s+$//;
		next CONFLINE if !$_ or /^#/;
		my($key,$other) = /^(\S+)(?:\s+(.+))?$/;
		die "Bad config line: $key\n" unless $key;
		$key = lc($key);
		$other = '' if not defined $other;
		die "Bad keyword: $key\n" unless exists $CONFIG{$key};
		if	    ($CONFIG{$key}->{'t'} eq 's') {
			$CONFIG{$key}->{'v'} = ParseString($other,exists $CONFIG{$key}->{'opt'}?$CONFIG{$key}->{'opt'}:'');
		} elsif ($CONFIG{$key}->{'t'} eq 'n') {
			$CONFIG{$key}->{'v'} = ParseNumber($other);
		} elsif ($CONFIG{$key}->{'t'} eq 'c') {
			$CONFIG{$key}->{'v'} = ParseChar($other);
		} elsif ($CONFIG{$key}->{'t'} eq 't') {
			$CONFIG{$key}->{'v'} = ParseTimeWord($other);
		} elsif ($CONFIG{$key}->{'t'} eq 'h') {
			ParseHash($other,$CONFIG{$key},$key);
		} else {
			die "Bad type for '$key' (internal error)\n";
		}
	}

	close(C);
}

#### Time-related functions ###################################################

sub ShortTime2UnixTime {
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($NOW);
	$sec = $min = $hour = $wday = $yday = $isdst = 0;
	($mday,$mon) = @_;
	$mon--;
	return timelocal($sec,$min,$hour,$mday,$mon,$year);
}

sub LogTime2UnixTime {
	my %MONTHS = (
		'jan' => 0,
		'' => 0,
		'feb' => 1,
		'' => 1,
		'mar' => 2,
		'' => 2,
		'apr' => 3,
		'' => 3,
		'may' => 4,
		'' => 4,
		'jun' => 5,
		'' => 5,
		'jul' => 6,
		'' => 6,
		'aug' => 7,
		'' => 7,
		'sep' => 8,
		'' => 8,
		'oct' => 9,
		'' => 9,
		'nov' => 10,
		'' => 10,
		'dec' => 11,
		'' => 11
	);
	my ($mday,$month,$year,$hour,$min,$sec) = ($_[0] =~ /(\d{2}) (\S{3}) (\d{2}) (\d{2}):(\d{2}):(\d{2})/);
	$month = lc($month);
	die "Bad month name in log file: '$month'\n" if not exists $MONTHS{$month};
	$month = $MONTHS{$month};
	return timelocal($sec,$min,$hour,$mday,$month,$year);
}

sub GetOnlyTime {
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($_[0]);
	return ($hour * 60 + $min) * 60 + $sec;
}

sub AddTimeToArray {
	my ($a,$s,$l) = @_;
	my $elem   = GetOnlyTime($s) / $STEP_L_SEC;
	my $offset = $s % $STEP_L_SEC;
	while($l) {
		if($offset+$l <= $STEP_L_SEC) {
			$a->[$elem] += $l;
			$l = 0;
		} else {
			$a->[$elem] += ($STEP_L_SEC - $offset);
			$l -= ($STEP_L_SEC - $offset);
			$offset = 0;
			$elem++; $elem = 0 if $elem == $GRAPH_STEPS;
		}
	}
}

sub TimeInGap {
	my ($a,$t) = @_;
	$t = GetOnlyTime($t);
	for(my $i = 0; $i < $#{$a}; $i += 2) {
		return 1 if $t >= $a->[$i] and $t <= $a->[$i+1];
	}
	return 0;
}


sub SumTimesWithinGaps {
	my ($t,$g) = @_;
	my $r = 0;
	for (my $i = 0; $i < $GRAPH_STEPS; $i++) {
		my $s = $i * $STEP_L_SEC;
		my $e = $s + $STEP_L_SEC;
		for(my $j = 0; $j < $#{$g}; $j += 2) {
			my $rs = max($s,$g->[$j]);
			my $re = min($e,$g->[$j+1]);
			if ($rs < $re) {
				$r += ($t->[$i] * ($re - $rs)) / ($e - $s);
			}
		}
	}
	return $r;
}

sub GetDayBegin {
	my ($n) = @_;
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($n);
	$sec = $min = $hour = $wday = $yday = $isdst = 0;
	return timelocal($sec,$min,$hour,$mday,$mon,$year);
}

sub GetShortDate {
	my ($n) = @_;
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($n);
	return sprintf("%02d/%02d %02d:%02d:%02d",$mday,$mon,$hour,$min,$sec);
}

#### Session-related functions ################################################

sub ClearSession {
	my $v = $_[0];

	$v->{'session-type'} = ''; # Session type - I/O
	$v->{'session-time'} = 0;  # Length of EMSI session's part

	$v->{'speed'} = 0;

	$v->{'cid'} = '';       # Caller ID of peer
	$v->{'address'} = '';   # Address of peer
	$v->{'protected'} = 0;  # Is session password-protected or not
	$v->{'listed'} = 0;		# Is peer listed or not
	$v->{'result'} = 0;		# Is session successfull or terminated
	$v->{'proto'} = '';		# Protocol (ZedZap, ZModem, Hydra, etc.)

	$v->{'recd-files'} = [0, 0, 0, 0]; # Number of received PKT, Bundles, TICs, files
	$v->{'recd-bytes'} = [0, 0, 0, 0]; # Size of received PKT, Bundles, TICs, files
	$v->{'recd-time'} = 0;	 # Time of receiving
	$v->{'recd-start'} = 0;  # Start time of current receive

	$v->{'sent-files'} = [0, 0, 0, 0]; # Number of sent PKT, Bundles, TICs, files
	$v->{'sent-bytes'} = [0, 0, 0, 0]; # Size of sent PKT, Bundles, TICs, files
	$v->{'sent-time'} = 0;	 # Time of sending
	$v->{'sent-start'} = 0;  # Start time of current send
}

sub FileType {
	my $n = lc($_[0]);
	my ($e) = ($n =~ /\.([^.]+)$/);
	if(defined $e) {
		return 0 if $e =~ /^pkt$/i;
		return 1 if $e =~ /^(mo|tu|sa|fr|su|th|we)[0-9a-z]$/i;
		return 2 if $e =~ /^tic$/i;
		return 3;
	} else {
		return 3;
	}
}

#### Additional small functions ###############################################

sub min {
	my $r = shift;
	my $a;
	while(defined ($a = shift)) {
		$r = $a if $r > $a;
	}
	return $r;
}

sub min0 {
	my $r = shift;
	my $a;
	while(defined ($a = shift)) {
		$r = $a if ($r > $a) and $a;
	}
	return $r;
}


sub max {
	my $r = shift;
	my $a;
	while(defined ($a = shift)) {
		$r = $a if $r < $a;
	}
	return $r;
}

sub sum {
	my $r = 0;
	foreach my $i (@{$_[0]}) { $r += $i; }
	return $r;
}

sub add {
	my ($a,$b) = @_;
	for (my $i = 0; $i <= $#{$a}; $i++) { $a->[$i] += $b->[$i];	}
}

