#!/usr/bin/perl -w
#< dirdate.pl - show the latest file dates...
# 12/11/2011 - Option --norepo, to EXCLUDE all repo folders.
# 11/09/2011 - Add option --invert, to invert the time sort, and show earliest (oldest) file
# 16/07/2011 - Add .suo and .ncb to the def msvc excludes (and .o for gcc Qt compiles)
# 06/06/2011 - Like in Ubuntu, added by-day -d option
# 15/03/2011 - copied from Ubuntu bin, but fixed -? exit
use strict;
use warnings;
use File::stat;
use File::Basename; # split path ($n,$d,$e) = fileparse($file, qr/\.[^.]*/);
use Time::gmtime;
my $perl_dir = 'C:\GTools\perl';
unshift(@INC, $perl_dir);
require 'lib_utils.pl' or die "Unable to load 'lib_utils.pl ...\n";
# log file stuff
my ($LF);
my $pgmname = $0;
if ($pgmname =~ /(\\|\/)/) {
    my @tmpsp = split(/(\\|\/)/,$pgmname);
    $pgmname = $tmpsp[-1];
}
my $outfile = $perl_dir."\\temp.$pgmname.txt";
open_log($outfile);

# user variables
my $load_log = 0;
my $no_repo = 0;

my $my_version = "Version 0.0.5 2011-09-11";
#my $my_version = "Version 0.0.4 2011-06-06";
my $os = $^O;

my @excluded_exts = (); # qw( .Po .o );
my @excluded_files = ();

my @def_msvc_excludes = qw( .obj .dep .dll .res .lib .exe .ilk .pdb .manifest .exp .idb
    .user .vcproj .dsp .sln .dsw .suo .ncb .old .bak .o );
my @def_msvc_files = qw( Buildlog.htm );

# options
my $invert_date = 0;    # show earliest - reverse order
my $per_day = 0;    # show as DAY groups

# program variables
my $in_folder = '';
my @g_all_files = ();

my $last_time = 0;
my $last_file = '';

my $earliest_time = time();
my $earliest_file = '';

my $tail_count = 0;
my $repo_cnt = 0;
my $repo_last = '';
my $repo_time = 0;
my $oldest_repo = '';
my $oldest_time = time();
my @warnings = ();
my $verbosity = 0;
my $got_big_X = 0;

sub VERB1() { return ($verbosity >= 1); }
sub VERB2() { return ($verbosity >= 2); }
sub VERB5() { return ($verbosity >= 5); }
sub VERB9() { return ($verbosity >= 9); }

# debug
my $dbg_01 = 0;

sub process_directory($);

sub show_warnings($) {
    my ($val) = @_;
    if (@warnings) {
        prt( "\nGot ".scalar @warnings." WARNINGS...\n" );
        foreach my $itm (@warnings) {
           prt("$itm\n");
        }
        prt("\n");
    } else {
       ### prt( "\nNo warnings issued.\n\n" );
    }
}

sub pgm_exit($$) {
    my ($val,$msg) = @_;
    if (length($msg)) {
        $msg .= "\n" if (!($msg =~ /\n$/));
        prt($msg);
    }
    show_warnings($val);
    close_log($outfile,$load_log);
    exit($val);
}

sub prtw($) {
    my ($msg) = shift;
    prt($msg);
    $msg =~ s/\n$//;
    push(@warnings,$msg);
}

sub get_YYYYMMDD_hhmmss_UTC($) {
    my ($t) = shift;
    my $tm = gmtime($t);
    my $m = sprintf( "%04d/%02d/%02d %02d:%02d:%02d",
        $tm->year() + 1900, $tm->mon() + 1, $tm->mday(),
        $tm->hour(), $tm->min(), $tm->sec() );
    return $m;
}
#
#sub get_YYYYMMDD($) {
#    my ($t) = shift;
#    my @f = (localtime($t))[0..5];
#    my $m = sprintf("%04d/%02d/%02d",
#        $f[5] + 1900, $f[4] + 1, $f[3]);
#    return $m;
#}

sub mycmp2 { # by 2nd component - time in this case
    return 1 if (${$a}[1] > ${$b}[1]);
    return -1 if (${$a}[1] < ${$b}[1]);
    return 0;
}

sub mycmp2_invert { # by 2nd component - time in this case
    return -1 if (${$a}[1] > ${$b}[1]);
    return 1 if (${$a}[1] < ${$b}[1]);
    return 0;
}


sub has_repo_folder($) {
    my $f = shift;
    return 1 if ($f =~ /(\\|\/)CVS(\\|\/)/);
    return 2 if ($f =~ /(\\|\/)\.svn(\\|\/)/);
    return 3 if ($f =~ /(\\|\/)\.git(\\|\/)/);
    return 0;
}   

sub has_excluded_ext($) {
    my $file = shift;
    my ($n,$d,$e) = fileparse($file, qr/\.[^.]*/);
    my ($ext);
    foreach $ext (@excluded_exts) {
        return 1 if ($e eq $ext);
    }
    return 0;
}
sub is_excluded_file($) {
    my $file = shift;
    my ($n,$d) = fileparse($file);
    my ($fil);
    foreach $fil (@excluded_files) {
        return 1 if ($n eq $fil);
        return 1 if ($n =~ /^$fil$/i);
    }
    return 0;
}

sub is_temp_file($) {
    my $file = shift;
    my ($n,$d) = fileparse($file);
    return 0 if ($n =~ /^template/i);
    return 1 if ($n =~ /^temp/i);
    return 0;
}


sub process_directory($) {
    my ($dir) = @_;
    my @dirs = ();
    if ( opendir( DIR, $dir ) ) {
        my @files = readdir(DIR);
        closedir(DIR);
        $dir .= '/' if !($dir =~ /(\\|\/)$/);
        my ($file,$ff,$sb,$ir);
        foreach $file (@files) {
            next if (($file eq '.')||($file eq '..'));
            $ff = $dir.$file;
            if (-f $ff) {
                next if (has_excluded_ext($file));
                next if (is_excluded_file($file));
                next if ($got_big_X && is_temp_file($file));
                if ($sb = stat($ff)) {
                    $ir = has_repo_folder($ff);
                    next if ($ir && $no_repo);
                    push(@g_all_files, [$ff, $sb->mtime, $sb->size, $ir, 0]);
                    if ($sb->mtime > $last_time) {
                        $last_time = $sb->mtime;
                        $last_file = $ff;
                    }
                    if ($sb->mtime < $earliest_time) {
                        $earliest_time = $sb->mtime;
                        $earliest_file = $ff;
                    }
                    if ($ir > 0) {
                        $repo_cnt++;
                        if ($sb->mtime > $repo_time) {
                            $repo_last = $ff;
                            $repo_time = $sb->mtime;
                        }
                        if ($sb->mtime < $oldest_time) {
                            $oldest_time = $sb->mtime;
                            $oldest_repo = $ff;
                        }
                    }
                } else {
                    prtw("WARNING: Unable to 'stat' [$ff]\n");
                }
            } elsif (-d $ff) {
                push(@dirs,$ff);
            }
        }
    }
    foreach $dir (@dirs) {
        process_directory($dir);
    }
}

#  ($tail_count > 0) && $per_day
sub show_on_day_basis($) {
    my $ra = shift; # \@arr
    my $cnt = scalar @{$ra};
    my ($i,$ff,$tm,$ctm,$val,$key,$num,$min,$len);
    my ($total,$j,$cnum,$num2);
    my %perday = ();
    for ($i = 0; $i < $cnt; $i++) {
        $ff = ${$ra}[$i][0];
        $tm = ${$ra}[$i][1];
        $ctm = get_YYYYMMDD($tm);
        if (!defined $perday{$ctm}) {
            $perday{$ctm} = [];
        }
        $val = $perday{$ctm};
        push(@{$val}, [ $ff, $tm ]);
    }
    my @arr = sort keys(%perday);
    $num = 0;
    $min = 0;
    $total = 0;
    foreach $key (@arr) {
        $num++;
        last if ($num > $tail_count);
        $val = $perday{$key};
        $cnt = scalar @{$val};
        $total += $cnt;
        $ff = ${$val}[0][0];
        $tm = ${$val}[0][1];
        $len = length($ff);
        $min = $len if ($len > $min);
        if (VERB9()) {
            #$num = scalar @{$val};
            for ($j = 0; $j < $cnt; $j++) {
                $ff = ${$val}[$j][0];
                $tm = ${$val}[$j][1];
                $len = length($ff);
                $min = $len if ($len > $min);
            }
        }
    }
    $num = scalar @arr;
    prt("List of $total files, spread over $num days...\n");
    if (VERB9()) {
        $num = 0;
        foreach $key (@arr) {
            last if ($num > $tail_count);
            $num++;
            $val = $perday{$key};
            $cnt = scalar @{$val};
            $num2 = 0;
            for ($j = 0; $j < $cnt; $j++) {
                $num2++;
                $ff = ${$val}[$j][0];
                $tm = ${$val}[$j][1];
                if ($j == 0) {
                    $ctm = get_YYYYMMDD($tm);
                    prt("Count of $cnt for day $ctm\n");
                }
                $ctm = get_YYYYMMDD_hhmmss_UTC($tm);
                # for display
                $cnum = sprintf("%4d",$num2);
                $ff .= ' ' while (length($ff) < $min);
                prt("$cnum: $ff $ctm\n");
            }
       }
        $num = scalar @arr;
        prt("\nRepeated list of $total files, spread over $num days... showing only first...\n");
    }
    $num = 0;
    foreach $key (@arr) {
        $num++;
        last if ($num > $tail_count);
        $val = $perday{$key};
        $cnt = scalar @{$val};
        $ff = ${$val}[0][0];
        $tm = ${$val}[0][1];
        # for display
        $ff .= ' ' while (length($ff) < $min);
        $cnt = ' '.$cnt while (length($cnt) < 6);
        $cnum = sprintf("%4d",$num);
        prt("$num: $ff ".get_YYYYMMDD_hhmmss_UTC($tm)." $cnt\n");
   }
}

sub show_last() {
    my @arr = ();
    if ($invert_date) {
        @arr = sort mycmp2_invert @g_all_files;
    } else {
        @arr = sort mycmp2 @g_all_files;
    }
    my $cnt = scalar @arr;
    my ($i,$ff,$tm,$num,$min,$len,$msg,$cnum,$ok);
    $min = 0;
    $ok = 0;
    if ($invert_date) {
        if ($cnt && length($earliest_file) && ($earliest_time < time())) {
            $len = length($earliest_file);
            $min = $len if ($len > $min);
            $ok = 1;
        }
        if (length($oldest_repo) && ($oldest_time > 0) && ($oldest_repo ne $earliest_file) ) {
            $len = length($oldest_repo);
            $min = $len if ($len > $min);      
        }
    } else {
        if ($cnt && length($last_file) && ($last_time > 0)) {
            $len = length($last_file);
            $min = $len if ($len > $min);
            $ok = 1;
        }
        if (length($repo_last) && ($repo_time > 0) && ($repo_last ne $last_file) ) {
            $len = length($repo_last);
            $min = $len if ($len > $min);      
        }
    }
    if ($ok) {
        if ($invert_date) {
            $msg = $earliest_file;
            $msg .= ' ' while (length($msg) < $min);
            prt("Oldest   : $msg ".get_YYYYMMDD_hhmmss_UTC($earliest_time).", of $cnt files.\n");
            if ( length($oldest_repo) && ($oldest_time < time()) && ($oldest_repo ne $earliest_file) ) {
                $msg = $oldest_repo;
                $msg .= ' ' while (length($msg) < $min);
                prt("Repo Old : $msg ".get_YYYYMMDD_hhmmss_UTC($oldest_time).", of $repo_cnt repo files.\n");
            }

        } else {
            $msg = $last_file;
            $msg .= ' ' while (length($msg) < $min);
            prt("Latest   : $msg ".get_YYYYMMDD_hhmmss_UTC($last_time).", of $cnt files.\n");
            if ( length($repo_last) && ($repo_time > 0) && ($repo_last ne $last_file) ) {
                $msg = $repo_last;
                $msg .= ' ' while (length($msg) < $min);
                prt("Repo Last: $msg ".get_YYYYMMDD_hhmmss_UTC($repo_time).", of $repo_cnt repo files.\n");
            }
        }
        if ($tail_count > 0) {
            if ($per_day) {
                show_on_day_basis(\@arr);
            } else {
                $num = $cnt;
                $min = 0;
                for ($i = 0; $i < $cnt; $i++) {
                    if ($num <= $tail_count) {
                        $ff = $arr[$i][0];
                        $len = length($ff);
                        $min = $len if ($len > $min);
                    }
                    $num--;
                }
                $num = $cnt;
                for ($i = 0; $i < $cnt; $i++) {
                    if ($num <= $tail_count) {
                        $ff = $arr[$i][0];
                        $tm = $arr[$i][1];
                        $ff .= ' ' while (length($ff) < $min);
                        $cnum = sprintf("%4d",$num);
                        prt("$cnum: $ff ".get_YYYYMMDD_hhmmss_UTC($tm)."\n");
                    }
                    $num--;
                }
            }
        }
        pgm_exit(0,"");
    } else {
        prt("No files found in [$in_folder]!\n");
    }
}


# ### MAIN ###
parse_args(@ARGV);
prt("Processing in folder [$in_folder]\n");
process_directory($in_folder);
show_last();
pgm_exit(0,"");
# ### end ###

sub give_help() {
    prt("$0 $my_version, in OS $os\n");
    prt("Usage: [options] input-directory\n");
    prt("Options:\n");
    prt(" --help   (-h or -?) = This help, and exit 0\n");
    prt(" --invert       (-i) = Invert sort and show OLDEST file.\n");
    prt(" --load         (-l) = Load log at end.\n");
    prt(" --tail <cnt>   (-t) = Show <cnt> of the latest files.\n");
    prt(" --xclude <ext> (-x) = Exclude files with this extension. Can be a ':' sep list.\n");
    if ($os =~ /MSWin/i) {
        prt(" A special -X will exclude most files built.\n");
    }
    prt(" --day          (-d) = Show tail using the 'day' as the criteria.\n");
    prt(" --norepo       (-n) = Exclude ALL repo folders. (.git/.svn/CVS)\n");
    prt("The default is to ONLY show the latest file found.\n");
    prt("And the latest in any 'repo' folder found.\n");
}
sub need_arg {
    my ($arg,@av) = @_;
    if (!@av) {
        prt("ERROR: Argument [$arg] must be followed by another argument!\n");
        pmg_exit(1,"");
    }
}
sub parse_args {
    my @av = @_;
    my $cnt = scalar @av;
    prt("Parsing $cnt aruments...\n") if ($dbg_01);
    my ($sarg,@arr);
    my $cmd = '';
    while (@av) {
        my $arg = $av[0];
        $cmd .= "$arg ";
        if ($arg =~ /^-/) {
            $sarg = substr($arg,1);
            $sarg = substr($sarg,1) while ($sarg =~ /^-/);
            if (($sarg eq '?')||($sarg =~ /^h/i)) {
                give_help();
                exit(0);
            } elsif ($sarg =~ /^i/) {
                $invert_date = 1;
                prt("Invert date - show oldest last.\n");
            } elsif ($sarg =~ /^d/) {
                $per_day = 1;
                prt("Set the show tail on a per day basis.\n");
            } elsif ($sarg =~ /^l/) {
                $load_log = 1;
                prt("Set to load log at end.\n");
            } elsif ($sarg =~ /^t/) {
                need_arg(@av);
                shift @av;
                $sarg = $av[0];
                $cmd .= "$sarg ";
                if ($sarg =~ /^\d+$/) {
                    $tail_count = $sarg;
                    prt("set tail count to [$tail_count]\n"); # if ($dbg_01);
                } else {
                    prt("ERROR: Argument [$arg] MUST be followed by a numeric count!\n");
                    exit(1);
                }            
            } elsif ($sarg =~ /^v/) {
                if ($sarg =~ /^v(\d+)$/) {
                    $verbosity = $1;
                } else {
                    while ($sarg =~ /^v/) {
                        $verbosity++;
                        $sarg = substr($sarg,1);
                    }
                }
                prt("Set verbosity to [$verbosity]\n");
            } elsif ($sarg =~ /^x/) {
                need_arg(@av);
                shift @av;
                $sarg = $av[0];
                $cmd .= "$sarg ";
                @arr = split(':',$sarg);
                foreach $sarg (@arr) {
                    $sarg = '.'.$sarg if ( !($sarg =~ /^\./) );
                    push(@excluded_exts,$sarg);
                    prt("Excluding extension [$sarg].\n");
                }
            } elsif ($sarg =~ /^X/) {
                $got_big_X = 1;
                prt("$arg excludes [");
                foreach $sarg (@def_msvc_excludes) {
                    push(@excluded_exts,$sarg);
                    prt("$sarg");
                }
                prt("]\n");
                prt("$arg and files [");
                foreach $sarg (@def_msvc_files) {
                    push(@excluded_files,$sarg);
                    prt("$sarg");
                }
                prt("]\n");
            } elsif ($sarg =~ /^n/) {
                $no_repo = 1;
                prt("Exclude all repo folders. (.git/.svn/CVS)\n");
            } else {
                prt("ERROR: Unknown argument [$arg]! Aborting...\n");
                pgm_exit(1,"");
            }
        } else {
            prt("set input folder to [$arg]\n") if ($dbg_01);
            $in_folder = $arg;
        }
        shift @av;
    }
    if (length($in_folder) == 0) {
        prt("ERROR: No input folder found in command...[$cmd]!\n");
        pgm_exit(1,"");
    }
}

# eof
