package de.renew.engine.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

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.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;


/**
 * Test class for the {@link WrappedFutureTask}.
 *
 * @author Felix Ortmann
 */
public class WrappedFutureTaskTest {
    private static final String TEST_STRING = "some String to be returned...";

    private Semaphor _lock;
    private Callable<Object> _callableWithWait;
    private Object _returnValue1;
    private String _returnValue2;
    private WrappedFutureTask<Object> _wrappedFutureTask1;
    private WrappedFutureTask<String> _wrappedFutureTask2;
    private WrappedFutureTask<Object> _wrappedFutureTask3;

    /**
     * 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() {
        _returnValue1 = new Object();
        _returnValue2 = TEST_STRING;

        //we do something and then return what we computed
        Callable<Object> callable1 = () -> {
            //we do something and then return what we computed
            return _returnValue1;
        };

        //we do something and then return what we computed
        Callable<String> callable2 = () -> {
            //we do something and then return what we computed
            return _returnValue2;
        };

        _callableWithWait = () -> {
            //we do something and then return what we computed
            //here we wait a bit ;)
            Thread.sleep(2000); //2 seconds
            return _returnValue2; //strings are also objects
        };

        _lock = new Semaphor();

        BlockingSimulationCallable<Object> sampleBlock1 =
            new BlockingSimulationCallable<>(callable1, _lock, Thread.currentThread());
        BlockingSimulationCallable<String> sampleBlock2 =
            new BlockingSimulationCallable<>(callable2, _lock, Thread.currentThread());
        BlockingSimulationCallable<Object> sampleBlock3 =
            new BlockingSimulationCallable<>(_callableWithWait, _lock, Thread.currentThread());

        _wrappedFutureTask1 = new WrappedFutureTask<>(sampleBlock1);
        _wrappedFutureTask2 = new WrappedFutureTask<>(sampleBlock2);
        _wrappedFutureTask3 = new WrappedFutureTask<>(sampleBlock3);
    }

    /**
     * Test method for {@link WrappedFutureTask#get()}.
     */
    @Test
    public final void testGetNormal() {
        //first we assume that everything is ok, we just want the return values.
        System.out.println("Execute the test tasks to be able to use the get() method.");
        //given
        SimulationThreadPool pool = SimulationThreadPool.getNew();
        pool.execute(_wrappedFutureTask1);
        pool.execute(_wrappedFutureTask2);
        pool.execute(_wrappedFutureTask3);
        try {
            System.out.println(
                "After this line there should no exception be thrown! \nTrying to get the return values.");
            //when/then
            assertEquals(_returnValue1, _wrappedFutureTask1.get());
            assertEquals(_returnValue2, _wrappedFutureTask2.get());
            assertEquals(_returnValue2, _wrappedFutureTask3.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    /**
     * Test method for {@link WrappedFutureTask#get()}
     * with an interrupt in the {@link Callable}.
     * This test checks that locks are unlocked if the {@code get}-Method
     * is called on a faulty {@code Callable}.
     */
    @Test
    public final void testGetWithInterrupt() {
        //given
        // now we voluntarily enforce an interrupt, to check the deblocking function of
        // get in case of exceptions.
        Callable<Object> callable1 = () -> {
            throw new InterruptedException();
        };

        Callable<String> callable2 = () -> {
            throw new ExecutionException(null);
        };

        //now we need semaphore mocks to test if methods were invoked on them
        _lock = mock(Semaphor.class);

        //wrap faulty callables into the blockingCallables
        BlockingSimulationCallable<Object> block1 =
            new BlockingSimulationCallable<>(callable1, _lock, Thread.currentThread());
        BlockingSimulationCallable<String> block2 =
            new BlockingSimulationCallable<>(callable2, _lock, Thread.currentThread());

        //create new WrappedFutureTasks to test their behaviour under exceptions...
        _wrappedFutureTask1 = new WrappedFutureTask<>(block1);
        _wrappedFutureTask2 = new WrappedFutureTask<>(block2);

        System.out.println("Execute the test tasks to be able to use the get() method.");
        SimulationThreadPool pool = SimulationThreadPool.getNew();

        pool.execute(_wrappedFutureTask1);
        pool.execute(_wrappedFutureTask2);
        pool.execute(_wrappedFutureTask3);

        //when/then
        try {
            System.out.println(
                "After this line an exception should be thrown, invisible on the console.");
            _wrappedFutureTask1.get();
        } catch (InterruptedException | ExecutionException e) {
            // when we got here, the wrappedFutureTask exception handling should have made
            // sure, that the lock is unblocked by now (V() was called). This is what we test for now.
            verify(_lock, atLeastOnce()).V(); //at least once the semaphore was unlocked.
                                              //this has to be, cause wrappedFutureTask shall unlock in case of exception!
        }

        try {
            System.out.println(
                "After this line an exception should be thrown, invisible on the console.");
            _wrappedFutureTask2.get();
        } catch (InterruptedException | ExecutionException e) {
            // when we got here, the wrappedFutureTask exception handling should have made
            // sure, that the lock is unblocked by now (V() was called). This is what we test for now.
            verify(_lock, atLeastOnce()).V(); //at least once the semaphore was unlocked.
                                              //this has to be, cause wrappedFutureTask shall unlock in case of exception!
        }
    }

    /**
     * Test method for {@link WrappedFutureTask#get(long, java.util.concurrent.TimeUnit)}.
     */
    @Test
    public final void testGetLongTimeUnit() {
        //now we wait a given amount of time for the result to be computed.
        //first we assume that everything is ok, we just want the return values.
        System.out.println("Execute the test tasks to be able to use the get() method.");

        //given
        SimulationThreadPool pool = SimulationThreadPool.getNew();
        pool.execute(_wrappedFutureTask1);
        pool.execute(_wrappedFutureTask2);
        pool.execute(_wrappedFutureTask3);

        //when/then
        try {
            System.out.println(
                "After this line there should no exception be thrown! \nTrying to get the return values.");
            assertEquals(_returnValue1, _wrappedFutureTask1.get(1000, TimeUnit.MILLISECONDS)); //max. wait a sec.
            assertEquals(_returnValue2, _wrappedFutureTask2.get(1000, TimeUnit.MILLISECONDS)); //max. wait a sec.
            assertEquals(_returnValue2, _wrappedFutureTask3.get(2500, TimeUnit.MILLISECONDS)); //max. wait 2.5 secs.
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            e.printStackTrace();
        }
    }

    /**
     * Test method for {@link WrappedFutureTask#get(long, java.util.concurrent.TimeUnit)}.
     */
    @Test
    public final void testGetLongTimeUnitWithTimeout() {
        //now we wait a given amount of time for the result to be computed.

        //given
        //first set up a mock semaphore
        _lock = mock(Semaphor.class);

        BlockingSimulationCallable<Object> block =
            new BlockingSimulationCallable<>(_callableWithWait, _lock, Thread.currentThread());
        _wrappedFutureTask3 = new WrappedFutureTask<>(block);

        System.out.println("Execute the test tasks to be able to use the get() method.");
        SimulationThreadPool pool = SimulationThreadPool.getNew();

        pool.execute(_wrappedFutureTask1);
        pool.execute(_wrappedFutureTask2);
        pool.execute(_wrappedFutureTask3);

        //when/then
        try {
            System.out.println(
                "After this line an exception should be thrown, which is invisible on the console.");
            System.out.println("If actually a visible exception is thrown, the test has failed!");
            //now we wait too short cause this callable needs at least 2 seconds to finish.
            _wrappedFutureTask3.get(1000, TimeUnit.MILLISECONDS); //max. wait a sec.
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            //if we break here, the test fails - see stacktrace
        } catch (TimeoutException e) {
            verify(_lock, atLeastOnce()).V(); // since we timed out, we want to verify the
                                              //exception handling of wrappedFutureTask and test, whether the semaphore is unlocked now.
        }
    }
}