package games.strategy.util;

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.swing.SwingUtilities;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
import org.junit.runner.RunWith;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.StrictStubs.class)
public final class EventThreadJOptionPaneTest {
  @Rule
  public final Timeout globalTimeout = new Timeout(5, TimeUnit.SECONDS);

  @Spy
  private final CountDownLatchHandler latchHandler = new CountDownLatchHandler();

  @Test
  public void testInvokeAndWaitWithSupplier_ShouldRunSupplierOnEventDispatchThreadWhenNotCalledFromEventDispatchThread()
      throws Exception {
    final CountDownLatch latch = new CountDownLatch(1);
    final AtomicBoolean runOnEventDispatchThread = new AtomicBoolean(false);

    new Thread(() -> {
      EventThreadJOptionPane.invokeAndWait(latchHandler, () -> {
        runOnEventDispatchThread.set(SwingUtilities.isEventDispatchThread());
        return Optional.empty();
      });
      latch.countDown();
    }).start();
    latch.await();

    assertThat(runOnEventDispatchThread.get(), is(true));
  }

  @Test
  public void testInvokeAndWaitWithSupplier_ShouldNotDeadlockWhenCalledFromEventDispatchThread() throws Exception {
    final CountDownLatch latch = new CountDownLatch(1);
    final AtomicBoolean run = new AtomicBoolean(false);

    SwingUtilities.invokeLater(() -> {
      EventThreadJOptionPane.invokeAndWait(latchHandler, () -> {
        run.set(true);
        return Optional.empty();
      });
      latch.countDown();
    });
    latch.await();

    assertThat(run.get(), is(true));
  }

  @Test
  public void testInvokeAndWaitWithSupplier_ShouldReturnSupplierResult() {
    final Object expectedResult = new Object();

    final Object actualResult =
        EventThreadJOptionPane.invokeAndWait(latchHandler, () -> Optional.of(expectedResult)).get();

    assertThat(actualResult, is(expectedResult));
  }

  @Test
  public void testInvokeAndWaitWithSupplier_ShouldRegisterAndUnregisterLatchWithLatchHandler() {
    EventThreadJOptionPane.invokeAndWait(latchHandler, () -> Optional.empty());

    verify(latchHandler, times(1)).addShutdownLatch(any(CountDownLatch.class));
    verify(latchHandler, times(1)).removeShutdownLatch(any(CountDownLatch.class));
  }

  @Test
  public void testInvokeAndWaitWithIntSupplier_ShouldReturnIntSupplierResult() {
    final int expectedResult = 42;

    final int actualResult = EventThreadJOptionPane.invokeAndWait(latchHandler, () -> expectedResult);

    assertThat(actualResult, is(expectedResult));
  }
}
