package de.renew.application;

import org.junit.Ignore;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import de.renew.engine.thread.SimulationThreadPool;
import de.renew.simulator.api.ISimulationManager;
import de.renew.util.Semaphor;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class RaceConditionTest {
    private static final String STARTER_THREAD_A_NAME = "starterA";
    private static final String STARTER_THREAD_C_NAME = "starterC";
    private static final String STOPPER_THREAD_B_NAME = "stopperB";

    private ISimulationManager _simulationManager;

    @BeforeEach
    public void setUp() {
        _simulationManager = SimulationManagerProvider.provider();
        System.out.println("Set up");
    }

    @AfterEach
    public void tearDown() {
        _simulationManager.cleanup();
        _simulationManager = null;
    }

    /**
     * This test sets up race conditions using different {@link Runnable}
     * implementations to start and terminate the simulation in the
     * {@link SimulatorPlugin}.
     * <p>
     * This scenario is only possible because the test implementation
     * does not conform to the lock-contract of the simulator plug-in.
     */
    @Disabled("Disabled until RNWCOMM-678 is fixed")
    @Test
    public void testSetupRaceCondition() {
        StarterThread starterA = new StarterThread(STARTER_THREAD_A_NAME);
        SimulationThreadPool.getNew().execute(starterA);
        starterA._initialized.P();

        StarterThread starterC = new StarterThread(STARTER_THREAD_C_NAME);
        SimulationThreadPool.getNew().execute(starterC);
        starterC._initialized.P();

        starterA._setup.V();
        starterA._setupReady.P();
        assertTrue(SimulationThreadPool.getCurrent().isMyThread(starterA._thread));
        starterA._setup.V();

        StopperThread stopperB = new StopperThread(STOPPER_THREAD_B_NAME);
        SimulationThreadPool.getCurrent().execute(stopperB);
        stopperB._initialized.P();
        assertTrue(SimulationThreadPool.getCurrent().isMyThread(stopperB._thread));
        stopperB._stop.V();
        stopperB._stopReady.P();

        //StarterThread starterC = new StarterThread("starterC");
        //SimulationThreadPool.getNew().execute(starterC);
        //starterC.initialized.P();
        starterC._setup.V();
        starterC._setupReady.P();
        assertFalse(SimulationThreadPool.getCurrent().isMyThread(starterC._thread));
        starterC._setup.V();
    }

    @Ignore
    private class StarterThread implements Runnable {
        private final Semaphor _initialized = new Semaphor();
        private final Semaphor _setup = new Semaphor();
        private final Semaphor _setupReady = new Semaphor();
        private Thread _thread;
        private final String _name;

        public StarterThread(String name) {
            _name = name;
        }

        @Override
        public void run() {
            _thread = Thread.currentThread();
            _initialized.V();
            System.out.println(_name + " initialized");
            _setup.P();
            _simulationManager.setupSimulation(null);
            _setupReady.V();
            _setup.P();
            System.out.println(_name + " finished");
        }
    }

    @Ignore
    private class StopperThread implements Runnable {
        private final Semaphor _initialized = new Semaphor();
        private final Semaphor _stop = new Semaphor();
        private final Semaphor _stopReady = new Semaphor();
        private Thread _thread;
        private final String _name;

        public StopperThread(String name) {
            _name = name;
        }

        @Override
        public void run() {
            _thread = Thread.currentThread();
            _initialized.V();
            System.out.println(_name + " initialized");
            _stop.P();
            _simulationManager.terminateSimulation();
            _stopReady.V();
            System.out.println(_name + " finished");
        }
    }
}