#!/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
# 3-Feb-2004
# 13:09 Started translating this thing into English
# 13:16 Coding completed, starting testing
# 13:18 Seems to be working
#
# Version 1.1e
# Author: Andrew Salo <salo@cs.karelia.ru>
# 2004-10-22  Added do_human_resources_report function (used to calculate
#             human resources)
#

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

# 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 $group_name;			# name of the group

# 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
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 \("Неправильная (или отсутствующая) дата. Дата должна " .
                   "быть записана в формате dd.mm.yyyy");
        }
        return mktime(0, 0, 0, $1 - 1, $2 - 1, $3 - 1900);
    } else {
        return \('Неправильная дата: ' . $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 'Синтаксическая ошибка в файле ', $filename,
                  ', в строке ', $lineno, ":\n",
                  "Части кода, времени или описания отсутствуют\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",
                         "Время в неправильном формате. Правильный формат времени " .
                         "dd.dd или 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 'Синтаксическая ошибка в файле ', $filename, ':',
                     "Отсутствует обозначение (ФИО) участника проекта\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 "Использование: report.pl OPTIONS\n";
    print "-r --report=NAME        Определение формата отчёта\n";
    print "                        (Возможные значения: progress, human_resources)\n";
    print "-o --output=FILE        Название файла отчёта\n";
    print "-s --start=DATE         Дата начала (dd.mm.yyyy) для отчёта о прогрессе\n";
    print "-e --end=DATE           Дата окончания (dd.mm.yyyy) для отчёта о прогрессе\n";
    print "-g --group=NAME         Название проекта или группы\n";
    print "-v --version            Печать версии программы\n";
    print "-h --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,
        'group=s'	=> \$group_name,
        'version'	=> \$print_version,
        'help' => \$help
        )
    );
    if ($print_version) {
        print $version, "\n";
        exit 0;
    }
    if ($help || !defined $report) {
        print_usage;
    } 
    if (defined $report && !defined $output_file) {
        print STDERR "Необходимо определить название файла отчёта\n";
        print STDERR "Проконсультируйтесь с опцией --output\n";
        exit 1;
    }
    if (!defined $group_name) {
        print STDERR "Предупреждение: Неопределено название группы опцией --group, по умолчанию используется 'none'\n";
        $group_name = 'none';
    }
}

# Writes a progress report to the specified file.
sub do_progress_report() {
    if (!defined $start_date) {
        print STDERR "Не определена дата начала.\n";
        print STDERR "См. справку --help синтаксиса команд\n";
        exit 1;
    }
    if (!defined $end_date) {
        print STDERR "Не определена дата окончания.\n";
        print STDERR "См. справку --help синтаксиса команд\n";
        exit 1;
    }
    my $start_stamp = parse_datestr $start_date;
    if (ref $start_stamp) {
        print STDERR 'Ошибка опции --start: ', $$start_stamp, "\n";
        exit 1;
    }
    my $end_stamp = parse_datestr $end_date;
    if (ref $end_stamp) {
        print STDERR 'Ошибка опции --end: ', $$end_stamp, "\n";
        exit 1;
    }
    # count hours for all persons
    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 "ОТЧЁТ О ТЕКУЩЕМ СОСТОЯНИИ ПРОЕКТА\n\n";
    print F "Группа:\t\t\t", $group_name, "\n";
    print F "Период:\t\t\t", $start_date, ' - ', $end_date, "\n\n\n";
    print F "Участник\t\tв этот период\tВсего часов\n";
    print F "---------------------------------------------\n";
    foreach (sort {lc($a) cmp lc($b)} keys %hours) {
        printf F "%-23.23s %d\t\t%d\n", $_, ${$hours{$_}}[0], ${hours{$_}}[1];
    }
    print F "\n\nТекущее состояние проекта\n";
    print F "-----------------------------\n\n\n";
    print F "Завершённые документы (название и ссылка)\n";
    print F "--------------------------------------\n\n\n";
    print F "Отклонения/комментарии менеджера\n";
    print F "---------------------------------\n";
    close F;
}

# Writes human resources report.
sub do_human_resources_report {

    my %phase_desc_short = (reqs 	=> "RE",
                            design 	=> "PR",
			    coding 	=> "CO",
			    testing 	=> "TE",
			    meeting	=> "ME",
			    docs	=> "DO");
    my %phase_desc_long = ('reqs'	=> 'Разработка требований, написание спецификации требований',
                           'design' 	=> 'Проектирование, написание документа проектирования',
		    	   'coding' 	=> 'Кодирование (включая отладку)',
		           'testing'	=> 'Тестирование',
		           'meeting' 	=> 'Встречи',
		           'docs' 	=> 'Документирование кода или написание документации для пользователя');

    # count hours for all persons
    my %hours;
    ${hours{'Total'}} = [0, 0, 0, 0, 0, 0, 0, 0];

    foreach my $pers (@person) {
        (my $total, my $requirements, my $design, my $coding, my $testing,
	 my $meetings, my $documentation, my $other) =
	    (0, 0, 0, 0, 0, 0, 0, 0);
        foreach my $rec (@{$pers->{hourlist}}) {
            $total += $rec->{hours};
            if ($rec->{phase} eq $phase_desc_short{'reqs'}) {
		$requirements += $rec->{hours};
	    }
	    elsif ($rec->{phase} eq $phase_desc_short{'design'}) {
		$design += $rec->{hours};
            }
	    elsif ($rec->{phase} eq $phase_desc_short{'coding'}) {
		$coding += $rec->{hours};
            }
	    elsif ($rec->{phase} eq $phase_desc_short{'testing'}) {
		$testing += $rec->{hours};
            }
	    elsif ($rec->{phase} eq $phase_desc_short{'meeting'}) {
		$meetings += $rec->{hours};
            }
	    elsif ($rec->{phase} eq $phase_desc_short{'docs'}) {
		$documentation += $rec->{hours};
            }
	    else {
		$other += $rec->{hours};
            }
        }
        $hours{$pers->{name}} = [$requirements, $design, $coding, $testing, 
				 $meetings, $documentation, $other, $total];
	for (my $i = 0; $i < 8; $i++) {
	    $hours{'Total'}[$i] += $hours{$pers->{name}}[$i];
	}
    }

    # print the report
    open F, '>' . $output_file or die "$output_file: $!\n";
    print F "ОТЧЁТ О ЧЕЛОВЕЧЕСКИХ РЕСУРСАХ\n\n";
    print F "Группа:\t\t\t", $group_name, "\n\n\n";
    print F "Участник\t\t$phase_desc_short{'reqs'}\t$phase_desc_short{'design'}\t$phase_desc_short{'coding'}",
            "\t$phase_desc_short{'testing'}\t$phase_desc_short{'meeting'}\t$phase_desc_short{'docs'}\tДругое\tИтого\n";
    print F "-------------------------------------------------------------------------------------\n";
    foreach (sort {lc($a) cmp lc($b)} keys %hours) {
	if ($_ ne 'Total') {
	    printf F "%-23.23s", $_;

	    for (my $i = 0; $i < 8; $i++) {
		printf F "\t%d", $hours{$_}[$i];
	    }

	print F "\n";
	}
    }


    print F "-------------------------------------------------------------------------------------\n";
    printf F "%-23.23s", 'Итого';
    for (my $i = 0; $i < 8; $i++) {
	printf F "\t%d", $hours{'Total'}[$i];
    }
    print F "\n\n\n";

    print F "Описание обозначений\n";
    print F "-----------------------\n";
    print F "$phase_desc_short{'reqs'} - $phase_desc_long{'reqs'}\n";
    print F "$phase_desc_short{'design'} - $phase_desc_long{'design'}\n";
    print F "$phase_desc_short{'coding'} - $phase_desc_long{'coding'}\n";
    print F "$phase_desc_short{'testing'} - $phase_desc_long{'testing'}\n";
    print F "$phase_desc_short{'docs'} - $phase_desc_long{'docs'}\n";
    print F "$phase_desc_short{'meeting'} - $phase_desc_long{'meeting'}\n";

    close F;
}

################################################################################
# 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 'human_resources') {
    do_human_resources_report;
}
else {
    print STDERR 'Неизвестный отчёт: ', $report, "\n";
    exit 1;
}

exit 0;
