package App::bif::show::timesheet;
use strict;
use warnings;
use utf8;
use Bif::Mo;
use DBIx::ThinSQL qw/ sq /;
use Time::Piece;
use Time::Seconds;

our $VERSION = '0.1.5_7';
extends 'App::bif';

my ( $bold, $reset );

sub _build {
    my $id    = shift;
    my $bill  = shift;
    my $count = @_;

    my @columns;
    my @select;
    my @having;
    my $format = ' l  l  ';
    my @headers;

    my $i = 1;
    foreach my $item (@_) {
        my ( $type, $start, $delta, $rollup ) = @{$item};
        my $end;

        if ( $type eq 'year' ) {
            $start = Time::Piece->strptime( $start->strftime('%Y'), '%Y' );
            $start = $start->add_years($delta);
            $end   = $start->add_years(1);
        }
        elsif ( $type eq 'month' ) {
            $start =
              Time::Piece->strptime( $start->strftime('%Y-%m'), '%Y-%m' );
            $start = $start->add_months($delta);
            $end   = $start->add_months(1);
        }
        elsif ( $type eq 'week' ) {
            $start =
              Time::Piece->strptime( $start->strftime('%Y-%m-%d'), '%Y-%m-%d' );
            my $wday = $start->_wday - 1;
            $wday = 6 if $wday < 0;
            $start = $start + ( -$wday * ONE_DAY );
            $start = $start + ( $delta * 7 * ONE_DAY );
            $end   = $start + ( 7 * ONE_DAY );
        }
        elsif ( $type eq 'day' ) {
            $start =
              Time::Piece->strptime( $start->strftime('%Y-%m-%d'), '%Y-%m-%d' );
            $start = $start + ( $delta * ONE_DAY );
            $end   = $start + (ONE_DAY);
        }
        else {
            die "unknown type: $type";
        }

        push( @columns, "TOTAL(col$i) AS duration$i" );
        push( @having,  ' OR ' ) if @having;
        push( @having,  "SUM(col$i) >= 60" );

        my @x;
        foreach my $j ( 1 .. $count ) {
            if ( $i == $j ) {
                push( @x, "wd.stop - wd.start AS col$j" );
            }
            else {
                push( @x, "NULL AS col$j" );
            }
        }

        push(
            @select,
            [
                ( $i == 1 ? 'select' : 'union_all_select' ) =>
                  [ 'n.kind AS kind', 'n.path AS path', @x, ],
                from       => 'work_deltas wd',
                inner_join => 'changes c',
                on         => {
                    'c.id' => \'wd.change_id',
                    $id ? ( 'c.identity_id' => $id, ) : (),
                },
                $rollup
                ? (
                    inner_join => 'nodes_tree nt',
                    on         => 'nt.child = wd.node_id',
                    inner_join => 'projects p',
                    on         => 'p.id = nt.parent',
                  )
                : (
                    inner_join => 'nodes n2',
                    on         => 'n2.id = wd.node_id',
                    inner_join => 'projects p',
                    on         => 'p.id = n2.project_id',
                ),
                inner_join                => 'nodes n',
                on                        => 'n.id = p.id',
                "where -- $start -> $end" => {
                    'wd.gtime_start >=' => $start->epoch,
                    'wd.gtime_stop <'   => $end->epoch,
                    $bill ? ( 'wd.bill' => 1 ) : (),
                },
            ]
        );

        $format .= 'r  ';

        if ( $type eq 'year' ) {
            push( @headers, $start->strftime('%Y') );
        }
        elsif ( $type eq 'month' ) {
            push( @headers, $start->strftime('%Y-%m') );
        }
        elsif ( $type eq 'week' ) {

            # Time::Piece's %V does not work under windows, so use the
            # ->method() calls instead. See
            # https://rn.cpan.org/Public/Bug/Display.html?id=105507
            push( @headers,
                sprintf( '%0.4d-W%0.2d', $start->year, $start->week ) );
        }
        elsif ( $type eq 'day' ) {
            push( @headers, $start->strftime('%Y-%m-%d') );
        }

        $i++;
    }

    $format .= ' ';

    return \@columns, \@select, \@having, $format, \@headers;
}

sub run {
    my $self = shift;
    my $opts = $self->opts;
    my $db   = $self->db;

    ( $bold, $reset ) = $self->colours(qw/bold reset/);

    my $id;
    if ( my $str = $self->opts->{identity} ) {
        unless ( $str eq '-' ) {
            my $iinfo = $self->get_node( $str, 'identity' );
            $id = $iinfo->{id};
        }
    }
    else {
        $id = $self->db->xval(
            select => 'b.identity_id',
            from   => 'bifkv b',
            where  => { key => 'self' },
        );
    }

    $opts->{date} //= localtime->strftime('%Y-%m-%d');
    my $dt = eval { Time::Piece->strptime( $opts->{date}, '%Y-%m-%d' ) }
      or return $self->err( 'InvalidDate', 'invalid date string: %s',
        $opts->{date} );

    my ( $col, $select, $having, $format, $header );

    if ( $opts->{year} ) {
        $opts->{number} ||= 4;
        ( $col, $select, $having, $format, $header ) = _build(
            $id,
            $opts->{bill},
            map { [ 'year', $dt, -( $opts->{number} - $_ ), $opts->{rollup} ] }
              1 .. $opts->{number}
        );
    }
    elsif ( $opts->{month} ) {
        $opts->{number} ||= 4;
        ( $col, $select, $having, $format, $header ) = _build(
            $id,
            $opts->{bill},
            map { [ 'month', $dt, -( $opts->{number} - $_ ), $opts->{rollup} ] }
              1 .. $opts->{number}
        );
    }
    elsif ( $opts->{week} ) {
        $opts->{number} ||= 4;
        ( $col, $select, $having, $format, $header ) = _build(
            $id,
            $opts->{bill},
            map { [ 'week', $dt, -( $opts->{number} - $_ ), $opts->{rollup} ] }
              1 .. $opts->{number}
        );
    }
    elsif ( $opts->{day} ) {
        $opts->{number} ||= 3;
        ( $col, $select, $having, $format, $header ) = _build(
            $id,
            $opts->{bill},
            map { [ 'day', $dt, -( $opts->{number} - $_ ), $opts->{rollup} ] }
              1 .. $opts->{number}
        );
    }
    else {
        ( $col, $select, $having, $format, $header ) = _build(
            $id,
            $opts->{bill},
            [ 'day',   $dt, 0, $opts->{rollup} ],
            [ 'week',  $dt, 0, $opts->{rollup} ],
            [ 'month', $dt, 0, $opts->{rollup} ],
            [ 'year',  $dt, 0, $opts->{rollup} ],
        );
        $opts->{number} = 4;
    }

    my @data = $db->xarrayrefs(
        select   => [ 'kind', 'path', @$col, ],
        from     => sq( map   { @$_ } @$select ),
        group_by => [qw/kind path/],

        #        having   => $having,
        order_by => 'path',
    );

    if ( !@data ) {
        print "Timesheet is empty (or < 0:01) for selected period.\n";
    }
    else {
        my @totals = ( 'TOTAL', '-' );

        foreach my $row (@data) {
            unless ( $opts->{rollup} and $row->[1] =~ m!/! ) {
                $totals[ 1 + $_ ] += $row->[ 1 + $_ ] for 1 .. $opts->{number};
            }
            $row->[ 1 + $_ ] = sprintf( '%d:%0.2d',
                int( $row->[ 1 + $_ ] / 3600 ),
                ( $row->[ 1 + $_ ] - 3600 * int( $row->[ 1 + $_ ] / 3600 ) ) /
                  60 )
              for 1 .. $opts->{number};
        }

        $totals[ 1 + $_ ] = sprintf( '%d:%0.2d',
            int( $totals[ 1 + $_ ] / 3600 ),
            ( $totals[ 1 + $_ ] - 3600 * int( $totals[ 1 + $_ ] / 3600 ) ) /
              60 )
          for 1 .. $opts->{number};

        push( @data, [ '-', '', map { '' } 1 .. $opts->{number} ], \@totals );

        $self->start_pager;
        print $self->render_table( $format, [ qw/Type Path/, @$header ],
            \@data );
    }

    if ( $opts->{year} ) {
        return $self->ok('ShowTimesheetYear');
    }
    elsif ( $opts->{month} ) {
        return $self->ok('ShowTimesheetMonth');
    }
    elsif ( $opts->{week} ) {
        return $self->ok('ShowTimesheetWeek');
    }
    elsif ( $opts->{day} ) {
        return $self->ok('ShowTimesheetDay');
    }

    return $self->ok('ShowTimesheet');
}

1;
__END__

=head1 NAME

=for bif-doc #timesheet

bif-show-timesheet - display time billed per project

=head1 VERSION

0.1.5_7 (2015-11-25)

=head1 SYNOPSIS

    bif show timesheet [DATE] [OPTIONS...]

=head1 DESCRIPTION

The C<bif-show-timesheet> command displays the amount of work recorded
on projects over the current year, month, week, and day.

Only projects worked longer than 1 minute in total over any of the
requested timespans are displayed.

=head1 ARGUMENTS & OPTIONS

=over

=item DATE

A date in format YYYY-MM-DD from which the first time period is
selected. Defaults to the current day.

=item --billed, -b

Only count work items that were recorded as billable.

=item --day, -d

Display multiple daily timesheets. The default number is 3.

=item --identity, -i IDENTITY_ID

Display timesheet for IDENTITY_ID. Use "-" to timesheet all identities.

=item --month, -m

Display multiple monthly timesheets. The default number is 4.

=item --number, -n NUMBER

Display NUMBER time periods instead of the default.

=item --rollup, -r

Roll times worked into the parent project(s) for accumulative values.

=item --week, -w

Display multiple weekly timesheets. The default number is 4.

=item --year, -y

Display multiple yearly timesheets. The default number is 4.

=back

=head1 SEE ALSO

L<bif-work>(1)

=head1 AUTHOR

Mark Lawrence E<lt>nomad@null.netE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright 2015 Mark Lawrence <nomad@null.net>

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; either version 3 of the License, or (at your
option) any later version.

