The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package App::TimeClock::DailyReport;

use POSIX qw(difftime strftime);
use Time::Local;
use Time::Piece;

=head1 NAME

App::TimeClock::DailyReport

=head1 DESCRIPTION

Can parse the timelog and generate a report using an instance of a
L<App::TimeClock::PrinterInterface>.

=head2 METHODS

=over

=item new($timelog, $printer)

Initializes a new L<App::TimeClock::DailyReport> object.

Two parameters are required:

=over

=item B<$timelog>

Must point to a timelog file. Will die if not.

=item B<$printer>

An object derived from L<App::TimeClock::PrinterInterface>. Will die if not.

=back

=cut
sub new {
    my $class = shift;
    my $self = {
        timelog => shift,
        printer => shift,
    };
    die "timelog ($self->{timelog}) does not exist" unless -f $self->{timelog} and -r $self->{timelog};
    die "printer is not a PrinterInterface" unless $self->{printer}->isa("App::TimeClock::PrinterInterface");
    bless $self, $class;
};


=item _timelocal() 

Returns a time (seconds since epoch) from a date and time.

=cut
sub _timelocal {
    my ($self, $date, $time) = @_;
    my ($year, $mon, $mday) = split(/\//, $date);
    my ($hours, $min, $sec ) = split(/:/, $time);

    return timelocal($sec, $min, $hours, $mday, $mon-1, $year);
};

=item _get_report_time()

Returns the time when the report was executed.

=cut
sub _get_report_time { $_[0]->{_report_time} || time }

=item _set_report_time()

Sets the time when the report is executed.

=cut
sub _set_report_time { $_[0]->{_report_time} = $_[0]->_timelocal($_[1], $_[2]) }

=item execute()

Opens the timelog file starts parsing it, looping over each day and
calling print_day() for each.

=cut
sub execute {
    my $self = shift;

    open FILE, '<', $self->{timelog} or die "$!\n";
    binmode FILE, ':utf8';

    my %projects;
    my ($current_project, $current_date, $work, $work_total);
    my ($start_time, $end_time);
    my ($work_year_to_date, $day_count) = (0,0);

    $current_date = "";
    $work_total = 0;

    $self->{printer}->print_header;

    while (not eof(FILE)) {
        chomp(my $iline = <FILE>);
        die "Expected check in in line $." unless $iline =~ /^i /;
        
        my $oline = undef;
        if (not eof(FILE)) {
            chomp($oline = <FILE>);
            die "Excepted check out in line $." unless $oline =~ /^o /;
        }

        # Split the line, it should contain:
        #
        # - state is either 'i' - check in or 'o' - check out.
        # - date is formatted as YYYY/MM//DD
        # - time is formatted as HH:MM:SS
        # - project is then name of the project/task and is only required when checking in.
        #
        my ($idate, $itime, $iproject) = (split(/ /, $iline, 4))[1..3];
        my ($odate, $otime, $oproject) = (defined $oline) ? (split(/ /, $oline, 4))[1..3] :
          (strftime("%Y/%m/%d", localtime($self->_get_report_time)),
           strftime("%H:%M:%S", localtime($self->_get_report_time)), "DANGLING");

        if (!length($current_date)) {
            # First check in, set the current date and start time
            $current_date = $idate;
            $start_time = $itime;
        } elsif ($current_date ne $idate) {
            # It's a new day, print the current day, update totals and reset variables
            $self->{printer}->print_day($current_date, $start_time, $end_time, $work_total, %projects);

            $work_year_to_date += $work_total;
            $day_count++;

            $work_total = 0;
            $current_date = $idate;
            $start_time = $itime;
            %projects = ();
            $end_time = "";
        }

        $current_project = $iproject;
        $work = difftime($self->_timelocal($odate, $otime), $self->_timelocal($idate, $itime)) / 60 / 60;
        $work_total += $work;
        $end_time = $otime;
        $projects{$current_project} += $work;

        if (defined $oproject && $oproject eq "DANGLING") {
            $projects{"$current_project (NOT checked out)"} = $projects{$current_project};
            delete $projects{$current_project};
        }
    }

    # Print the last day (in the loop we're only printing when date changes)
    if (length($current_date)) {
	$self->{printer}->print_day($current_date, $start_time, $end_time, $work_total, %projects);
	$work_year_to_date += $work_total;
	$day_count++;
    }

    $self->{printer}->print_footer($work_year_to_date, $day_count);
};
1;

=back

=for text
=encoding utf-8
=end

=head1 AUTHOR

Søren Lund, C<< <soren at lund.org> >>

=head1 SEE ALSO

L<timeclock.pl>

=head1 COPYRIGHT

Copyright (C) 2012 Søren Lund

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 dated June, 1991 or at your option
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

A copy of the GNU General Public License is available in the source tree;
if not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.