#!/usr/bin/perl -w

use strict;
use warnings;
use Getopt::Long;
use Pod::Usage;
use App::HWD;
use Date::Manip;
use Text::CSV_XS;

MAIN: {
    my $show_nextid;
    my $show_started;
    my $show_tasks;
    my $show_burndown;
    my $show_todo;
    my $csv;
    my $wrap = 72;

    Getopt::Long::Configure( "no_ignore_case" );
    Getopt::Long::Configure( "bundling" );
    GetOptions(
        'nextid'        => \$show_nextid,
        'todo'          => \$show_todo,
        'started:s'     => \$show_started,
        'tasks:s'       => \$show_tasks,
        'burndown'      => \$show_burndown,
        'wrap:i'        => \$wrap,
        'csv!'          => \$csv,
        'h|help|?'      => sub { pod2usage({-verbose => 1}); exit; },
        'H|man'         => sub { pod2usage({-verbose => 2}); exit; },
        'V|version'     => sub { print_version(); exit; },
    ) or exit 1;
    #die "Must specify input files\n" unless @ARGV;

    # XXX the --started and --tasks options with no argument eats the filename.
    # Attempt to compensate.
    for my $var ($show_started, $show_tasks) {
        if ($var and -e $var) {
            unshift @ARGV, $var;
            $var = '';
        }
    }

    my ($tasks,$works,$tasks_by_id) = App::HWD::get_tasks_and_work( <> );

    if ( $show_nextid ) {
        my $max = (sort {$a <=> $b} keys %$tasks_by_id )[-1];
        $max = $max ? $max+1 : 101;
        print "Next task ID: $max\n";
        exit;
    }

    my $show_full_dump = 1;
    my $filter = undef;

    if ( $csv ) {
        show_full_dump( $tasks, $filter, $wrap, $csv );
        $show_full_dump = 0;
    }

    if ( defined $show_tasks ) {
        show_tasks( $show_tasks, $tasks, $works, $tasks_by_id );
        $show_full_dump = 0;
    }

    if ( $show_burndown ) {
        show_burndown( $tasks, $works, $tasks_by_id );
        $show_full_dump = 0;
    }

    if ( defined $show_started ) {
        show_started( $show_started, $tasks, $works, $tasks_by_id );
        $show_full_dump = 0;
    }

    if ( $show_todo ) {
        $filter = sub {
            my $task = shift;
            return $task->is_todo;
        };
        show_full_dump( $tasks, $filter, $wrap, $csv );
        $show_full_dump = 0;
    }

    if ( $show_full_dump ) {
        show_full_dump( $tasks, $filter, $wrap, $csv );
        print "\n";
        show_totals( $tasks );
    }
}


sub show_full_dump {
    my $tasks = shift;
    my $filter = shift;
    my $wrap = shift;
    my $csv = shift;

    my @fields = qw( estimated velocity started unstarted deleted );

    my %total;
    $total{$_} = 0 for @fields;

    for my $task ( @$tasks ) {
        my $points = $task->estimate || 0;
        if ( $task->date_deleted ) {
            $total{deleted} += $points;
        }
        else {
            if ( $points ) {
                $total{estimated}   += $points;
                $total{velocity}    += $points if $task->completed;
                $total{started}     += $points if $task->started && !$task->completed;
                $total{unstarted}   += $points if !$task->started;
            }
            if ( !$filter || $filter->( $task ) ) {
                print_task( $task, $wrap, $csv );
            }
        }
    }

    if ( !$csv ) {
        print "\n";
        for my $type ( @fields ) {
            printf "%6.2f %s\n", $total{$type}, $type;
        }
    }
}

sub print_task {
    my $task = shift;
    my $wrap = shift;
    my $csv = shift;

    my $level = $task->level;
    my $name = $task->name;
    my $id = $task->id;

    if ( $id || $task->estimate ) {
        my $worked = $task->hours_worked;
        my $estimate = $task->estimate;

        unless ( $csv ) {
            $worked = fractiony( $worked );
            $estimate = fractiony( $estimate );
        }
        my $x = $task->completed ? "X" : " ";
        print_cols( $wrap, $csv, $level, $id, $estimate, $worked, $x, $name );
    }
    else {
        print_cols( $wrap, $csv, $level, ("") x 4, $name );
    }
}

sub print_cols {
    my $wrap = shift;
    my $csv = shift;
    my $level = shift;
    my @cols = @_;

    for ( @cols[0..0] ) {
        $_ = $_ ? sprintf( "%4d", $_ ) : "";
    }
    for ( @cols[2..5] ) {
        $_ = "" unless defined $_;
    }

    if ( $csv ) {
        my $csv = Text::CSV_XS->new;
        s/^\s+// for @cols;
        s/\s+$// for @cols;
        $csv->combine( @cols ) or die "Can't create a CSV string!";
        print join( ",", $csv->string ), "\n";
    }
    else {
        my $indent = " " x (($level-1)*4);

        my $str = sprintf( "%4s %6.6s %6.6s %1s %s %s", @cols[0..3], $indent, $cols[4] );

        if ( $wrap ) {
        }

        print $str, "\n";
    }
}


sub fractiony {
    my $n = shift;
    my $str;

    if ( $n ) {
        my $frac = $n - int($n);
        $str = sprintf( "%4d", int($n) );
        $str .= $frac ? "+" : " ";
    }
    else {
        $str = "";
    }
    return $str;
}

sub show_started {
    my ( $who, $tasks, $works, $tasks_by_id ) = @_;

    my %started;
    foreach my $w (@$works) {
        next if $who && ($who ne $w->who);
        my $t = $tasks_by_id->{$w->task};
        if ( !$t->completed() ) {
            $started{$w->who}{$t->id}++;
        }
    }
    my %unique_tasks;
    foreach my $w (sort keys %started) {
        print "$w is working on...\n";
        my $points = 0;
        foreach my $key (sort { $a <=> $b } keys %{$started{$w}}) {
            my $task = $tasks_by_id->{$key};
            print "  " . $task->summary . "\n";
            $points += $task->estimate;
            $unique_tasks{ $key } = $task->estimate;
        }
        print "$w has $points points open\n";
        print "\n";
    }
    if ( !$who ) {
        my $total_points = 0;
        $total_points += $unique_tasks{$_} for keys %unique_tasks;
        print "$total_points points open on the project\n";
    }
} # show_started


sub show_tasks {
    my ( $who, $tasks, $works, $tasks_by_id ) = @_;

    my %worker;
    foreach my $t (@$tasks) {
        foreach my $w ($t->work) {
            $worker{ $w->who }{$t->id}++;
        }
    }

    my @who = $who ? ($who) : keys %worker;
    foreach my $w (@who) {
        if ( !$worker{$w} ) {
            print "$w has no tasks!\n";
            next;
        }
        print "$w worked on:\n";
        foreach my $id (keys %{$worker{$w}}) {
            my $task = $tasks_by_id->{$id};
            print "  ", $task->summary, "\n";
        }
        print "\n";
    }
} # show_tasks


sub show_burndown {
    my ( $tasks, $works, $tasks_by_id ) = @_;

    my %day;

    # ASSUMPTION: projects will finish before Jan 1, 2100
    my $earliest = ParseDate("2100/1/1"); 

    # determine the earliest date work has been done and keep track
    # of finished task points
    foreach my $w (@$works) {
        my $date = ParseDate($w->when)
            or die "Work " . $w->task . " has an invalid date: " . $w->when;
        if (Date_Cmp($date, $earliest) < 0) {
            $earliest = $date;
        }
        if ( $w->completed ) {
            my $est = $tasks_by_id->{ $w->task }->estimate;
            $day{$date}{finished} += $est;
        }
    }

    # determine the total for each date
    foreach my $t (@$tasks) {
        next if $t->date_deleted;
        my $date = ParseDate( $t->date_added ) || $earliest;
        if ( !$date ) {
            die "Task " . $t->name . " has no date!";
        }
        $day{$date}{total} += $t->estimate;
    }

    # Print the running task and finished totals
    my $total;
    my $finished;
    my $format = "\%10s\t\%-5s\t\%-s\n";
    printf $format, qw(YYYY/MM/DD Total Todo);
    foreach my $date (sort keys %day) {
        $total += $day{$date}{total} || 0;
        $finished += $day{$date}{finished} || 0;
        $date =~ s#^(\d{4})(\d\d)(\d\d).+#$1/$2/$3#
            or die "Invalid date ($date)";
        printf $format, $date, $total, $total - $finished;
    }
}

sub show_totals {
    my ( $tasks, $works, $tasks_by_id ) = @_;

    my @totals;
    my $curr_total;

    for my $task ( @$tasks ) {
        if ( $task->level eq 1 ) {
            push( @totals, $curr_total = [ 0, $task->name ] );
        }
        if ( !$task->date_deleted ) {
            $curr_total->[0] += $task->estimate;
        }
    }

    for $curr_total ( @totals ) {
        printf( "%4d %s\n", $curr_total->[0], $curr_total->[1] );
    }
}

sub print_version {
    printf( "hwd v%s\n", $App::HWD::VERSION, $^V );
}

__END__

=head1 NAME

hwd -- The How We Doin'? project tracking tool

=head1 SYNOPSIS

hwd [options] schedule-file(s)

Options:

        --nextid    Display the next highest task ID
        --todo      Displays tasks left to do, started or not.
        --started   Displays tasks that have been started
        --started=person
                    Displays tasks started by person
        --tasks     Displays tasks sorted by person
        --tasks[=person]
                    Displays tasks for a given user
        --burndown  Display a burn-down table
        --totals    Display top-level totals

        --wrap=n    Wrap output at n columns, or 0 for no wrapping.
                    Default is 72.
        --csv       Output in CSV format

    -h, --help      Display this help
    -H, --man       Longer manpage for prove
    -V, --version   Display version info

=head1 COMMAND LINE OPTIONS

=head2 --todo

Limit the dump of tasks to only those that are left to do, whether or
not they've been started.

=head2 --started[=who]

Shows what tasks have been started by the person specified, or by everyone
if no one one is specified.

    Ape is working on...
      104 - Add FK constraints between FOOHEAD and BARDETAIL (2/2)

    Chimp is working on...
      107 - Refactor (1/1)

=head2 --tasks[=person]

Shows the list of tasks and their status sorted by user.  If a person is
specified, only the tasks for that person will be shown.

=head2 --nextid

Shows the next ID available.

=head2 --burndown

Print a "burn down" graph:

    YYYY/MM/DD      Total   Todo
    2005/07/15      100     98
    2005/07/17      100     77
    2005/07/18      100     75
    2005/07/19      100     70

That is, how fast is the amount of work left "burning down" to zero?

=head2 -V, --version

Display version info.

=head1 BUGS

Please use the CPAN bug ticketing system at L<http://rt.cpan.org/>.
You can also mail bugs, fixes and enhancements to 
C<< <bug-app-hwd at rt.cpan.org> >>.

=head1 AUTHORS

Andy Lester C<< <andy at petdance.com> >>

=head1 COPYRIGHT

Copyright 2005 by Andy Lester C<< <andy at petdance.com> >>.

This program is free software; you can redistribute it and/or 
modify it under the same terms as Perl itself.

See L<http://www.perl.com/perl/misc/Artistic.html>.

=cut

# vim: expandtab
