#! /usr/bin/perl
#
# ###########################################################
#
# This perl script is a frontend to 'rawfragistics'.
#
# Beside understanding some specific options it
# converts a Q3 logfile format based on time values
# with "seconds.tenthseconds" to a format understood by
# Fragistics (i.e., time is "minutes:seconds").
#
# It also tries to correct various artefacts that might
# break lines of a logfile due to Q3 server restarts
#
# ###########################################################
#
# Copyright (c) 2002 Steffen Schwigon.  All rights reserved.
#
# This package is free software and is provided "as is"
# without express or implied warranty.  It may be used,
# redistributed and/or modified under the terms of the
# Perl Artistic License
# (see http://www.perl.com/perl/misc/Artistic.html).
#
# ###########################################################

use Getopt::Long;
use Pod::Usage;
use Shell;

use strict;
use warnings;

# constants
my $MYNAME        = 'fragistics';
my $TPLTNAME      = 'templates';
my $IMAGESNAME    = 'images';
my $MAXLEN        = 1024;
my $CFGNAME       = "$MYNAME.conf";
my $RAWFRAGISTICS = 'rawfragistics';

# global cmdline options
my $CFGFILE              = undef;
my $LOGFILE              = undef;
my $DESTDIR              = undef;
my $DESTBASE         = undef;
my $VERBOSE              = 1;
my $HELP                 = 0;
my $MAN                  = 0;
my $FORCE                = 0;
my $SPOOL                = 0;
my $INIT                 = 0;
my $FRAGISTICS_HOME      = undef;
my $FRAGISTICS_SPOOL     = undef;
my $FRAGISTICS_IMAGES    = undef;
my $FRAGISTICS_TEMPLATES = undef;
my $TPLTPATH             = undef;
my $IMAGESPATH           = undef;
my $PKGLIBDIR            = '/usr/local/lib';

# string get_cfg_value (section, key);
# Reads the value for a given section and key.
# Config file format:
#
#   [SECTION]
#   key = value
#   // comment
#
sub get_cfg_value {
  my ($srchsec,$srchkey) = @_;
  my $section = '';
  my $key = '';
  my $val = undef;
  open CF, "< $CFGFILE"
    or die "Can't open config file $CFGFILE\n";
  while (<CF>) {
    next if m{^//}; # comment
    $section = $1 if /^\[([^\]]*)\]/; # [SECTION]
    next unless $section eq $srchsec;
    $key=$1, $val=$2, last
      if /^([^=\s]*)\s*=\s*(.*)/ and $1 eq $srchkey; # key = val
  }
  close CF;
  return $val;
}

# loop through the log file and call callbacks
sub convert_timeformat {
  my $logfile = shift;
  my $destfile = shift;
  open LF, "< $logfile"
    or die "Can't open log file $logfile\n";
  open DF, "> $destfile"
    or die "Can't open destination file $destfile\n";
  while (<LF>) {
    # match time format "seconds.tenthseconds"
    if (/^\s*([0-9]+\.[0-9])( .*$)/) {
      my $timeSec = $1;
      my $rest = $2;
      my $timeMin = $timeSec / 60;
      my $minutes = sprintf ("%d", $timeMin);
      my $seconds = sprintf ("%02d", ($timeMin - $minutes) * 60);
      my $convertedtime = sprintf ("%d:%02d", $minutes, $seconds);
      # print with time format "minutes:seconds"
      print DF $convertedtime,$rest, "\n";
    } else {
      # print unchanged line
      print DF;
    }
  } # while
  close DF;
  close LF;
}

sub get_timeformat {
  my $logfile = shift;

  print "Determining time format for log file $logfile\n" if $VERBOSE;
  my $tf = undef;
  open LF, "< $logfile"
    or die "Can't open log file $logfile\n";
  # search for a '------...'-line
  while (<LF>) {
    next unless /^\s*\d*(:|\.)\d*.*-/;
    $tf = $1;
    last;
  }
  close LF;

  die "Unable to determine time format for log file\n" if !defined($tf);

  print "Time format use '$tf' as separator\n";
  return $tf;
}

sub read_cmdline_args {

  # -- defaults --
  my @alternatives;
  my $param_cfgfile      = undef;
  my $param_homedir      = undef;
  my $param_logfile      = undef;
  my $param_tpltpath     = undef;
  my $param_imagespath   = undef;
  my $param_spooldir     = undef;
  my $param_destbase     = undef;

  # cmdline options
  GetOptions ('verbose!'       => \$VERBOSE,
              'cfgfile=s'      => \$param_cfgfile,
              'logfile=s'      => \$param_logfile,
              'destdir=s'      => \$DESTDIR,
              'destbase=s'     => \$param_destbase,
              'spooldir=s'     => \$param_spooldir,
              'homedir=s'      => \$param_homedir,
              'tpltpath=s'     => \$param_tpltpath,
              'imagespath=s'   => \$param_imagespath,
              'force!'         => \$FORCE,
              'spool'          => \$SPOOL,
              'init'           => \$INIT,
              'help'           => \$HELP,
              'man'            => \$MAN)
    or pod2usage(2);

  # homedir
  @alternatives = ();
  push (@alternatives, $param_homedir) if $param_homedir;
  push (@alternatives, "$ENV{'FRAGISTICS_HOME'}") if $ENV{'FRAGISTICS_HOME'};
  push (@alternatives, "$ENV{'HOME'}/.$MYNAME") if $ENV{'HOME'};
  push (@alternatives, "./.$MYNAME");
  $FRAGISTICS_HOME = $alternatives[0];
  #printf "Homedir $FRAGISTICS_HOME\n" if $VERBOSE;

  # spooldir
  @alternatives = ();
  push (@alternatives, $param_spooldir) if $param_spooldir;
  push (@alternatives, $FRAGISTICS_HOME.'/spool');
  $FRAGISTICS_SPOOL = $alternatives[0];

  # destbase
  @alternatives = ();
  push (@alternatives, $param_destbase) if $param_destbase;
  push (@alternatives, $FRAGISTICS_HOME.'/stats');
  $DESTBASE = $alternatives[0];
  #printf "Dest path $param_destbase\n" if $VERBOSE;

  # destdir
  $DESTDIR = undef if $SPOOL; # no single destdir in spool mode

  # misc subdirs
  $FRAGISTICS_IMAGES    = $FRAGISTICS_HOME.'/'.$IMAGESNAME;
  $FRAGISTICS_TEMPLATES = $FRAGISTICS_HOME.'/'.$TPLTNAME;

  # template path
  @alternatives = ();
  push (@alternatives, $param_tpltpath) if $param_tpltpath;
  push (@alternatives, "$FRAGISTICS_HOME/$TPLTNAME") if $FRAGISTICS_HOME && -d "$FRAGISTICS_HOME/$TPLTNAME";
  push (@alternatives, "$PKGLIBDIR/$MYNAME/$TPLTNAME") if -d "$PKGLIBDIR/$MYNAME/$TPLTNAME";
  $TPLTPATH = $alternatives[0]
    or die "Can't find $TPLTNAME directory in $FRAGISTICS_HOME $PKGLIBDIR/$MYNAME\n";
  #printf "Template path $TPLTPATH\n" if $VERBOSE;

  # images path
  @alternatives = ();
  push (@alternatives, $param_imagespath) if $param_imagespath;
  push (@alternatives, "$FRAGISTICS_HOME/$IMAGESNAME") if $FRAGISTICS_HOME && -d "$FRAGISTICS_HOME/$IMAGESNAME";
  push (@alternatives, "$PKGLIBDIR/$MYNAME/$IMAGESNAME") if -d "/$PKGLIBDIR/$MYNAME/$IMAGESNAME";
  $IMAGESPATH = $alternatives[0]
    or die "Can't find $IMAGESNAME directory in $FRAGISTICS_HOME $PKGLIBDIR/$MYNAME\n";
  #printf "Template path $IMAGESPATH\n" if $VERBOSE;

  # cfg file
  @alternatives = ();
  push (@alternatives, $param_cfgfile) if $param_cfgfile;
  push (@alternatives, "$FRAGISTICS_HOME/$CFGNAME") if $FRAGISTICS_HOME && -R "$FRAGISTICS_HOME/$CFGNAME";
  push (@alternatives, "$ENV{'HOME'}/$CFGNAME") if ($ENV{'HOME'} and -R "$ENV{'HOME'}/$CFGNAME");
  push (@alternatives, "$PKGLIBDIR/$MYNAME/$CFGNAME") if -R "$PKGLIBDIR/$MYNAME/$CFGNAME";
  push (@alternatives, "/etc/$CFGNAME") if -R "/etc/$CFGNAME";
  push (@alternatives, "./$CFGNAME") if -R "./$CFGNAME";
  $CFGFILE = $alternatives[0]
    or die "Can't find config file $CFGNAME in $FRAGISTICS_HOME $ENV{'HOME'} /etc/$MYNAME /etc .\n";
  #printf "Config file $CFGFILE\n" if $VERBOSE;

  # logfile
  @alternatives = ();
  my $cfglogfile = get_cfg_value ('LOGS', 'logpath0');
  push (@alternatives, $param_logfile) if $param_logfile;
  push (@alternatives, $cfglogfile) if $cfglogfile;
  push (@alternatives, "games.log");
  $LOGFILE = $alternatives[0];
  #print "Log file $LOGFILE\n" if $VERBOSE;
}

# create nonexisting directories
sub create_dirs {
  # homedir
  unless (-e $FRAGISTICS_HOME) {
    printf "Create homedir $FRAGISTICS_HOME\n" if $VERBOSE;
    system ('mkdir', $FRAGISTICS_HOME) # returns 0 if ok
      and die "Can't create homedir $FRAGISTICS_HOME\n";
  }
  # input spool
  unless (-e $FRAGISTICS_SPOOL) {
    printf "Create gameslog spool dir $FRAGISTICS_SPOOL\n" if $VERBOSE;
    system ('mkdir', $FRAGISTICS_SPOOL) # returns 0 if ok
      and die "Can't create gameslog spool dir $FRAGISTICS_SPOOL\n";
  }
}

sub copy_global_customization_files_to_user {
  my $templates_src = "$PKGLIBDIR/$MYNAME/$TPLTNAME";
  my $images_src = "$PKGLIBDIR/$MYNAME/$IMAGESNAME";
  my $cfg_src;
  if (-R "$PKGLIBDIR/$MYNAME/$CFGNAME") {
    $cfg_src = "$PKGLIBDIR/$MYNAME/$CFGNAME";
  } else {
    $cfg_src = "/etc/$CFGNAME";
  }

  # templates
  if (-e $FRAGISTICS_TEMPLATES and !$FORCE) {
    print "Template dir $FRAGISTICS_TEMPLATES already exists.\n" if $VERBOSE;
    print "  Use --force to overwrite.\n" if $VERBOSE;
  } else {
    # template dir
    unless (-e $FRAGISTICS_TEMPLATES) {
      printf "Create templates dir $FRAGISTICS_TEMPLATES\n" if $VERBOSE;
      system ('mkdir', $FRAGISTICS_TEMPLATES) # returns 0 if ok
	  and die "Can't create templates dir $FRAGISTICS_TEMPLATES\n";
    }
    print "Copy templates from $templates_src/ to $FRAGISTICS_TEMPLATES/.\n";
    cp ("-f", "$templates_src/*.htmlt", "$FRAGISTICS_TEMPLATES/");
  }

  # images
  if (-e $FRAGISTICS_IMAGES and !$FORCE) {
    print "Image dir $FRAGISTICS_TEMPLATES already exists.\n" if $VERBOSE;
    print "  Use --force to overwrite.\n" if $VERBOSE;
  } else {
    unless (-e $FRAGISTICS_IMAGES) {
      printf "Create images dir $FRAGISTICS_IMAGES\n" if $VERBOSE;
      system ('mkdir', $FRAGISTICS_IMAGES) # returns 0 if ok
	and die "Can't create images dir $FRAGISTICS_IMAGES\n";
    }
      print "Copy images from $images_src to $FRAGISTICS_TEMPLATES/.\n";
    cp ("-rf", "$images_src/*", "$FRAGISTICS_IMAGES/");
  }

  # conf file
  if (-e $FRAGISTICS_HOME.'/'.$CFGNAME and !$FORCE) {
    print "Config file $FRAGISTICS_HOME/$CFGNAME already exists.\n" if $VERBOSE;
    print "  Use --force to overwrite.\n" if $VERBOSE;
  } else {
    print "Copy config file from $cfg_src to $FRAGISTICS_HOME/.\n";
    cp ("-f", "$cfg_src", "$FRAGISTICS_HOME/");
  }

}

# process a single gameslogfile
sub process_log {
  my $logfile = shift;
  my $okfile;
  my $convertedlog;
  my $destdir;
  my $timeformat;

  print "Process $logfile ...\n" if $VERBOSE;

  # time format
  $timeformat = get_timeformat ($logfile);
  if ($timeformat eq ':') {
    print "Time format 'min:sec' ... good\n" if $VERBOSE;
  }

  # convert time format if necessary
  if ($timeformat eq '.') {
    print "Time format 'sec.tenthsec' ... convert to format 'min:sec'\n" if $VERBOSE;
    $convertedlog = basename ($logfile);
    chomp $convertedlog;
    $convertedlog =~ s/\.[^.]*$//;
    $convertedlog .= '.convertedlog';
    $convertedlog = "$FRAGISTICS_HOME/$convertedlog";
    if (-e $convertedlog and !$FORCE) {
      print "Converted log $convertedlog already exists,\n" if $VERBOSE;
      print "  so we use it. Use --force to recreate.\n" if $VERBOSE;
    } else {
      print "Converting $logfile -> $convertedlog ...\n" if $VERBOSE;
      convert_timeformat ($logfile, $convertedlog);
    }
    $okfile = $convertedlog;
  } else {
    $okfile = $logfile;
  }

  # dest dir
  $destdir = $DESTDIR; # default
  unless ($destdir) {
    $destdir = basename ($logfile);
    $destdir =~ s/\.[^.]*$//;
    $destdir = "$DESTBASE/".$destdir;
  }

  if (-e $destdir and not $FORCE) {
    print STDERR "Destination dir $destdir already exists,\n  use --force to overwrite.\n" if $VERBOSE;
    return 0;
  } else {
    print "Create $destdir\n" if $VERBOSE;
    if (system ('mkdir', '-p', $destdir)) {  # returns 0 if ok
      print STDERR "Can't create $destdir\n";
      return 0;
    }
  }

  print "\n";

  # call parameterized fragistics
  print "Execute $RAWFRAGISTICS (the $MYNAME backend) ...\n* $okfile -> $destdir:\n" if $VERBOSE;
  print "\t$RAWFRAGISTICS \\LOGS/number_of_logs 1 \\LOGS/logpath0 $okfile \\MAIN/dest_path $destdir \\MAIN/cfgfile $CFGFILE \\MAIN/src_path $TPLTPATH\n" if $VERBOSE;

  if (system ($RAWFRAGISTICS,                # system returns 0 if ok
	      '\LOGS/number_of_logs', 1,
	      '\LOGS/logpath0', $okfile,
	      '\MAIN/dest_path', $destdir,
	      '\MAIN/cfgfile', $CFGFILE,
	      '\MAIN/src_path', $TPLTPATH
	     )) {
    print STDERR "$@";
    return 0;
  }

  return 1;
}

sub spool {
  opendir (DIR, $FRAGISTICS_SPOOL) or die "Can't opendir $FRAGISTICS_SPOOL: $!";
  my @gameslogspool = grep { /^[^.]/ } readdir (DIR);
  closedir(DIR);

  foreach my $gameslog (@gameslogspool) {
    process_log ($FRAGISTICS_SPOOL.'/'.$gameslog);
  }
}

sub init {
  print "Initialize user specific customization directory $FRAGISTICS_HOME ...\n";
  create_dirs;
  copy_global_customization_files_to_user;
}


# void main();
#
# The frontend to the fragistics engine.
# It takes command line options:
#   -f logfile
#   -o outputdir
#
# If no logfile given, it takes LOGS/logpath0 from fragistics.conf.
# If no outputdir given, it create 'stats_LOGFILEBASENAME' (according
# to a logfile 'LOGFILEBASENAME.ext').
#
sub main {
  # cmd line args and usage
  read_cmdline_args();
  pod2usage (1) if $HELP;
  pod2usage (-exitstatus => 0, -verbose => 2) if $MAN;

  if ($VERBOSE) {
    print "Fragistics frontend v1.7.0, (c) 2002 Steffen Schwigon\n";
    print "Config file $CFGFILE\n";
    print "Homedir $FRAGISTICS_HOME\n";
    print "Template path $TPLTPATH\n";
    print "Destbase $DESTBASE\n";
  }

  if ($INIT) { # init user specific fragistics environment
    init;
    exit 0;
  }

  create_dirs;

  if ($SPOOL) { # spool mode
    print "Spool mode - process all files in $FRAGISTICS_SPOOL.\n" if $VERBOSE;
    spool;
  } else {
    process_log ($LOGFILE);
  }
}

main;

__END__

=head1 NAME

frontend - Frontend to fragistics.

=head1 SYNOPSIS

frontend [options]

 Options:
  --help         ... print this help text.

  --verbose
  --noverbose    ... [Do not] describe what is going on.

  --homedir      ... Homedir, used as default for input/output files
                     (default: $FRAGISTICS_HOME or $HOME/.fragistics)

  --cfgfile      ... Which configfile to use
                     (default: "fragistics.conf" in $HOME/.fragistics or /etc/fragistics)

  --logfile      ... Which logfile to parse; ignored in spool mode
                     (default: get logfilename from config file)

  --destbase     ... Which base directory to write in spool mode
                     (default: "<homedir>/stats/")

  --destdir      ... Which directory to write; ignored in spool mode
                     (default: "<destbase>/<logfilebasename>")

  --spooldir     ... Where to search for logfiles in spool mode
                     (default: "<homedir>/spool/")

  --spool        ... Spool mode;
                     process all files in  <spoolpath>
                     write output stats to <destbase>/<logfilebasename>/

  --init         ... Initialize a users personal fragistics directory with
                     all subdirectories, local configuration file, html
                     templates and image directories by copying everything
                     needed from the global directory into $FRAGISTICS_HOME
                     or $HOME/.fragistics

=head1 DESCRIPTION

B<This program> prepares everything expected by the fragistics engine. It
mainly converts the time format and solves known artefacts in broken logfiles,
e.g., incomplete lines due to Q3 server restarts.

=cut
