#!/usr/bin/env perl
# $Id: git-stats,v 1.6 2019/12/25 20:01:53 tom Exp $
# -----------------------------------------------------------------------------
# Copyright 2019 by Thomas E. Dickey
#
#                         All Rights Reserved
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Except as contained in this notice, the name(s) of the above copyright
# holders shall not be used in advertising or otherwise to promote the
# sale, use or other dealings in this Software without prior written
# authorization.
# -----------------------------------------------------------------------------
# Make a summary report for files stored in Git, showing the number of commits,
# and amount of change for each committer.

# TODO report start/end date too, and give rank for commits and lines

use strict;
use warnings;
use diagnostics;

use Getopt::Std;
use Text::CharWidth qw(mbswidth);

$| = 1;

our ( $opt_d, $opt_u );

our %committers;
our %totals;
our $widest = 8;

sub update($$$$) {
    my %obj  = %{ $_[0] };
    my %next = %{ $_[1] };
    my %last;
    if ( $obj{FILES} ) {
        %last = %{ $obj{FILES} };
    }
    for my $file ( keys %next ) {
        $last{$file}++;
    }
    $obj{FILES} = \%last;
    $obj{COMMIT} += 1;
    $obj{INSERT} += $_[2];
    $obj{DELETE} += $_[3];
    return \%obj;
}

sub count($$$$) {
    my $author = shift;
    my $insert = shift;
    my $delete = shift;
    my %files  = %{ $_[0] };
    if ( $author ne "" ) {
        my $length = 2 + mbswidth($author);
        $widest = $length if ( $length > $widest );
        my %author;
        if ( defined $committers{$author} ) {
            %author = %{ $committers{$author} };
        }
        %author = %{ &update( \%author, \%files, $insert, $delete ) };
        $committers{$author} = \%author;

        %totals = %{ &update( \%totals, \%files, $insert, $delete ) };
    }
}

sub do_file($) {
    my $param = shift;
    my @input;
    if ( open( FP, "git log --numstat \"$param\" 2>/dev/null |" ) ) {
        (@input) = <FP>;
        close FP;
    }
    my $total = 0;

    my $author = "";
    my %file_changed;
    my $insertions = 0;
    my $deletions  = 0;

    for my $n ( 0 .. $#input ) {
        chomp $input[$n];
        if ( $input[$n] =~ /^commit\s/ ) {
            &count( $author, $insertions, $deletions, \%file_changed );
            $author       = "";
            %file_changed = ();
            $insertions   = 0;
            $deletions    = 0;
            $total++;
        }
        elsif ( $input[$n] =~ /^Author:\s/ ) {
            $author = $input[$n];
            $author =~ s/^Author:\s+//;
            $author =~ s/\s\<.*//;
        }
        elsif ( $input[$n] =~ /^\d+\s+\d+\s+/ ) {
            my $temp = $input[$n];
            $temp =~ s/\s+.*$//;
            $insertions += $temp;
            $temp = $input[$n];
            $temp =~ s/^\d+\s+(\d+)\s+.*$/$1/;
            $deletions += $temp;
            my $filename = $input[$n];
            $filename =~ s/^\d+\s+\d+\s+//;
            $file_changed{$filename}++;
        }
    }
    &count( $author, $insertions, $deletions, \%file_changed );
}

sub report($$) {
    my $name  = shift;
    my %data  = %{ $_[0] };
    my %files = %{ $data{FILES} };
    my @files = keys %files;
    printf "%s", $name;
    printf "%*s", $widest - mbswidth($name), $opt_d;
    printf "%8d$opt_d" . "%8d$opt_d" . "%8d$opt_d" . "%8d\n", 1 + $#files,
      $data{COMMIT}, $data{INSERT}, $data{DELETE};
}

sub main::HELP_MESSAGE() {
    printf STDERR <<EOF
Usage: $0 [options]

Options:

-d SEP     set field-separator (default: space)
-u         write UTF-8 report
EOF
      ;
    exit;
}

&getopts('d:u') || &main::HELP_MESSAGE;
$opt_d = ' ' unless ($opt_d);

binmode( STDOUT, ":utf8" ) if ($opt_u);

if ( $#ARGV >= 0 ) {
    while ( $#ARGV >= 0 ) {
        &do_file( shift @ARGV );
    }
}
else {
    &do_file(".");
}

printf "%-*s$opt_d" . "%8s$opt_d" . "%8s$opt_d" . "%8s$opt_d%8s\n",
  $widest - 1, "Author", "Files", "Commits",
  "Inserts", "Deletes";
foreach my $committer ( sort keys %committers ) {
    &report( $committer, $committers{$committer} );
}
&report( "TOTAL", \%totals );

1;
