#!/usr/bin/perl

# this software is licensed for use under the Free Software Foundation's GPL v3.0 license, as retrieved
# from http://www.gnu.org/licenses/gpl-3.0.html on 2014-11-17.  A copy should also be available in this
# project's Git repository at https://github.com/jimsalterjrs/sanoid/blob/master/LICENSE.


use strict;
use warnings;

my $zfs = '/sbin/zfs';
my %args = getargs(@ARGV);

my $progversion = '1.4.7';

if ($args{'version'}) { print "$progversion\n"; exit 0; }

my $dataset = getdataset($args{'path'});

my %versions = getversions($args{'path'}, $dataset);

foreach my $version (sort { $versions{$a}{'mtime'} <=> $versions{$b}{'mtime'} } keys %versions) {
	my $disptime = localtime($versions{$version}{'mtime'});
	my $dispsize = humansize($versions{$version}{'size'});

	print "$disptime\t$dispsize\t$version\n";
}

exit 0;

###################################################################
###################################################################
###################################################################

sub humansize {

	my ($rawsize) = @_;
	my $humansize;

	if ($rawsize > 1024*1024*1024) {
        	$humansize = sprintf("%.1f",$rawsize/1024/1024/1024) . ' GB';
	} elsif ($rawsize > 1024*1024) {
        	$humansize = sprintf("%.1f",$rawsize/1024/1024) . ' MB';
	} elsif ($rawsize > 255) {
        	$humansize = sprintf("%.1f",$rawsize/1024) . ' KB';
	} else {
		$humansize = $rawsize . ' Bytes';
	}

	return $humansize;
}

sub getversions {
	my ($path, $dataset) = @_;
	my @snaps = findsnaps($dataset, $args{'path'});

	my $snappath = '.zfs/snapshot';
	my $relpath = $path;
	$relpath =~ s/^$dataset\///;

	my %versions;

	foreach my $snap (@snaps) {
		my $filename = "$dataset/$snappath/$snap/$relpath";
		my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($filename);

		# only push to the $versions hash if this size and mtime aren't already present (simple dedupe)
		my $duplicate = 0;
		foreach my $version (keys %versions) {
			if ($versions{$version}{'size'} eq $size && $versions{$version}{'mtime'} eq $mtime) {
				$duplicate = 1;
			}
		}
		if (! $duplicate) {
			$versions{$filename}{'size'} = $size;
			$versions{$filename}{'mtime'} = $mtime;
		}
	}

	return %versions;
}

sub findsnaps {
	my ($dataset, $path) = @_;

	my $snappath = '.zfs/snapshot';

	my $relpath = $path;
	$relpath =~ s/^$dataset//;

	my @snaps;
	opendir (my $dh, "$dataset/$snappath");
	while (my $dir=(readdir $dh)) {
		if ($dir ne '.' && $dir ne '..') { push @snaps, $dir; }
	}
	closedir $dh;

	return @snaps;
}

sub getdataset {

	my ($path) = @_;

	open FH, "$zfs list -Ho mountpoint |";
	my @datasets = <FH>;
	close FH;

	my @matchingdatasets;
	foreach my $dataset (@datasets) {
		chomp $dataset;
		if ( $path =~ /^$dataset/ ) { push @matchingdatasets, $dataset; }
	}

	my $bestmatch = '';
	foreach my $dataset (@matchingdatasets) {
		if ( length $dataset > length $bestmatch ) { $bestmatch = $dataset; }
	}
	return $bestmatch;
}

sub getargs {
	my @args = @_;
	my %args;

	my %novaluearg;
	my %validarg;
	push my @validargs, ('debug','version');
	foreach my $item (@validargs) { $validarg{$item} = 1; }
	push my @novalueargs, ('debug','version');
	foreach my $item (@novalueargs) { $novaluearg{$item} = 1; }

	while (my $rawarg = shift(@args)) {
		my $arg = $rawarg;
		my $argvalue;
		if ($rawarg =~ /=/) {
			# user specified the value for a CLI argument with =
			# instead of with blank space. separate appropriately.
			$argvalue = $arg;
			$arg =~ s/=.*$//;
			$argvalue =~ s/^.*=//;
		}
		if ($rawarg =~ /^--/) {
			# doubledash arg
			$arg =~ s/^--//;
			if (! $validarg{$arg}) { die "ERROR: don't understand argument $rawarg.\n"; }
			if ($novaluearg{$arg}) {
				$args{$arg} = 1;
			} else {
				# if this CLI arg takes a user-specified value and
				# we don't already have it, then the user must have
				# specified with a space, so pull in the next value
				# from the array as this value rather than as the
				# next argument.
				if ($argvalue eq '') { $argvalue = shift(@args); }
				$args{$arg} = $argvalue;
			}
		} elsif ($arg =~ /^-/) {
			# singledash arg
			$arg =~ s/^-//;
			if (! $validarg{$arg}) { die "ERROR: don't understand argument $rawarg.\n"; }
			if ($novaluearg{$arg}) {
				$args{$arg} = 1;
			} else {
				# if this CLI arg takes a user-specified value and
				# we don't already have it, then the user must have
				# specified with a space, so pull in the next value
				# from the array as this value rather than as the
				# next argument.
				if ($argvalue eq '') { $argvalue = shift(@args); }
				$args{$arg} = $argvalue;
			}
		} else {
			# bare arg
			$args{'path'} = $arg;
		}
	}

	return %args;
}
