package de.renew.engine.simulator;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Supplier;

import org.apache.log4j.Logger;

import de.renew.engine.thread.SimulationThreadPool;
import de.renew.simulatorontology.simulation.StepIdentifier;

/**
 * Factory class for creating step identifiers in simulation threads.
 */
public class StepIdentifierFactory {
    private static final Logger LOGGER = Logger.getLogger(StepIdentifierFactory.class);

    private final long _simulationRunId;
    private long _cycle;

    /**
     * Constructor of the StepIdentifierFactory.
     * @param simulationRunId the simulationRunId
     */
    public StepIdentifierFactory(long simulationRunId) {
        _simulationRunId = simulationRunId;
        _cycle = 0L;
    }

    /**
     * Creates a new step identifier with an incremented cycle.
     * This method must be called from a simulation thread.
     *
     * @return the next step identifier
     */
    public StepIdentifier nextStepIdentifier() {
        return executeInSimulationThread(
            () -> new StepIdentifier(_simulationRunId, new long[] { ++_cycle }));
    }

    /**
     * Creates a step identifier with the current cycle.
     * This method must be called from a simulation thread.
     *
     * @return the current step identifier
     */
    public StepIdentifier currentStepIdentifier() {
        return executeInSimulationThread(
            () -> new StepIdentifier(_simulationRunId, new long[] { _cycle }));
    }

    private StepIdentifier executeInSimulationThread(Supplier<StepIdentifier> supplier) {
        Future<StepIdentifier> future =
            SimulationThreadPool.getCurrent().submitAndWait(supplier::get);

        try {
            return future.get();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            LOGGER.error("Interrupted while waiting for simulation thread to finish", e);
            throw new RuntimeException("Simulation thread was interrupted", e);
        } catch (ExecutionException e) {
            LOGGER.error("Simulation thread threw an exception", e);
            throw new RuntimeException("Simulation thread execution failed", e.getCause());
        }
    }
}