#!perl
use strict;
use warnings;

use Test::More tests => 53;

use FindBin qw($Bin);
use File::Spec;
use IO::Handle; # error()
use Fcntl qw(SEEK_SET SEEK_CUR SEEK_END);

my $f = make_files();
my $r;

ok open($r, '<:reverse', $f->{small}{file}), 'open:reverse (small-sized file)';
ok scalar(grep { $_ eq 'reverse' } $r->get_layers), 'has :reverse';
ok !$r->error, 'not error()';
ok !eof($r), 'not eof()';

is_deeply [<$r>], $f->{small}{contents}, 'readline:reverse';
ok !$r->error, 'not error()';
ok eof($r), 'eof()';
is scalar(<$r>), undef, 'readline:reverse (after EOF)';
ok !$r->error, 'not error()';
ok eof($r), 'eof()';
cmp_ok fileno($r), '>', -1, 'fileno()';

ok open($r, '<:reverse', $f->{normal}{file}), 'open:reverse (moderate-sized file)';

is_deeply [<$r>], $f->{normal}{contents}, 'readline:reverse';


ok open($r, '<:reverse', $f->{longline}{file}), 'open:reverse (long-lined file)';
is_deeply [<$r>], $f->{longline}{contents}, 'readline:reverse';
ok close($r), 'close:reverse';

ok open($r, '<:reverse', $f->{nenl}{file}), 'open:reverse (file not ending newline)';
is_deeply [<$r>], $f->{nenl}{contents}, 'readline:reverse';
ok close($r), 'close:reverse';

ok open($r, '<:reverse', $f->{zerobyte}{file}), 'open:reverse (zero byte file)';
is_deeply [<$r>], $f->{zerobyte}{contents}, 'readline:reverse';
ok close($r), 'close:reverse';

ok open($r, '<:reverse', \"foo\nbar\nbaz\n"), 'open:scalar:reverse';
is_deeply [<$r>], ["baz\n", "bar\n", "foo\n"], 'readline:scalar:reverse';
ok close($r), 'close:scalar:reverse';
# Read again

ok open($r, '<', $f->{normal}{file}), 'open:perlio';
is scalar(<$r>), $f->{normal}{contents}[-1], 'normal readline';
#ok binmode $r, ':reverse';
binmode $r;
$r->push_layer('reverse');
is scalar(<$r>), $f->{normal}{contents}[-1], 'backward readline';
$r->pop_layer();
is scalar(<$r>), $f->{normal}{contents}[-1], 'normal readline again';

ok open($r, '<:reverse', $f->{normal}{file}), 'open:reverse';
my $s   = <$r>;

my $pos = tell $r;

is $pos, length($s), 'tell';

like $r->_dump, qr/RDBUF/, 'with reading buffer';
ok seek($r, 0, SEEK_SET), 'rewind';
unlike $r->_dump, qr/RDBUF/, 'without reading buffer';
is scalar(<$r>), $f->{normal}{contents}[0], 'readline after rewind';
seek $r, $pos, SEEK_SET;
is scalar(<$r>), $f->{normal}{contents}[1], 'SEEK_SET';

seek $r, 0, SEEK_END;
is scalar(<$r>), undef, 'SEEK_END';
$pos = tell $r;

seek $r, -length($f->{normal}{contents}[-1]), SEEK_CUR;

is scalar(<$r>), $f->{normal}{contents}[-1], 'SEEK_CUR';

is tell($r), $pos, 'tell';

seek $r, 0, SEEK_SET;
$r->push_layer('reverse');
is_deeply [<$r>], [ readline( PerlIO::Util->open('<', $f->{normal}{file}) ) ], ':reverse:reverse makes no sense :-)';

# Errors

open($r, '<:dir', '.') or die $!;
ok !binmode($r, ':reverse'), "unseekable->push_layer(reverse) failed (\$!=$!)";

SKIP:{
	my $v = sprintf '%vd', $^V;
	skip "perlio.c before perl 5.8.8 (this is $v) has some problems in binmode()", 1
		if $] < 5.008_008;
	1 while $r->pop_layer();
	ok !binmode($r, ':reverse'), ':reverse to invalid filehandle';
}
ok !binmode(STDOUT, ':reverse'), ':reverse to output filehandle';

#ok open($r, '-| :raw', $^X, '-e', '"print qq{foo\nbar\n}"'), 'open pipe';
#eval{
#	$r->push_layer('reverse');
#};
#ok $@, ':reverse to pipe -> fail';
#ok close($r), 'close pipe';

eval{
	no warnings 'layer';
	my $io = PerlIO::Util->open('<:crlf', $f->{normal}{file});
	$io->push_layer('reverse');
};
ok $@, 'with a no-raw layer';

my $file = File::Spec->catfile($Bin, 'util', 'foobar');
ok !open($r, '>:reverse', $file), 'open with write-mode';
ok !-e $file, "doesn't create the file";

ok !open($r, '<:reverse', $file), 'open with non-existing file';

ok !open($r, '+<:reverse', $f->{normal}{file}), 'open with read & write -mode';


sub make_files{
	my %f;
	use constant BUFSIZ => 4096;

	my $cts = [];
	my $f1 = File::Spec->catfile($Bin, 'util', 'revlongline');
	open my $o, '>', $f1 or die "Cannot open $f1 for writing: $!";
	binmode $o;
	foreach my $s('x' .. 'z'){
		my $c = $s x (BUFSIZ+100) . "\n";
		print $o $c;
		unshift @$cts, $c;
	}
	$f{longline}{file} = $f1;
	$f{longline}{contents} = $cts;

	$cts = [];
	my $f2 = File::Spec->catfile($Bin, 'util', 'revsmall');
	open $o, '>', $f2 or die "Cannot open $f2 for writing: $!";
	binmode $o;
	foreach my $s('x' .. 'z'){
		my $c = $s x (10) . "\n";
		print $o $c;
		unshift @$cts, $c;
	}
	$f{small}{file} = $f2;
	$f{small}{contents} = $cts;

	$cts = [];
	my $f3 = File::Spec->catfile($Bin, 'util', 'revnormal');
	open $o, '>', $f3 or die "Cannot open $3f for writing: $!";
	binmode $o;
	foreach my $s(1000 .. 1500){
		my $c = $s . "\n";
		print $o $c;
		unshift @$cts, $c;
	}
	$f{normal}{file} = $f3;
	$f{normal}{contents} = $cts;

	$cts = [];
	my $f4 = File::Spec->catfile($Bin, 'util', 'revnotendnewline');
	open $o, '>', $f4 or die "Cannot open $f4 for writing: $!";
	binmode $o;
	print $o "foo\nbar\nbaz";
	@$cts = ("bazbar\n", "foo\n");
	$f{nenl}{file} = $f4;
	$f{nenl}{contents} = $cts;

	$cts = [];
	my $f5 = File::Spec->catfile($Bin, 'util', 'revzerobyte');
	open $o, '>', $f5 or die "Cannot open $f5 for writing: $!";
	@$cts = ();
	$f{zerobyte}{file} = $f5;
	$f{zerobyte}{contents} = $cts;



	eval q{
		sub END{
			ok unlink($f1), '(cleanup)';
			ok unlink($f2), '(cleanup)';
			ok unlink($f3), '(cleanup)';
			ok unlink($f4), '(cleanup)';
			ok unlink($f5), '(cleanup)';
		}
	};

	return \%f;
}