#!/usr/bin/env perl
use strict;
use warnings;

use DateTime;
use DateTime::Format::Natural;
use Getopt::Long::Descriptive qw(describe_options prog_name);
use WWW::Mechanize;

# Check command line syntax.
my ($opt, $usage) = describe_options(
        '%c %o <Base-Trac-URL> <start-date> <end-date>',
        [ 'csv'         => 'Brief CSV output instead of the usual report.' ],
        [ 'version'     => 'Output program version and exit.' ],
        [ 'help'        => 'Print usage message and exit.' ]
    );

my $prog_name = prog_name();

print(<<END), exit if $opt->help;
$prog_name: Report the number of Trac tickets opened and closed in the given
time period.

Usage: $usage

Base Trac URL is the location of Trac installation, the start and end dates
define the period to report for, in any common date format. For example:

    $prog_name https://trac.example.com/ 2011-07-24 2011-07-31

or try a more relaxed

    $prog_name trac.example.com "last monday" today

form.
END
print("$prog_name version 1.0.\n"), exit if $opt->version;

die "Usage: $usage\n" if @ARGV != 3;


# Extract parameters.
my $timeline_url = $ARGV[0];
$timeline_url = "http://$timeline_url" if $timeline_url !~ m@^\w+://@;
$timeline_url .= '/' unless $timeline_url =~ m@/$@;
$timeline_url .= 'timeline';

my $parser = DateTime::Format::Natural->new;
my $start_date = $parser->parse_datetime($ARGV[1]);
die "Invalid start date: " . $parser->error() if !$parser->success;

my $end_date = $parser->parse_datetime($ARGV[2]);
die "Invalid end date: " . $parser->error() if !$parser->success;


# Transform them to Trac weird (end, days back) input format.
my $trac_days_back = $end_date->delta_days($start_date)->delta_days;

# NB: Trac always seems to use US date format with 2 digit years currently.
my $trac_end_date = $end_date->strftime("%m/%d/%y");


# Do get the timeline. Unfortunately this requires loading the page twice,
# first time to get the form and the second one to submit it. It would be nice
# to avoid this but I don't see how.
my $mech = WWW::Mechanize->new(autocheck => 1);

$mech->get($timeline_url);
my $response = $mech->submit_form(
        form_id => 'prefs',
        fields => {
            from => $trac_end_date,
            daysback => $trac_days_back,
            ticket => 'on',
            ticket_details => undef,
            changeset => undef,
            milestone => undef,
            wiki => undef,
        }
    );

my $timeline_dump = $response->content;


# Don't bother really parsing HTML, just count the lines.

# Use an array to have a well-defined order of ticket kinds in the report
# output below.
my @ticket_kinds = qw(new closed reopened);
my %counters = map { $_ => 0 } @ticket_kinds;

for (split /\n/, $timeline_dump) {
    next unless /<dt class="(\w+)ticket">/;

    if ( !exists($counters{$1}) ) {
        warn qq{Unknown timeline ticket entry "$1ticket".\n};
        next;
    }

    $counters{$1}++;
}


# Finally output the results in either human-readable report or
# machine-readable CSV format.
if ( $opt->csv ) {
    print $start_date->strftime('%F'), ',',
          $end_date->strftime('%F'), ',',
          $counters{new}, ',',
          $counters{closed}, ',',
          $counters{reopened}, "\n";
}
else {
    print "Ticket statistics for $timeline_url" .
            ' from ' . $start_date->strftime('%F') .
            ' to ' . $end_date->strftime('%F') .
            ":\n";
    print '-' x 20 . "\n";
    for (@ticket_kinds) {
        printf "%-10s%10d\n", ucfirst, $counters{$_};
    }

    print '-' x 20 . "\n";
    printf "%-10s%10d\n",
           'Delta', $counters{new} + $counters{reopened} - $counters{closed};
}

exit 0
