package de.renew.application;

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

import org.apache.log4j.Logger;

import de.renew.database.SetupHelper;
import de.renew.database.SetupHelper.SimulationState;
import de.renew.engine.thread.SimulationThreadPool;
import de.renew.simulator.api.ISimulationLockExecutor;
import de.renew.simulator.api.ISimulationManager;
import de.renew.simulatorontology.simulation.NoSimulationException;

/**
 * An implementation of {@link ISimulationStateRestorer}
 */
class SimulationStateRestorerImpl implements ISimulationStateRestorer {
    /**
     * The logger for this class.
     */
    private static final Logger LOGGER = Logger.getLogger(SimulationStateRestorerImpl.class);

    private final ISimulationLockExecutor _lockExecutor;
    private final ISimulationManager _simulationManager;

    SimulationStateRestorerImpl(
        ISimulationLockExecutor lockExecutor, ISimulationManager simulationManager)
    {
        _lockExecutor = lockExecutor;
        _simulationManager = simulationManager;
    }

    @Override
    public SimulationState restoreStateFromDatabase() {
        Supplier<SimulationState> action = () -> {
            try {
                Future<SimulationState> future = SimulationThreadPool.getCurrent().submitAndWait(
                    () -> SetupHelper
                        .setup(_simulationManager.getCurrentEnvironment().getProperties()));
                return future.get();
            } catch (InterruptedException e) {
                LOGGER.error("Timeout while waiting for simulation thread to finish", e);
            } catch (ExecutionException e) {
                Throwable t = e.getCause();
                if (t instanceof NoSimulationException exc) {
                    throw exc;
                } else if (t instanceof RuntimeException exc) {
                    throw exc;
                } else if (t instanceof Error exc) {
                    throw exc;
                } else {
                    LOGGER.error("Simulation thread threw an exception", e);
                }
            }

            // We should never return nothing but some error occurred before.
            return null;
        };

        return _lockExecutor.supplyWithLock(action);
    }
}
