package com.github.czyzby.reinvent.concurrent.lock;

import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.jetty.util.ConcurrentHashSet;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import com.github.czyzby.reinvent.concurrent.lock.LockProvider;

public class LockProviderTest {
    final LockProvider<String> lockProvider = LockProvider.newInstance();

    @Before
    public void initiate() {
        lockProvider.reset();
    }

    @Test
    public void shouldAssignOneLockToEachKeyConcurrently() throws Exception {
        final ExecutorService executor = Executors.newWorkStealingPool();

        final int locksAmount = 4;
        final LockProvider<Integer> lockProvider = LockProvider.newInstance();
        final Set<Lock> acquiredLocks = new ConcurrentHashSet<>();
        for (int index = 0; index < 10000; index++) {
            final int lockIndex = index % locksAmount;
            executor.execute(() -> acquiredLocks.add(lockProvider.acquireLock(lockIndex)));
        }
        executor.shutdown();
        executor.awaitTermination(1000, TimeUnit.MILLISECONDS);
        Assert.assertEquals(locksAmount, acquiredLocks.size());
    }

    @Test
    public void shouldPrepareLock() throws Exception {
        final String key = "key";
        Assert.assertFalse(lockProvider.getLock(key).isPresent());
        lockProvider.prepareLock(key);
        Assert.assertTrue(lockProvider.getLock(key).isPresent());
        Assert.assertEquals(lockProvider.acquireLock(key), lockProvider.getLock(key).get());
    }

    @Test
    public void shouldPrepareMultipleLocks() throws Exception {
        final String[] keys = { "key1", "key2", "key3" };
        for (final String key : keys) {
            Assert.assertFalse(lockProvider.getLock(key).isPresent());
        }
        lockProvider.prepareLocks(keys);
        for (final String key : keys) {
            Assert.assertTrue(lockProvider.getLock(key).isPresent());
        }
    }

    @Test
    public void shouldPrepareMultipleLocksFromCollection() throws Exception {
        final String[] keys = { "key1", "key2", "key3" };
        for (final String key : keys) {
            Assert.assertFalse(lockProvider.getLock(key).isPresent());
        }
        lockProvider.prepareLocks(Stream.of(keys).collect(Collectors.toList()));
        for (final String key : keys) {
            Assert.assertTrue(lockProvider.getLock(key).isPresent());
        }
    }

    @Test
    public void shouldRemoveLock() throws Exception {
        final String key = "key";
        lockProvider.prepareLock(key);
        Assert.assertTrue(lockProvider.getLock(key).isPresent());
        lockProvider.removeLock(key);
        Assert.assertFalse(lockProvider.getLock(key).isPresent());
    }

    @Test
    public void shouldRemoveMultipleLocks() throws Exception {
        final String[] keys = { "key1", "key2", "key3" };
        lockProvider.prepareLocks(keys);
        for (final String key : keys) {
            Assert.assertTrue(lockProvider.getLock(key).isPresent());
        }
        lockProvider.removeLocks(keys);
        for (final String key : keys) {
            Assert.assertFalse(lockProvider.getLock(key).isPresent());
        }
    }

    @Test
    public void shouldRemoveMultipleLocksFromCollection() throws Exception {
        final String[] keys = { "key1", "key2", "key3" };
        lockProvider.prepareLocks(keys);
        for (final String key : keys) {
            Assert.assertTrue(lockProvider.getLock(key).isPresent());
        }
        lockProvider.removeLocks(Stream.of(keys).collect(Collectors.toList()));
        for (final String key : keys) {
            Assert.assertFalse(lockProvider.getLock(key).isPresent());
        }
    }
}
