use warnings;
use strict;

use File::Temp 0.22 qw(tempdir);
use Test::More tests => 375;

BEGIN { use_ok "Hash::SharedMem::Handle"; }
BEGIN { use_ok "Hash::SharedMem", qw(is_shash); }

my $tmpdir = tempdir(CLEANUP => 1);
my $sh = Hash::SharedMem::Handle->open("$tmpdir/t0", "rwc");
ok $sh;
ok is_shash($sh);
is scalar($sh->is_snapshot), !!0;
is_deeply [$sh->is_snapshot], [!!0];
is scalar($sh->is_readable), !!1;
is_deeply [$sh->is_readable], [!!1];
is scalar($sh->is_writable), !!1;
is_deeply [$sh->is_writable], [!!1];
is scalar($sh->mode), "rw";
is_deeply [$sh->mode], ["rw"];
eval { ${\($sh->mode)} = undef; };
like $@, qr/\AModification of a read-only value attempted /;

is $sh->can("getd"), $sh->can("exists");

is scalar($sh->exists("a100")), !!0;
is_deeply [$sh->exists("a100")], [!!0];
is scalar($sh->getd("a100")), !!0;
is_deeply [$sh->getd("a100")], [!!0];
is scalar($sh->length("a100")), undef;
is_deeply [$sh->length("a100")], [undef];
is scalar($sh->get("a100")), undef;
is_deeply [$sh->get("a100")], [undef];
is scalar($sh->occupied), !!0;
is_deeply [$sh->occupied], [!!0];
is scalar($sh->count), 0;
is_deeply [$sh->count], [0];
eval { ${\($sh->count)} = undef; };
like $@, qr/\AModification of a read-only value attempted /;
like scalar($sh->size), qr/\A[0-9]+\z/;
like join(",", $sh->size), qr/\A[0-9]+\z/;
eval { ${\($sh->size)} = undef; };
like $@, qr/\AModification of a read-only value attempted /;
is scalar($sh->key_min), undef;
is_deeply [$sh->key_min], [undef];
is scalar($sh->key_max), undef;
is_deeply [$sh->key_max], [undef];
is scalar($sh->key_ge("a110")), undef;
is_deeply [$sh->key_ge("a110")], [undef];
is scalar($sh->key_gt("a110")), undef;
is_deeply [$sh->key_gt("a110")], [undef];
is scalar($sh->key_le("a110")), undef;
is_deeply [$sh->key_le("a110")], [undef];
is scalar($sh->key_lt("a110")), undef;
is_deeply [$sh->key_lt("a110")], [undef];
is_deeply scalar($sh->keys_array), [];
is_deeply [$sh->keys_array], [[]];
eval { ${\($sh->keys_array)} = undef; };
like $@, qr/\AModification of a read-only value attempted /;
is_deeply scalar($sh->keys_hash), {};
is_deeply [$sh->keys_hash], [{}];
eval { ${\($sh->keys_hash)} = undef; };
like $@, qr/\AModification of a read-only value attempted /;
is_deeply scalar($sh->group_get_hash), {};
is_deeply [$sh->group_get_hash], [{}];
eval { ${\($sh->group_get_hash)} = undef; };
like $@, qr/\AModification of a read-only value attempted /;

$sh->set("a110", "b110");
is scalar($sh->set("a100", "b100")), undef;
is_deeply [$sh->set("a120", "b120")], [];

is scalar($sh->exists("a100")), !!1;
is_deeply [$sh->exists("a100")], [!!1];
is scalar($sh->getd("a100")), !!1;
is_deeply [$sh->getd("a100")], [!!1];
is scalar($sh->length("a100")), 4;
is_deeply [$sh->length("a100")], [4];
eval { ${\($sh->length("a100"))} = undef; };
like $@, qr/\AModification of a read-only value attempted /;
is scalar($sh->get("a100")), "b100";
is_deeply [$sh->get("a100")], ["b100"];
is scalar($sh->occupied), !!1;
is_deeply [$sh->occupied], [!!1];
is scalar($sh->count), 3;
is_deeply [$sh->count], [3];
eval { ${\($sh->count)} = undef; };
like $@, qr/\AModification of a read-only value attempted /;
like scalar($sh->size), qr/\A[0-9]+\z/;
like join(",", $sh->size), qr/\A[0-9]+\z/;
eval { ${\($sh->size)} = undef; };
like $@, qr/\AModification of a read-only value attempted /;
is scalar($sh->key_min), "a100";
is_deeply [$sh->key_min], ["a100"];
is scalar($sh->key_max), "a120";
is_deeply [$sh->key_max], ["a120"];
is scalar($sh->key_ge("a110")), "a110";
is_deeply [$sh->key_ge("a110")], ["a110"];
is scalar($sh->key_gt("a110")), "a120";
is_deeply [$sh->key_gt("a110")], ["a120"];
is scalar($sh->key_le("a110")), "a110";
is_deeply [$sh->key_le("a110")], ["a110"];
is scalar($sh->key_lt("a110")), "a100";
is_deeply [$sh->key_lt("a110")], ["a100"];
is_deeply scalar($sh->keys_array), [qw(a100 a110 a120)];
is_deeply [$sh->keys_array], [[qw(a100 a110 a120)]];
eval { ${\($sh->keys_array)} = undef; };
like $@, qr/\AModification of a read-only value attempted /;
is_deeply scalar($sh->keys_hash), { a100=>undef, a110=>undef, a120=>undef };
is_deeply [$sh->keys_hash], [{ a100=>undef, a110=>undef, a120=>undef }];
eval { ${\($sh->keys_hash)} = undef; };
like $@, qr/\AModification of a read-only value attempted /;
is_deeply scalar($sh->group_get_hash),
	{ a100=>"b100", a110=>"b110", a120=>"b120" };
is_deeply [$sh->group_get_hash], [{ a100=>"b100", a110=>"b110", a120=>"b120" }];
eval { ${\($sh->group_get_hash)} = undef; };
like $@, qr/\AModification of a read-only value attempted /;

is scalar($sh->exists("a000")), !!0;
is scalar($sh->length("a000")), undef;
is scalar($sh->get("a000")), undef;
is scalar($sh->exists("a105")), !!0;
is scalar($sh->length("a105")), undef;
is scalar($sh->get("a105")), undef;
is scalar($sh->exists("a110")), !!1;
is scalar($sh->length("a110")), 4;
is scalar($sh->get("a110")), "b110";
is scalar($sh->exists("a115")), !!0;
is scalar($sh->length("a115")), undef;
is scalar($sh->get("a115")), undef;
is scalar($sh->exists("a120")), !!1;
is scalar($sh->length("a120")), 4;
is scalar($sh->get("a120")), "b120";
is scalar($sh->exists("a130")), !!0;
is scalar($sh->length("a130")), undef;
is scalar($sh->get("a130")), undef;

my $sn = $sh->snapshot;
ok is_shash($sn);
is scalar($sn->is_snapshot), !!1;
is_deeply [$sn->is_snapshot], [!!1];
is scalar($sn->is_readable), !!1;
is_deeply [$sn->is_readable], [!!1];
is scalar($sn->is_writable), !!0;
is_deeply [$sn->is_writable], [!!0];
is scalar($sn->mode), "r";
is_deeply [$sn->mode], ["r"];

is $sn->exists("a000"), !!0;
is $sn->length("a000"), undef;
is $sn->get("a000"), undef;
is $sn->exists("a100"), !!1;
is $sn->length("a100"), 4;
is $sn->get("a100"), "b100";
is $sn->exists("a105"), !!0;
is $sn->length("a105"), undef;
is $sn->get("a105"), undef;
is $sn->exists("a110"), !!1;
is $sn->length("a110"), 4;
is $sn->get("a110"), "b110";
is $sn->exists("a115"), !!0;
is $sn->length("a115"), undef;
is $sn->get("a115"), undef;
is $sn->exists("a120"), !!1;
is $sn->length("a120"), 4;
is $sn->get("a120"), "b120";
is $sn->exists("a130"), !!0;
is $sn->length("a130"), undef;
is $sn->get("a130"), undef;
is $sn->occupied, !!1;
is $sn->count, 3;
is $sn->key_min, "a100";
is $sn->key_max, "a120";
is $sn->key_ge("a110"), "a110";
is $sn->key_gt("a110"), "a120";
is $sn->key_le("a110"), "a110";
is $sn->key_lt("a110"), "a100";
is_deeply $sn->keys_array, [qw(a100 a110 a120)];
is_deeply $sn->keys_hash, { a100=>undef, a110=>undef, a120=>undef };
is_deeply $sn->group_get_hash, { a100=>"b100", a110=>"b110", a120=>"b120" };

$sh->set("a105", "b105");
$sh->set("a110", undef);

is $sh->exists("a000"), !!0;
is $sh->length("a000"), undef;
is $sh->get("a000"), undef;
is $sh->exists("a100"), !!1;
is $sh->length("a100"), 4;
is $sh->get("a100"), "b100";
is $sh->exists("a105"), !!1;
is $sh->length("a105"), 4;
is $sh->get("a105"), "b105";
is $sh->exists("a110"), !!0;
is $sh->length("a110"), undef;
is $sh->get("a110"), undef;
is $sh->exists("a115"), !!0;
is $sh->length("a115"), undef;
is $sh->get("a115"), undef;
is $sh->exists("a120"), !!1;
is $sh->length("a120"), 4;
is $sh->get("a120"), "b120";
is $sh->exists("a130"), !!0;
is $sh->length("a130"), undef;
is $sh->get("a130"), undef;
is $sh->occupied, !!1;
is $sh->count, 3;
is $sh->key_min, "a100";
is $sh->key_max, "a120";
is $sh->key_ge("a110"), "a120";
is $sh->key_gt("a110"), "a120";
is $sh->key_le("a110"), "a105";
is $sh->key_lt("a110"), "a105";
is_deeply $sh->keys_array, [qw(a100 a105 a120)];
is_deeply $sh->keys_hash, { a100=>undef, a105=>undef, a120=>undef };
is_deeply $sh->group_get_hash, { a100=>"b100", a105=>"b105", a120=>"b120" };

is $sn->exists("a000"), !!0;
is $sn->length("a000"), undef;
is $sn->get("a000"), undef;
is $sn->exists("a100"), !!1;
is $sn->length("a100"), 4;
is $sn->get("a100"), "b100";
is $sn->exists("a105"), !!0;
is $sn->length("a105"), undef;
is $sn->get("a105"), undef;
is $sn->exists("a110"), !!1;
is $sn->length("a110"), 4;
is $sn->get("a110"), "b110";
is $sn->exists("a115"), !!0;
is $sn->length("a115"), undef;
is $sn->get("a115"), undef;
is $sn->exists("a120"), !!1;
is $sn->length("a120"), 4;
is $sn->get("a120"), "b120";
is $sn->exists("a130"), !!0;
is $sn->length("a130"), undef;
is $sn->get("a130"), undef;
is $sn->occupied, !!1;
is $sn->count, 3;
is $sn->key_min, "a100";
is $sn->key_max, "a120";
is $sn->key_ge("a110"), "a110";
is $sn->key_gt("a110"), "a120";
is $sn->key_le("a110"), "a110";
is $sn->key_lt("a110"), "a100";
is_deeply $sn->keys_array, [qw(a100 a110 a120)];
is_deeply $sn->keys_hash, { a100=>undef, a110=>undef, a120=>undef };
is_deeply $sn->group_get_hash, { a100=>"b100", a110=>"b110", a120=>"b120" };

eval { $sn->set("a115", "b115") };
like $@, qr#\Acan't\ write\ shared\ hash\ \Q$tmpdir\E/t0:
		\ shared\ hash\ handle\ is\ a\ snapshot\ #x;
is $sh->exists("a115"), !!0;
is $sh->length("a115"), undef;
is $sh->get("a115"), undef;
is $sh->occupied, !!1;
is $sh->count, 3;
is $sh->key_min, "a100";
is $sh->key_max, "a120";
is $sh->key_ge("a110"), "a120";
is $sh->key_gt("a110"), "a120";
is $sh->key_le("a110"), "a105";
is $sh->key_lt("a110"), "a105";
is_deeply $sh->keys_array, [qw(a100 a105 a120)];
is_deeply $sh->keys_hash, { a100=>undef, a105=>undef, a120=>undef };
is_deeply $sh->group_get_hash, { a100=>"b100", a105=>"b105", a120=>"b120" };
is $sn->exists("a115"), !!0;
is $sn->length("a115"), undef;
is $sn->get("a115"), undef;
is $sn->occupied, !!1;
is $sn->count, 3;
is $sn->key_min, "a100";
is $sn->key_max, "a120";
is $sn->key_ge("a110"), "a110";
is $sn->key_gt("a110"), "a120";
is $sn->key_le("a110"), "a110";
is $sn->key_lt("a110"), "a100";
is_deeply $sn->keys_array, [qw(a100 a110 a120)];
is_deeply $sn->keys_hash, { a100=>undef, a110=>undef, a120=>undef };
is_deeply $sn->group_get_hash, { a100=>"b100", a110=>"b110", a120=>"b120" };

$sh->gset("a115", "c115");
is $sh->get("a115"), "c115";
$sh->gset("a115", "d115");
is $sh->get("a115"), "d115";
$sh->gset("a115", "d115");
is $sh->get("a115"), "d115";
$sh->gset("a115", undef);
is $sh->get("a115"), undef;
$sh->gset("a115", undef);
is $sh->get("a115"), undef;

is scalar($sh->gset("a115", "e115")), undef;
is $sh->get("a115"), "e115";
is scalar($sh->gset("a115", "f115")), "e115";
is $sh->get("a115"), "f115";
is scalar($sh->gset("a115", "f115")), "f115";
is $sh->get("a115"), "f115";
is scalar($sh->gset("a115", undef)), "f115";
is $sh->get("a115"), undef;
is scalar($sh->gset("a115", undef)), undef;
is $sh->get("a115"), undef;

is_deeply [$sh->gset("a115", "g115")], [undef];
is $sh->get("a115"), "g115";
is_deeply [$sh->gset("a115", "h115")], ["g115"];
is $sh->get("a115"), "h115";
is_deeply [$sh->gset("a115", "h115")], ["h115"];
is $sh->get("a115"), "h115";
is_deeply [$sh->gset("a115", undef)], ["h115"];
is $sh->get("a115"), undef;
is_deeply [$sh->gset("a115", undef)], [undef];
is $sh->get("a115"), undef;

$sh->cset("a115", "z", "i115");
is $sh->get("a115"), undef;
$sh->cset("a115", undef, "j115");
is $sh->get("a115"), "j115";
$sh->cset("a115", "z", "k115");
is $sh->get("a115"), "j115";
$sh->cset("a115", undef, "l115");
is $sh->get("a115"), "j115";
$sh->cset("a115", "j115", "m115");
is $sh->get("a115"), "m115";
$sh->cset("a115", "z", "m115");
is $sh->get("a115"), "m115";
$sh->cset("a115", undef, "m115");
is $sh->get("a115"), "m115";
$sh->cset("a115", "m115", "m115");
is $sh->get("a115"), "m115";
$sh->cset("a115", "z", undef);
is $sh->get("a115"), "m115";
$sh->cset("a115", undef, undef);
is $sh->get("a115"), "m115";
$sh->cset("a115", "m115", undef);
is $sh->get("a115"), undef;
$sh->cset("a115", "z", undef);
is $sh->get("a115"), undef;
$sh->cset("a115", undef, undef);
is $sh->get("a115"), undef;

is scalar($sh->cset("a115", "z", "i115")), !!0;
is $sh->get("a115"), undef;
is scalar($sh->cset("a115", undef, "j115")), !!1;
is $sh->get("a115"), "j115";
is scalar($sh->cset("a115", "z", "k115")), !!0;
is $sh->get("a115"), "j115";
is scalar($sh->cset("a115", undef, "l115")), !!0;
is $sh->get("a115"), "j115";
is scalar($sh->cset("a115", "j115", "m115")), !!1;
is $sh->get("a115"), "m115";
is scalar($sh->cset("a115", "z", "m115")), !!0;
is $sh->get("a115"), "m115";
is scalar($sh->cset("a115", undef, "m115")), !!0;
is $sh->get("a115"), "m115";
is scalar($sh->cset("a115", "m115", "m115")), !!1;
is $sh->get("a115"), "m115";
is scalar($sh->cset("a115", "z", undef)), !!0;
is $sh->get("a115"), "m115";
is scalar($sh->cset("a115", undef, undef)), !!0;
is $sh->get("a115"), "m115";
is scalar($sh->cset("a115", "m115", undef)), !!1;
is $sh->get("a115"), undef;
is scalar($sh->cset("a115", "z", undef)), !!0;
is $sh->get("a115"), undef;
is scalar($sh->cset("a115", undef, undef)), !!1;
is $sh->get("a115"), undef;

is_deeply [$sh->cset("a115", "z", "i115")], [!!0];
is $sh->get("a115"), undef;
is_deeply [$sh->cset("a115", undef, "j115")], [!!1];
is $sh->get("a115"), "j115";
is_deeply [$sh->cset("a115", "z", "k115")], [!!0];
is $sh->get("a115"), "j115";
is_deeply [$sh->cset("a115", undef, "l115")], [!!0];
is $sh->get("a115"), "j115";
is_deeply [$sh->cset("a115", "j115", "m115")], [!!1];
is $sh->get("a115"), "m115";
is_deeply [$sh->cset("a115", "z", "m115")], [!!0];
is $sh->get("a115"), "m115";
is_deeply [$sh->cset("a115", undef, "m115")], [!!0];
is $sh->get("a115"), "m115";
is_deeply [$sh->cset("a115", "m115", "m115")], [!!1];
is $sh->get("a115"), "m115";
is_deeply [$sh->cset("a115", "z", undef)], [!!0];
is $sh->get("a115"), "m115";
is_deeply [$sh->cset("a115", undef, undef)], [!!0];
is $sh->get("a115"), "m115";
is_deeply [$sh->cset("a115", "m115", undef)], [!!1];
is $sh->get("a115"), undef;
is_deeply [$sh->cset("a115", "z", undef)], [!!0];
is $sh->get("a115"), undef;
is_deeply [$sh->cset("a115", undef, undef)], [!!1];
is $sh->get("a115"), undef;

$sh->idle;
is scalar($sh->idle), undef;
is_deeply [$sh->idle], [];

$sh->tidy;
is scalar($sh->tidy), undef;
is_deeply [$sh->tidy], [];

my $h;
$sh->tally_get;
$h = $sh->tally_get;
is ref($h), "HASH";
ok !grep { !/\A[a-z_]+\z/ } keys %$h;
ok !grep { !/\A(?:0|[1-9][0-9]*)\z/ } values %$h;
$h = [$sh->tally_get];
is @$h, 1;
is ref($h->[0]), "HASH";
ok !grep { !/\A[a-z_]+\z/ } keys %{$h->[0]};
ok !grep { !/\A(?:0|[1-9][0-9]*)\z/ } values %{$h->[0]};

$sh->tally_zero;
is scalar($sh->tally_zero), undef;
is_deeply [$sh->tally_zero], [];

$sh->tally_gzero;
$h = $sh->tally_gzero;
is ref($h), "HASH";
ok !grep { !/\A[a-z_]+\z/ } keys %$h;
ok !grep { !/\A(?:0|[1-9][0-9]*)\z/ } values %$h;
$h = [$sh->tally_gzero];
is @$h, 1;
is ref($h->[0]), "HASH";
ok !grep { !/\A[a-z_]+\z/ } keys %{$h->[0]};
ok !grep { !/\A(?:0|[1-9][0-9]*)\z/ } values %{$h->[0]};

my $nx = Hash::SharedMem::Handle->open("$tmpdir/t1", "c");
ok $nx;
ok is_shash($nx);
is scalar($nx->is_snapshot), !!0;
is_deeply [$nx->is_snapshot], [!!0];
is scalar($nx->is_readable), !!0;
is_deeply [$nx->is_readable], [!!0];
is scalar($nx->is_writable), !!0;
is_deeply [$nx->is_writable], [!!0];
is scalar($nx->mode), "";
is_deeply [$nx->mode], [""];
eval { $nx->exists("a100") };
like $@, qr#\Acan't\ read\ shared\ hash\ \Q$tmpdir\E/t1:
		\ shared\ hash\ was\ opened\ in\ unreadable\ mode\ #x;
eval { $nx->length("a100") };
like $@, qr#\Acan't\ read\ shared\ hash\ \Q$tmpdir\E/t1:
		\ shared\ hash\ was\ opened\ in\ unreadable\ mode\ #x;
eval { $nx->get("a100") };
like $@, qr#\Acan't\ read\ shared\ hash\ \Q$tmpdir\E/t1:
		\ shared\ hash\ was\ opened\ in\ unreadable\ mode\ #x;
eval { $nx->set("a100", "b100") };
like $@, qr#\Acan't\ write\ shared\ hash\ \Q$tmpdir\E/t1:
		\ shared\ hash\ was\ opened\ in\ unwritable\ mode\ #x;
eval { $nx->gset("a100", "b100") };
like $@, qr#\Acan't\ update\ shared\ hash\ \Q$tmpdir\E/t1:
		\ shared\ hash\ was\ opened\ in\ unreadable\ mode\ #x;
eval { $nx->cset("a100", "b100", "c100") };
like $@, qr#\Acan't\ update\ shared\ hash\ \Q$tmpdir\E/t1:
		\ shared\ hash\ was\ opened\ in\ unreadable\ mode\ #x;

eval { Hash::SharedMem::Handle->open("$tmpdir/t1", "c") };
is $@, "";
my @sh = Hash::SharedMem::Handle->open("$tmpdir/t1", "c");
is scalar(@sh), 1;
ok is_shash($sh[0]);
eval { ${\(Hash::SharedMem::Handle->open("$tmpdir/t1", "c"))} = undef; };
like $@, qr/\AModification of a read-only value attempted /;
eval { ${\($sh->snapshot)} = undef; };
like $@, qr/\AModification of a read-only value attempted /;
eval { ${\($sn->snapshot)} = undef; };
like $@, qr/\AModification of a read-only value attempted /;

1;
