# mt-aws-glacier - Amazon Glacier sync client
# Copyright (C) 2012-2013  Victor Efimov
# http://mt-aws.com (also http://vs-dev.com) vs@vs-dev.com
# License: GPLv3
#
# This file is part of "mt-aws-glacier"
#
#    mt-aws-glacier is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    mt-aws-glacier is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.

package JournalTest;

use strict;
use warnings;
use utf8;
use FindBin;
use lib "$FindBin::RealBin/../lib";
use App::MtAws::Journal;
use File::Path;
use App::MtAws::TreeHash;
use Test::More;
use Encode;
use Carp;


sub new
{
	my ($class, %args) = @_;
	my $self = \%args;
	defined($self->{create_journal_version})||confess;
	$self->{filenames_encoding} ||= 'UTF-8';
	$self->{journal_encoding} ||= 'UTF-8';
	bless $self, $class;
	return $self;
}

sub test_all
{
	my ($self) = @_;
	$self->test_journal;
	$self->test_real_files;
	
	$self->test_all_files;
	$self->test_new_files;
	$self->test_existing_files;
}

sub test_journal
{
	my ($self) = @_;
	mkpath(encode($self->{filenames_encoding}, $self->{dataroot}, Encode::DIE_ON_ERR|Encode::LEAVE_SRC));
	$self->create_journal();
	
	my $j = App::MtAws::Journal->new(journal_encoding => $self->{journal_encoding},
		journal_file => $self->{journal_file}, root_dir => $self->{dataroot}, follow => $self->{follow});
	$j->read_journal(should_exist => 1);
	
	my @checkfiles = grep { $_->{type} eq 'normalfile' && $_->{journal} && $_->{journal} eq 'created' } @{$self->{testfiles}};
	ok(( scalar @checkfiles == scalar keys %{ $j->{journal_h} } ), "journal - number of planed and real files match");
	
	for my $cf (@checkfiles) {
		ok (my $jf = $j->{journal_h}->{$cf->{filename}}, "file $cf->{filename} exists in Journal");
		ok ($jf->{size} == $cf->{filesize}, "file size match $jf->{size} == $cf->{filesize}");
		ok ($jf->{treehash} eq $cf->{final_hash}, "treehash matches");
		ok ($jf->{archive_id} eq $cf->{archive_id}, "archive id matches");
		is ($j->absfilename($cf->{filename}), File::Spec->rel2abs($cf->{filename}, $self->{dataroot}), "absfilename match"); # actually better test in real
	}
	
	my $tmproot_e = encode($self->{filenames_encoding}, $self->{tmproot}, Encode::DIE_ON_ERR|Encode::LEAVE_SRC);
	rmtree($tmproot_e)
		if ($tmproot_e) && (-d $tmproot_e);
}

sub test_real_files
{
	my ($self) = @_;
	mkpath(encode($self->{filenames_encoding}, $self->{dataroot}, Encode::DIE_ON_ERR|Encode::LEAVE_SRC));
	$self->create_files();
	
	my $j = App::MtAws::Journal->new(journal_encoding => $self->{journal_encoding},
		journal_file => $self->{journal_file}, root_dir => $self->{dataroot}, filter => $self->{filter}, follow => $self->{follow});
	$j->read_files({new=>1,existing=>1});
	
	my @checkfiles = grep { $_->{type} ne 'dir' && !$_->{exclude} } @{$self->{testfiles}};
	ok((scalar @checkfiles) == scalar @{$j->{listing}{new}}+scalar @{$j->{listing}{existing}}, "number of planed and real files match");
	
	my %testfile_h = map { $_->{filename } => $_} @checkfiles;
	for my $realfile (@{$j->{listing}{new}}, @{$j->{listing}{existing}}) {
		ok ( $testfile_h{ $realfile->{relfilename} }, "found file $realfile->{relfilename} exists in planned test file list" );
	}
	my $tmproot_e = encode($self->{filenames_encoding}, $self->{tmproot}, Encode::DIE_ON_ERR|Encode::LEAVE_SRC);
	rmtree($tmproot_e)
		if ($tmproot_e) && (-d $tmproot_e);
}

sub test_all_files
{
	my ($self) = @_;
	mkpath(encode($self->{filenames_encoding}, $self->{dataroot}, Encode::DIE_ON_ERR|Encode::LEAVE_SRC));
	$self->create_journal();
	$self->create_files('skip');
	my $j = App::MtAws::Journal->new(journal_encoding => $self->{journal_encoding},
		journal_file => $self->{journal_file}, root_dir => $self->{dataroot}, filter => $self->{filter}, follow => $self->{follow});
	$j->read_files({new=>1,existing=>1});
	
	my @checkfiles = grep { $_->{type} ne 'dir' && !$_->{skip} && !$_->{exclude} } @{$self->{testfiles}};
	ok((scalar @checkfiles) == scalar @{$j->{listing}{new}}+scalar @{$j->{listing}{existing}}, "number of planed and real files match");
	
	my %testfile_h = map { $_->{filename } => $_} @checkfiles;
	for my $realfile (@{$j->{listing}{new}}, @{$j->{listing}{existing}}) {
		ok ( $testfile_h{ $realfile->{relfilename} }, "found file $realfile->{relfilename} exists in planned test file list" );
	}
	my $tmproot_e = encode($self->{filenames_encoding}, $self->{tmproot}, Encode::DIE_ON_ERR|Encode::LEAVE_SRC);
	rmtree($tmproot_e)
		if ($tmproot_e) && (-d $tmproot_e);
}


sub test_new_files
{
	my ($self) = @_;
	mkpath(encode($self->{filenames_encoding}, $self->{dataroot}, Encode::DIE_ON_ERR|Encode::LEAVE_SRC));
	$self->create_journal();
	$self->create_files('skip');
	my $j = App::MtAws::Journal->new(journal_encoding => $self->{journal_encoding},
		journal_file => $self->{journal_file}, root_dir => $self->{dataroot}, filter => $self->{filter}, follow => $self->{follow});#
	$j->read_journal(should_exist => 1);
	$j->read_files({new=>1});
	my @checkfiles = grep { $_->{type} ne 'dir' && !$_->{skip} && !$_->{exclude} && (!$_->{journal} || $_->{journal} ne 'created' ) } @{$self->{testfiles}};

	ok((scalar @checkfiles) == scalar @{$j->{listing}{new}}, "number of planned and real files match");

	my %testfile_h = map { $_->{filename } => $_} @checkfiles;
	for my $realfile (@{$j->{listing}{new}}) {
		ok ( $testfile_h{ $realfile->{relfilename} }, "found file $realfile->{relfilename} exists in planned test file list" );
	}
	my $tmproot_e = encode($self->{filenames_encoding}, $self->{tmproot}, Encode::DIE_ON_ERR|Encode::LEAVE_SRC);
	rmtree($tmproot_e)
		if ($tmproot_e) && (-d $tmproot_e);
}

sub test_existing_files
{
	my ($self) = @_;
	mkpath(encode($self->{filenames_encoding}, $self->{dataroot}, Encode::DIE_ON_ERR|Encode::LEAVE_SRC));
	$self->create_journal();
	$self->create_files('skip');
	my $j = App::MtAws::Journal->new(journal_encoding => $self->{journal_encoding},
		journal_file => $self->{journal_file}, root_dir => $self->{dataroot}, filter => $self->{filter}, follow => $self->{follow});
	$j->read_journal(should_exist => 1);
	$j->read_files({existing=>1});
	
	my @checkfiles = grep { $_->{type} ne 'dir' && !$_->{skip} && !$_->{exclude} && ($_->{journal} && $_->{journal} eq 'created')} @{$self->{testfiles}};
	ok((scalar @checkfiles) == scalar @{$j->{listing}{existing}}, "number of planed and real files match");
	
	my %testfile_h = map { $_->{filename } => $_} @checkfiles;
	for my $realfile (@{$j->{listing}{existing}}) {
		ok ( $testfile_h{ $realfile->{relfilename} }, "found file $realfile->{relfilename} exists in planned test file list" );
	}
	my $tmproot_e = encode($self->{filenames_encoding}, $self->{tmproot}, Encode::DIE_ON_ERR|Encode::LEAVE_SRC);
	rmtree($tmproot_e)
		if ($tmproot_e) && (-d $tmproot_e);
}

sub create_files
{
	my ($self, $mode) = @_;
	for my $testfile (@{$self->{testfiles}}) {
		$testfile->{fullname} = "$self->{dataroot}/$testfile->{filename}";
		if ($testfile->{type} eq 'dir') {
			mkpath(encode($self->{filenames_encoding}, $testfile->{fullname}, Encode::DIE_ON_ERR|Encode::LEAVE_SRC));
		} elsif (($testfile->{type} eq 'normalfile')  && ( #&& !$testfile->{exclude}
			(!defined($mode)) ||
			( ($mode eq 'skip') && !$testfile->{skip} )
			))
		{
			open (F, ">:encoding(UTF-8)", encode($self->{filenames_encoding}, $testfile->{fullname}, Encode::DIE_ON_ERR|Encode::LEAVE_SRC));
			print F $testfile->{content};
			close F;
		}
	}
}


sub create_journal
{
	my ($self, $mode) = @_;
	if ($self->{create_journal_version} =~ /^[ABC]$/) {
		$self->create_journal_vABC($self->{create_journal_version}, $mode);
	} elsif ($self->{create_journal_version} eq '0') {
		$self->create_journal_v0($mode);
	} else {
		confess;
	}
}

# creating journal for v0.7beta
sub create_journal_v0
{
	my ($self, $mode) = @_;
	open (F, ">:encoding($self->{journal_encoding})", encode($self->{filenames_encoding}, $self->{journal_file}, Encode::DIE_ON_ERR|Encode::LEAVE_SRC)) || confess;
	my $t = time() - (scalar @{$self->{testfiles}})*2;
	for my $testfile (@{$self->{testfiles}}) {
		if (($testfile->{type} eq 'normalfile') && $testfile->{journal} && ($testfile->{journal} eq 'created')) {
			$testfile->{archive_id} = get_random_archive_id($t);
			$testfile->{filesize} = length($testfile->{content});
			$testfile->{final_hash} = scalar_treehash(encode_utf8($testfile->{content}));
			print F $t." CREATED $testfile->{archive_id} $testfile->{filesize} $testfile->{final_hash} $testfile->{filename}\n";
		} elsif (($testfile->{type} eq 'normalfile') && $testfile->{journal} && ($testfile->{journal} eq 'created_and_deleted')) {
			$testfile->{archive_id} = get_random_archive_id($t);
			$testfile->{filesize} = length($testfile->{content});
			$testfile->{final_hash} = scalar_treehash(encode_utf8($testfile->{content}));
			print F $t." CREATED $testfile->{archive_id} $testfile->{filesize} $testfile->{final_hash} $testfile->{filename}\n";
			print F ($t+1)." DELETED $testfile->{archive_id} $testfile->{filename}\n";
		}
		$t++;
	}
	close F;
}

# creating journal v 'A'
sub create_journal_vABC
{
	my ($self, $version, $mode) = @_;
	open (F, ">:encoding($self->{journal_encoding})", encode($self->{filenames_encoding}, $self->{journal_file}, Encode::DIE_ON_ERR|Encode::LEAVE_SRC));
	my $t = time() - (scalar @{$self->{testfiles}})*2;
	my $ft = $t - 1000;
	my $dt = $t + 1;
	for my $testfile (@{$self->{testfiles}}) {
		$ft = $testfile->{mtime} if defined $testfile->{mtime};
		if (($testfile->{type} eq 'normalfile') && $testfile->{journal} && ($testfile->{journal} eq 'created')) {
			$testfile->{archive_id} = get_random_archive_id($t);
			$testfile->{filesize} = length($testfile->{content});
			$testfile->{final_hash} = scalar_treehash(encode_utf8($testfile->{content}));
			print F "$version\t$t\tCREATED\t$testfile->{archive_id}\t$testfile->{filesize}\t$ft\t$testfile->{final_hash}\t$testfile->{filename}\n";
		} elsif (($testfile->{type} eq 'normalfile') && $testfile->{journal} && ($testfile->{journal} eq 'created_and_deleted')) {
			$testfile->{archive_id} = get_random_archive_id($t);
			$testfile->{filesize} = length($testfile->{content});
			$testfile->{final_hash} = scalar_treehash(encode_utf8($testfile->{content}));
			print F "$version\t$t\tCREATED\t$testfile->{archive_id}\t$testfile->{filesize}\t$ft\t$testfile->{final_hash}\t$testfile->{filename}\n";
			print F "$version\t$dt\tDELETED\t$testfile->{archive_id}\t$testfile->{filename}\n";
		}
		$t++;
	}
	close F;
}

sub get_random_archive_id
{
	my ($i) = @_;
	my $th = App::MtAws::TreeHash->new();
	my $s = $$.time().$i;
	$th->eat_data(\$s);
	$th->calc_tree();
	$th->get_final_hash();
}

sub scalar_treehash
{
	my ($str) = @_;
	confess if utf8::is_utf8($str);
	my $th = App::MtAws::TreeHash->new();
	$th->eat_data(\$str);
	$th->calc_tree();
	$th->get_final_hash();
}

1;
