use strict;
use warnings;

use Test::Fatal;
use Test::More;
use Fcntl ':flock';    # import LOCK_* constants
use Path::Tiny;

my $tmp_dir = Path::Tiny->tempdir(CLEANUP => 1);

# the default semantics of flock(2) allows to re-set
# lock type via sequential flock invocations from
# lock owning process. That's not desired behaviour
# for tests, so, we don't allow that, to

my $locked = 0;

BEGIN {
    *CORE::GLOBAL::flock = sub {
        my ($fd, $flags) = @_;
        if ($flags & (LOCK_EX | LOCK_SH)) {
            print("locking file (current status = $locked)\n");
            if (!$locked) {
                return $locked = 1;
            }
        } elsif ($flags & LOCK_UN) {
            print("unlocking (current status = $locked)\n");
            if ($locked) {
                $locked = 0;
                return 1;
            } else {
                die("cannot unlock $fd: it wasn't locked\n");
            }
        }
    };
}

use IPC::LeaderBoard;

my $create = sub {
    return IPC::LeaderBoard::create(
        n_slots           => 2,
        slot_shared_size  => 4,
        slot_private_size => 2,
        mmaped_file       => "$tmp_dir/lb.score",
    );
};

my $attach = sub {
    return IPC::LeaderBoard::attach(
        n_slots           => 2,
        slot_shared_size  => 4,
        slot_private_size => 2,
        mmaped_file       => "$tmp_dir/lb.score",
    );
};

subtest "creation & attachments" => sub {
    $locked = 0;
    my $master = $create->();
    ok $master, "leaderboard has been created";
    my @clients = map { $attach->() } (1 .. 2);
    is scalar(@clients), 2, "multiple clients can attach to LeaderBoard";

    like(exception { $create->() }, qr/owned/, "cannot create LeaderBoard as it can be exlusively onwned once by one process",);

    undef $master;    # explicitely destroy object before creation of the new one
    $master = $create->();
    ok $master, "LeaderBoard info has been recreated - exclusively";

    undef $master;
    like(exception { $attach->() }, qr/abandoned/, "cannot attach to OnlineInfo as it seems to be abandoned",);

};

subtest "usage" => sub {
    $locked = 0;
    my $master = $create->();
    my ($slave_1, $slave_2) = map { $attach->() } (0 .. 1);

    is_deeply [$slave_1->read_slot(0)], [[(0) x 4], [(0) x 2]], "initial values";
    is_deeply [$slave_2->read_slot(1)], [[(0) x 4], [(0) x 2]], "initial values";

    # we have to read the related row, before updating it
    $slave_1->read_slot(0);
    $slave_2->read_slot(0);
    ok $slave_1->update(0, [1, 2, 3, 4], 0 => 6), "successfull shared & private update";
    ok !$slave_2->update(0, [11, 12, 13, 14], 1 => 9), "non-successfull shared update (somebody else updated the data), but successfull private";
    ok !$slave_1->update(0, [21, 22, 23, 24]), "non-successfull update (we have to re-read record), no private update";

    is_deeply [$slave_1->read_slot(0)], [[1, 2, 3, 4], [6, 9]], "assure that we get the last values we have written";
    is_deeply [$slave_2->read_slot(0)], [[1, 2, 3, 4], [6, 9]], "assure that we get the last values we have written";

    ok $slave_2->update(
        0, [11, 12, 13, 14],
        0 => 66,
        1 => 99
        ),
        "after refresh, as nobody modified it, we can update";
    is_deeply [$slave_1->read_slot(0)], [[11, 12, 13, 14], [66, 99]], "assure that we get the last values we have written";

    $slave_1->read_slot(0);
    ok !$slave_1->update(0, 0 => 7), "successfull private update (no succes of shared data update, as we don't provide values for it)";
    is_deeply [$slave_1->read_slot(0)], [[11, 12, 13, 14], [7, 99]], "assure that we get the last values we have written";

};

done_testing;
