package de.renew.engine.thread;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import de.renew.util.Semaphor;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;


/**
 * Test class for the {@link BlockingSimulationRunnable}.
 *
 * @author Felix Ortmann
 */
public class BlockingSimulationRunnableTest {
    private static final String TEST_THREAD_NAME = "test";
    private static final String TEST_STRING_1 = "1";
    private static final String TEST_STRING_2 = "2";
    private static final String TEST_STRING_3 = "3";

    private Runnable _runnable;
    private BlockingSimulationRunnable _block1;
    private BlockingSimulationRunnable _block2;
    private BlockingSimulationRunnable _block3;
    private Semaphor _lock;
    private Thread _thread;

    //since we have no return-statements in the runnables, we will change a String
    private String _changeableTestString1;
    private String _changeableTestString2;
    private String _changeableTestString3;
    private SimulationThreadPool _pool;

    /**
     * Setup method to initialize this test environment.
     * Resets the test strings to null and initializes the fields that will be
     * used in the tests.
     */
    @BeforeEach
    public void setUp() {
        _changeableTestString1 = null;
        _changeableTestString2 = null;
        _changeableTestString3 = null;

        _lock = mock(Semaphor.class);
        _thread = Thread.currentThread();

        _pool = SimulationThreadPool.getNew();

        _runnable = () -> _changeableTestString1 = TEST_STRING_1;

        Runnable innerRun1 = () -> _changeableTestString2 = TEST_STRING_2;
        Runnable innerRun2 = () -> _changeableTestString3 = TEST_STRING_3;

        //initialize the blockings under test
        _block1 = new BlockingSimulationRunnable(_runnable, _lock, _thread);
        _block2 = new BlockingSimulationRunnable(innerRun1, _lock, _thread);
        _block3 = new BlockingSimulationRunnable(innerRun2, _lock, null);
    }

    /**
     * Test method for {@link BlockingSimulationRunnable#getAncestor}.
     */
    @Test
    public final void testGetAncestor() {
        //when/then
        assertEquals(_thread, _block1.getAncestor());
        assertEquals(_thread, _block2.getAncestor());
        assertNull(_block3.getAncestor());
    }

    /**
     * Test method for {@link BlockingSimulationRunnable#run}.
     */
    @Test
    public final synchronized void testRun() {
        //when
        _pool.execute(_block1);
        _pool.execute(_block2);
        _pool.execute(_block3);

        try {
            System.out.println("Waiting a bit for the Runnable-Execution to finish.");
            wait(1500);
        } catch (InterruptedException e) {
            // we want the pool to have enough time to execute each runnable concurrently
            e.printStackTrace();
        }

        //then
        assertEquals(TEST_STRING_1, _changeableTestString1);
        assertEquals(TEST_STRING_2, _changeableTestString2);
        assertEquals(TEST_STRING_3, _changeableTestString3);

        //test the unlocking behavior of the run-methods final statement
        verify(_lock, atLeast(3)).V();
    }

    /**
     * Test method for {@link BlockingSimulationRunnable#abort}.
     */
    @Test
    public final void testAbort() {
        //given
        SimulationThread simThread =
            new SimulationThread(_thread.getThreadGroup(), _runnable, TEST_THREAD_NAME, 5);

        //when
        _block2.abort(simThread);

        //then
        assertNull(_block2.getAncestor());
        assertNull(simThread.getAncestor());
        verify(_lock, atLeastOnce()).V();
    }

    /**
     * Test method for {@link BlockingSimulationRunnable#abort}
     * with {@code null} as the parameter.
     */
    @Test
    public final void testAbortNull() {
        //when
        _block1.abort(null);

        //then
        assertNull(_block1.getAncestor());
        verify(_lock, atLeastOnce()).V();
    }
}