#!/usr/bin/perl -w
use strict;
use POSIX qw(mktime);
use Getopt::Long;

#
# report.pl - This program can be used to create various reports
# from text files which contain working hours of individual project workers.
#
# Author and maintainer: Turjo Tuohiniemi <tuohinie@cs.helsinki.fi>
#
# History of making
# 6-Sep-2002
# 12:10 Started writing code (had oral plans and requirements)
# 12:55 First demo with the client
# 13:30 Break (other work related duties)
# 13:35 Coding continues
# 14:03 Testing (got progress report ready)
# 14:08 Bugs fixed and done with testing
# 14:08 Second demo with the client
# 14:10 Client approved the program
# 
# 6-Sep-2004
# 04:55 Started writing code (had new user manual, that is, plan)
# 05:15 Done coding, testing
# 05:18 Seems to work

# version number
my $version = 'Report 1.1';

# command line arguments
my $report;			# report to be produced
my $output_file;		# output file for the report
my $start_date;			# start date for progress report
my $end_date;			# end date for progress report
my $phase_code;			# phase code for phase report

# list of hours we have collected, sorted by person. Each entry contains
# a reference to a hash which has the following keys:
# 'name' => scalar that holds the name of the person
# 'hourlist' => reference to array in which each element is a reference to
#	hash with the following keys:
#	'date' => timestamp (integer) of the entry
#	'hours' => how many hours this entry counts for
#	'phase' => the phase code of the entry
my @person;

# Parses a date string in the form dd.mm.yyyy into an integer timestamp.
# The date string is specified as the parameter. A scalar return value
# indicates success and contains the timestamp. A reference indicates
# error and contains a string which describes the error.
sub parse_datestr($) {
    my $date = shift;
    if ($date =~ /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/) {
        if ($1 < 1 || $1 > 31 || $2 < 1 || $2 > 12 || $3 < 1900) {
            return \("Incorrect (or unexistent) date. Date should " .
                   "be written as dd.mm.yyyy");
        }
        return mktime(0, 0, 0, $1 - 1, $2 - 1, $3 - 1900);
    } else {
        return \('Illegal date: ' . $date);
    }
}

# Reads an hour description file. The first line contains the name of the
# person, subsequent lines contain individual hour records. A record line
# has four columns, separated by tabs or spaces: the date in format dd.mm.yyyy,
# phase code, number of hours, and a brief description of the time used.
# Any line that begins with the hash sign (#) will be ignored as a comment.
# Name of the file to be read is specified as the only parameter.
# Modifies the global @person variable, returns nothing. Exits in case
# of error.
sub read_hour_file($) {
    my $filename = shift;
    open F, '<' . $filename or die "$filename: $!\n";
    my $lineno = 0;
    my $reading_name = 1;
    my $name;
    my @hour_entries;
    while (<F>) {
        $lineno++;
        chomp;
        # ignore comments and empty lines
        next if /^\s*#/;
        next if /^\s*$/;
        if ($reading_name) {
            # the first line holds the name of the person
            $name = $_;
            $reading_name = 0;
            next;
        }
        # parse line
        /^(.*?)\s+(.*?)\s+(.*?)\s+(.+)$/;
        if (!defined $4) {
            print STDERR 'Syntax error in file ', $filename,
                  ', on line number ', $lineno, ":\n",
                  "Phase code, hours or description missing\n";
            exit 1;
        }
        (my $date, my $phase, my $hours) = ($1, $2, $3);
        my $timestamp = parse_datestr($date);
        if (ref $timestamp) {
            print STDERR 'Error in file ', $filename,
                         ', on line number ', $lineno, ":\n",
                         $$timestamp, "\n";
            exit 1;
        }
        if (not $hours =~ /^((\d+(.\d+)?)|(\.\d+))$/) {
            print STDERR 'Syntax error in file ', $filename,
                         ', on line number ', $lineno, ":\n",
                         "Hour improperly formatted. Valid formats are " .
                         "dd.dd or dd\n";
            exit 1;
        }
        # construct a hash for the entry and save it
        my %entry = (
            'date' => $timestamp,
            'phase' => $phase,
            'hours' => $hours
        );
        push @hour_entries, \%entry;
    }
    # we're done with this file
    close F;
    if ($reading_name) {
        print STDERR 'Syntax error in file ', $filename, ':',
                     "The file does not contain the name of any person\n";
        exit 1;
    }
    # finally, append an entry to @person
    my %pers = (
        'name' => $name,
        'hourlist' => \@hour_entries
    );
    push @person, \%pers;
}

# Prints the usage and exits
sub print_usage() {
    print "Usage: report.pl OPTIONS\n";
    print "-r --report=NAME        Specify report to be produced\n";
    print "                        (Possible values: progress, phase)\n";
    print "-o --output=FILE        Output report to file\n";
    print "-c --code=PHASE         Count hours of the specified phase\n";
    print "-s --start=DATE         Start date (dd.mm.yyyy) for progress report\n";
    print "-e --end=DATE           End date (dd.mm.yyyy) for progress report\n";
    print "-v --version            Print version number\n";
    print "-h --help               Print this help\n";
    exit 1;
}

# Processes command line arguments. Sets up global variables according
# to arguments. Returns nothing.
sub handle_cmdline() {
    my $help = 0;
    my $print_version = 0;
    exit(1) if (!GetOptions(
        'report=s'	=> \$report,
        'output=s'	=> \$output_file,
        'start=s'	=> \$start_date,
        'end=s'		=> \$end_date,
        'version'	=> \$print_version,
        'help'		=> \$help,
        'code=s'	=> \$phase_code
        )
    );
    if ($print_version) {
        print $version, "\n";
        exit 0;
    }
    if ($help || !defined $report) {
        print_usage;
    } 
    if (defined $report && $report ne 'phase' && !defined $output_file) {
        print STDERR "You must specify output file for the report\n";
        print STDERR "Consult the --output option\n";
        exit 1;
    }
    if (($report || '') eq 'phase' && defined $output_file) {
        print STDERR "You may not specify an output file for the phase report\n";
        exit 1;
    }
    
}

# Writes a progress report to the specified file.
sub do_progress_report() {
    if (!defined $start_date) {
        print STDERR "No start date specified.\n";
        print STDERR "See --help for command line syntax\n";
        exit 1;
    }
    if (!defined $end_date) {
        print STDERR "No end date specified.\n";
        print STDERR "See --help for command line syntax\n";
        exit 1;
    }
    my $start_stamp = parse_datestr $start_date;
    if (ref $start_stamp) {
        print STDERR 'Error with --start: ', $$start_stamp, "\n";
        exit 1;
    }
    my $end_stamp = parse_datestr $end_date;
    if (ref $end_stamp) {
        print STDERR 'Error with --end: ', $$end_stamp, "\n";
        exit 1;
    }
    # count hours for each person
    my %hours;
    foreach my $pers (@person) {
        (my $total, my $in_period) = (0, 0);
        foreach my $rec (@{$pers->{hourlist}}) {
            $total += $rec->{hours};
            if ($rec->{date} >= $start_stamp && $rec->{date} <= $end_stamp) {
                $in_period += $rec->{hours};
            }
        }
        $hours{$pers->{name}} = [$in_period, $total];
    }
    # print the report
    open F, '>' . $output_file or die "$output_file: $!\n";
    print F "EDISTYMISRAPORTTI\n\n";
    print F "Ajanjakso:\t\t", $start_date, ' - ', $end_date, "\n\n\n";
    print F "Työtunnit\t\tTämä jakso\tYhteensä\n";
    print F "------------------------------------------------\n";
    foreach (sort {lc($a) cmp lc($b)} keys %hours) {
        printf F "%-24.24s %d\t\t%d\n", $_, ${$hours{$_}}[0], ${hours{$_}}[1];
    }
    print F "\n\nAlkaneet projektivaiheet (pvm ja vaihe)\n";
    print F "---------------------------------------\n\n\n";
    print F "Valmistuneet dokumentit (pvm ja dokum. nimi)\n";
    print F "--------------------------------------------\n\n\n";
    print F "Ryhmän työskentelyyn vaikuttaneita asioita\n";
    print F "------------------------------------------\n";
    close F;
}

# Prints a phase report.
sub do_phase_report() {
    if (!defined $phase_code) {
        print STDERR "No phase code specified.\n";
        print STDERR "See --help for command line syntax\n";
        exit 1;
    }
    $phase_code = lc $phase_code;
    # count hours for each person
    my %hours;
    foreach my $pers (@person) {
        my $total = 0;
        foreach my $rec (@{$pers->{hourlist}}) {
            $total += $rec->{hours} if (lc $rec->{phase} eq $phase_code);
        }
        $hours{$pers->{name}} = $total;
    }
    # print the report
    foreach (sort {lc($a) cmp lc($b)} keys %hours) {
        printf "%-24.24s %d\n", $_, $hours{$_};
    }
}

################################################################################
# Main program starts here
#

# handle command line arguments
handle_cmdline;

# read in all .txt files
foreach (glob('*.txt')) {
    read_hour_file $_;
}

# produce the requested report
if ($report eq 'progress') {
    do_progress_report;
} elsif ($report eq 'phase') {
    do_phase_report;
} else {
    print STDERR 'Unknown report requested: ', $report, "\n";
    exit 1;
}

exit 0;
