package File::Manifest;
use strict;
use Carp;
use IO::File;
use File::Recurse;
use vars qw($VERSION);

$VERSION = '1.01';

sub new {
    my $o = bless { manifest=>'MANIFEST', cksum => {}, mode=>0644 }, shift;

    if (@_ == 1 or @_ == 2) {
	$o->bind(@_);
    } elsif (@_ != 0) {
	croak "new File::Manifest([dir,dest])";
    }
    $o;
}

sub add_cksum {
    my ($o, $name, $box) = @_;
    croak "$o->add_cksum(name,obj)" if @_ != 3;
    $o->{cksum}{$name} = $box;
}

sub bind {
    my ($o, $dir, $dest) = @_;
    $o->{dir} = $dir;
    $o->{dest} = $dest || $dir;
}

sub read_tree {
    my ($o) = @_;

    # find some sort of checksum function
    my $ok=scalar(keys %{$o->{cksum}});
    eval {require MD5; $o->add_cksum('md5', new MD5); $ok=1; } if !$ok;
    eval {require Checksum::Any; $o->add_cksum('any',new Checksum::Any);$ok=1} if !$ok;
    warn "Basic checksum not found" if !$ok;

    chdir $o->{dir} or die "chdir $o->{dir}: $!";
    my @files;
    recurse(sub {
	my $f = substr(shift, 2);
	return 0 if (-d $f or $f eq $o->{manifest});
	my $z = {nm=>$f};
	while (my ($k,$box) = each %{$o->{cksum}}) {
	    # assume all cksums have the same interface as MD5...
	    $box->reset();
	    if (-l $f) { 
		# do something reasonable with symlinks
		$box->add(readlink($f));
	    } else {
		my $fh = new IO::File;
		if (!$fh->open($f)) {
		    warn "open $f: $!";
		} else {
		    $box->addfile($fh);
		}
	    }
	    $z->{$k} = $box->hexdigest();
	}
	push(@files, $z);
	0;
    }, '.');
    @files = sort { $a->{nm} cmp $b->{nm} } @files;
    \@files;
}
    
sub write {
    my $o = shift;
    # bind @_?

    my $files = $o->read_tree;

    chdir $o->{dest} or die "chdir $o->{dest}: $!";
    my $fh = new IO::File;
    $fh->open($o->{manifest}, O_WRONLY|O_CREAT|O_TRUNC, $o->{mode})
	or die "open $o->{manifest}: $!";
    for my $f (@$files) { print $fh join($;, %$f)."\n"; }
}

sub diff {
    my $self = shift;

    my $orig;
    {
	chdir $self->{dest} or die "chdir $self->{dest}: $!";
	my $fh = new IO::File;
	$fh->open($self->{manifest}) or die "open $self->{manifest}: $!";
	while (defined (my $l = <$fh>)) {
	    chomp $l;
	    my %z = split($;, $l);
	    $orig->{$z{nm}} = \%z;
	}
    };

    my $r = { '=' => [], '+' => [], '-' => [], '!' => [] };
    my $new = $self->read_tree;
    for my $f (@$new) {
	my $o = $orig->{$f->{nm}};
	delete $orig->{$f->{nm}};  #check once only
	if ($o) {
	    my $eq=1;
	    for my $k (keys %$f) {
		do {$eq=0; last} if (exists $o->{$k} and $o->{$k} ne $f->{$k});
	    }
	    push(@{$r->{$eq? '=':'!'}}, $f);
	} else {
	    push(@{$r->{'+'}}, $f);
	}
    }
    for my $o (values %$orig) { push(@{$r->{'-'}}, $o) }

    $r;
}

1;
