package simulator.cross.module;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

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

import de.renew.engine.thread.SimulationThreadPool;
import de.renew.net.Net;
import de.renew.net.NetElementID;
import de.renew.net.Place;
import de.renew.simulatorontology.simulation.SimulationRunningException;
import de.renew.util.RenewObjectInputStream;
import de.renew.util.RenewObjectOutputStream;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

/**
 * This class is a test class for the interaction between the classes {@link Place} from the module
 * de.renew.Simulator and {@link RenewObjectOutputStream} and {@link RenewObjectInputStream}
 * from the module {@link de.renew.util}.
 */
public class PlaceTest {
    private static final String FILE_PATH =
        String.join(File.separator, "src", "integTest", "resources", "test.ser");
    private static final String PLACE_NAME = "Test";

    private final File _testFile = new File(FILE_PATH);

    private RenewObjectInputStream _renewObjectInputStream;
    private RenewObjectOutputStream _renewObjectOutputStream;
    private Place _place;
    private Place _readPlace;
    private boolean _done;

    /**
     * Setup method to initialize this test environment.
     *
     * @throws IOException if there is an error with the creation of the file
     *                     or streams
     */
    @BeforeEach
    void setUp() throws IOException {
        if (_testFile.exists()) {
            boolean deleted = _testFile.delete();
            if (!deleted) {
                throw new IOException(
                    "Failed to delete existing file: " + _testFile.getAbsolutePath());
            }
        }
        if (!_testFile.exists()) {
            boolean created = _testFile.createNewFile();
            if (!created) {
                throw new IOException("Failed to create new file: " + _testFile.getAbsolutePath());
            }
        }
        _done = false;

        FileOutputStream fileOutputStream = new FileOutputStream(_testFile);
        FileInputStream fileInputStream = new FileInputStream(_testFile);

        _renewObjectOutputStream = new RenewObjectOutputStream(fileOutputStream);
        _renewObjectInputStream = new RenewObjectInputStream(fileInputStream);
    }

    /**
     * Tests the methods {@link RenewObjectOutputStream#writeObject}
     * and {@link RenewObjectInputStream#readObject()}
     * It creates a place, serializes it, deserializes it and compares the two places.
     *
     * @throws InterruptedException if any thread interrupts the current thread
     *                              while it is sleeping
     */
    @Test
    void testWriteAndReadObjectPlace() throws InterruptedException {
        Runnable runnable = () -> {
            try {
                //given
                Net net = new Net();
                NetElementID netElementID = new NetElementID();
                _place = new Place(net, PLACE_NAME, netElementID);

                //when
                _renewObjectOutputStream.writeObject(_place);
                _readPlace = (Place) _renewObjectInputStream.readObject();

                _done = true;
            } catch (SimulationRunningException | IOException | ClassNotFoundException e) {
                SimulationThreadPool.discardNew();
                e.printStackTrace();
            }
        };

        //when
        runOnSimulationThread(runnable);

        //then
        assertNotNull(_readPlace);
        assertEquals(PLACE_NAME, _readPlace.getName());
        assertEquals(_place.getID(), _readPlace.getID());
    }

    /**
     * Runs the given {@code Runnable} on a simulation thread and waits until
     * the execution is finished.
     *
     * @param runnable the {@code Runnable} to run
     * @throws InterruptedException if any thread interrupts the current thread
     *                              while it is sleeping
     */
    private void runOnSimulationThread(Runnable runnable) throws InterruptedException {
        _done = false;
        SimulationThreadPool.getNew();
        SimulationThreadPool simulationThreadPool = SimulationThreadPool.getSimulationThreadPool();
        Thread simulationThread = simulationThreadPool.getThreadFactory().newThread(runnable);
        simulationThread.start();
        while (!_done) {
            System.out.println("Simulation thread is running");
            Thread.sleep(2000);
        }
    }
}