#!/usr/bin/perl

use strict;
use warnings;

use Digest::SHA;
use File::Basename;
use Getopt::Long;
use JSON::PP;
use Pod::Usage;

my $keep = 0;
my $man  = 0;
my $help = 0;
my @crates;

sub hash_file {
    my $file = shift;
    open( my $fh, '<', $file );
    my $state = Digest::SHA->new(256);
    $state->addfile($fh);
    my $digest = $state->hexdigest;
    close($fh);
    return $digest;
}

sub prune_simple {
    my $fh       = shift;
    my $json     = JSON::PP->new;
    my $manifest = $json->decode(<$fh>);
    %{ %{$manifest}{'files'} } = ();
    seek( $fh, 0, 0 );
    $json->pretty(0);
    print $fh $json->encode($manifest);
    my $curpos = tell($fh);
    truncate( $fh, $curpos );
}

sub prune_with_rehash {
    my $fh       = shift;
    my $dir      = shift;
    my $json     = JSON::PP->new;
    my $manifest = $json->decode(<$fh>);
    my $files    = \%{ $manifest->{'files'} };

    foreach my $file ( sort keys %{$files} ) {
        my $path = "$dir/$file";
        if ( !-e $path ) {
            delete $files->{$file};
            next;
        }
        my $hash = hash_file("$dir/$file");
        $files->{$file} = $hash;
    }

    seek( $fh, 0, 0 );
    $json->pretty(0);
    print $fh $json->encode($manifest);
    my $curpos = tell($fh);
    truncate( $fh, $curpos );
}

GetOptions( 'keep|k' => \$keep, 'help|?' => \$help, man => \$man )
  or pod2usage(2);
pod2usage(1)                              if $help;
pod2usage( -exitval => 0, -verbose => 2 ) if $man;
@crates = @ARGV;

if ( @ARGV < 1 ) {
    push( @crates, "." );
}

foreach my $crate (@crates) {
    my $checksum_file;
    my $dir;

    if ( -f $crate ) {
        $checksum_file = $crate;
        $dir           = dirname($crate);
    }
    else {
        $checksum_file = "$crate/.cargo-checksum.json";
        $dir           = $crate;
    }

    if ( !-f $checksum_file ) {
        warn "No checksum file found for $crate\n";
        next;
    }
    open( my $fh, '+<', $checksum_file );
    if ($keep) {
        prune_with_rehash( $fh, $dir );
    }
    else {
        prune_simple($fh);
    }
    close($fh);
}

__END__

=head1 NAME

cargo-prune-checksums

=head1 SYNOPSIS

cargo-prune-checksums [options] <crates ...>

 Options:
   -help            brief help message
   -man             full documentation
   -keep            keep checksums of files that still exist, and
                    re-hash them if needed
   crates...        crates to prune checksums for

=head1 OPTIONS

=over 4

=item B<-help>
Print a brief help message and exits.

=item B<-man>
Prints the manual page and exits.

=item B<-keep>
Keep checksums of files that still exist, and re-hash them if needed.

=back

=head1 DESCRIPTION

B<This program> will remove deleted files from .cargo-checksum.

=cut
