#!perl

use CPAN::FindDependencies;
use File::Find::Rule::Age;
use LWP::Simple;
use Text::Diff;

my($command, @command_params, %finddeps_args);
if(@ARGV) {
    while(my $arg = shift(@ARGV)) {
        my $this_command;
        if($arg eq 'add') {
            die("You must provide an argument to '$arg'.\n\n"._help())
                unless(@ARGV);
            ($this_command, @command_params) = (\&_add, shift(@ARGV));
        } elsif($arg =~ /^(rm|remove|delete)$/) {
            die("You must provide an argument to '$arg'.\n\n"._help())
                unless(@ARGV);
            ($this_command, @command_params) = (\&_remove, shift(@ARGV));
        } elsif($arg eq 'report') {
            die("You must provide an argument to '$arg'.\n\n"._help())
                unless(@ARGV);
            ($this_command, @command_params) = (\&_report, shift(@ARGV));
        } elsif($arg eq 'mirror') {
            die("You must provide an argument to '$arg'.\n\n"._help())
                unless(@ARGV);
            $finddeps_args{mirrors} ||= [];
            push @{$finddeps_args{mirrors}}, shift(@ARGV);
        } elsif($arg eq 'perl') {
            die("You must provide an argument to '$arg'.\n\n"._help())
                unless(@ARGV);
            die("You can't provide two or moreversions of perl.\n\n"._help())
                if(exists($finddeps_args{perl}));
            $finddeps_args{perl} = shift(@ARGV);
        } elsif($arg eq 'list') {
            ($this_command, @command_params) = (\&_print_list);
        } elsif($arg eq 'help') {
            ($this_command, @command_params) = (\&_print_help);
        } else {
            die("'$arg' isn't a valid argument.\n\n"._help());
        }

        if($command && $this_command) {
            die("You can't provide two or more commands.\n\n"._help());
        } elsif($this_command) {
            $command = $this_command;
        }
    }
}
$finddeps_args{mirrors} ||= ['DEFAULT'];
($finddeps_args{perl}   ||= $^V) =~ s/^v//;

# create the cache if needed
chdir($ENV{CPANDEPS_DIFF_DIR} || $ENV{HOME});
mkdir('.cpandeps-diff');
mkdir(".cpandeps-diff/$finddeps_args{perl}");
mkdir(".cpandeps-diff/$finddeps_args{perl}/cache");
# clear out Ye Olde cache files. Max age is 23h so multiple runs within a
# day won't hammer servers, but a daily cron job will always be fresh
unlink(
    File::Find::Rule->file()->age(older => '23h')
        ->in(".cpandeps-diff/$finddeps_args{perl}/cache")
);

($command, @command_params) = (\&_report, _list())
    unless($command);
$command->(@command_params);

sub _get_deps {
    my $module = shift;
    return join("\n", sort { $a cmp $b } map { $_->distribution() } grep { $module ne $_->name() } CPAN::FindDependencies::finddeps(
        $module,
        nowarnings => 1,
        perl       => $finddeps_args{perl},
        cachedir   => ".cpandeps-diff/$finddeps_args{perl}/cache",
        map { (mirror => $_) } @{$finddeps_args{mirrors}}
    ));
}

sub _list {
    opendir(my $dir_fh, ".cpandeps-diff/$finddeps_args{perl}") ||
        die("Couldn't read $ENV{HOME}/.cpandeps-diff/$finddeps_args{perl}: $!\n");
    my @list = grep { -f ".cpandeps-diff/$finddeps_args{perl}/$_" } readdir($dir_fh);
    closedir($dir_fh);
    return sort { $a cmp $b } @list;
}

sub _report {
    foreach my $module (@_) {
        my $current_deps  = _get_deps($module);
        my $previous_deps = do {
            open(my $fh, '<', ".cpandeps-diff/$finddeps_args{perl}/$module") ||
                die("Couldn't read $ENV{HOME}/.cpandeps-diff/$finddeps_args{perl}/$module: $!\n");
            my $prev = join('', <$fh>);
            close($fh);
            $prev;
        };
        if($current_deps ne $previous_deps) {
            print "Differences found in dependencies for $module:\n";
            print diff(\$previous_deps, \$current_deps, { STYLE => 'Table' });
            open(my $fh, '>', ".cpandeps-diff/$finddeps_args{perl}/$module") ||
                die("Couldn't write $ENV{HOME}/.cpandeps-diff/$finddeps_args{perl}/$module: $!\n");
            print $fh $current_deps;
            close($fh);
        }
    }
}

sub _add {
    my $module = shift;
    return if(grep { $_ eq $module } _list());

    open(my $fh, '>', ".cpandeps-diff/$finddeps_args{perl}/$module") ||
        die("Couldn't write $ENV{HOME}/.cpandeps-diff/$finddeps_args{perl}/$module: $!\n");
    print $fh _get_deps($module);
    close($fh);
}

sub _remove {
    my $module = shift;
    die("'$module' isn't in the database.\n\n"._help()) unless(grep { $_ eq $module } _list());

    unlink(".cpandeps-diff/$finddeps_args{perl}/$module");
}

sub _print_list { print "$_\n" foreach (_list()); }
sub _print_help { print _help(); }
sub _help {
    return <<EOHELP
$0: generate reports when modules' dependencies get new releases

Usage: $0 [command \@args]

In the absence of any commands, generate a report about all known
modules. Otherwise ...

Commands:

  if no command is given it will report on all modules

    add \$module [\@args]
        Add the named module to the list of modules we care about

    remove \$module
    delete \$module
    rm     \$module
        Stop reporting on this module

    list
        Show which modules we're going to report on

    report \$module [\@args]
        Generate a report for just this one module

    help
        This!

Arguments:

    perl \$version
        Use this version of perl for figuring out what's in core

    mirror \$mirror
        Use this mirror (see CPAN::FindDependencies doco for details)

Files:

    Data is stored in a directory called .cpandeps-diff, which by default
    is under your home directory. If you want to put it somewhere else
    (you probably don't) then set CPANDEPS_DIFF_DIR in your environment.

EOHELP
}
