package de.renew.remote;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import de.renew.engine.simulator.BindingList;
import de.renew.engine.thread.SimulationThreadPool;
import de.renew.net.NetInstance;
import de.renew.net.NetInstanceList;
import de.renew.simulatorontology.simulation.SimulationEnvironment;


/**
 * A simulator accessor allows an application to control
 * a remote simulator by starting and stopping the search process,
 * monitoring firing transitions, listing well-known net instances,
 * etc.
 *
 * A simulator accessor can be registered at the RMI registry as a
 * starting point for remote accesses.
 */
public class SimulatorAccessorImpl extends UnicastRemoteObject implements SimulatorAccessor {

    /**
     * The LOGGER variable is used to enable logging for the SimulatorAccessorImpl class.
     * It provides a mechanism to log various events, errors, or informational messages
     * during the execution of simulator-related operations. The logger is instantiated
     * using the Log4j framework and is associated specifically with the SimulatorAccessorImpl
     * class, ensuring that the log outputs are easily traceable and context-specific.
     * This logger can be used to aid debugging, maintain operational insights, or to monitor
     * the behavior of the simulator and its associated processes.
     */
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(SimulatorAccessorImpl.class);

    /**
     * Represents the simulation environment associated with this simulator accessor.
     * It provides an encapsulated instance of {@link SimulationEnvironment} that
     * contains all parameters and configurations of the running simulation.
     *
     * This variable is final and is initialized through the constructor of the
     * {@code SimulatorAccessorImpl} class. It ensures that the simulator accessor
     * always operates within the scope of a defined simulation context, allowing
     * access to simulation engines, extensions, and properties relevant to the active
     * simulation instance.
     */
    private final SimulationEnvironment _environment;

    /**
     * Constructs a new instance of the SimulatorAccessorImpl.
     *
     * @param environment the simulation environment associated with this simulator accessor.
     * @throws RemoteException if there is a communication-related exception during remote method invocation.
     */
    public SimulatorAccessorImpl(SimulationEnvironment environment) throws RemoteException {
        super(0, SocketFactoryDeterminer.getInstance(), SocketFactoryDeterminer.getInstance());
        this._environment = environment;
    }

    @Override
    public NetInstanceAccessor[] getNetInstances() throws RemoteException {
        Future<NetInstanceAccessor[]> future =
            SimulationThreadPool.getCurrent().submitAndWait(new Callable<NetInstanceAccessor[]>()
            {
                @Override
                public NetInstanceAccessor[] call() throws Exception {
                    NetInstance[] netInstances = NetInstanceList.getAll();
                    NetInstanceAccessor[] result = new NetInstanceAccessor[netInstances.length];

                    for (int i = 0; i < netInstances.length; i++) {
                        result[i] = new NetInstanceAccessorImpl(netInstances[i], _environment);
                    }
                    return result;
                }
            });
        try {
            return future.get();
        } catch (InterruptedException e) {
            LOGGER.error("Timeout while waiting for simulation thread to finish", e);
        } catch (ExecutionException e) {
            LOGGER.error("Simulation thread threw an exception", e);
        }

        // We should never return nothing but some error occured befor.
        return null;

    }

    /**
     * Query whether the simulator is still running.
     *
     * return true, if simulator could possibly make another step
     * in the future.
     */
    @Override
    public boolean isActive() throws RemoteException {
        Future<Boolean> future =
            SimulationThreadPool.getCurrent().submitAndWait(new Callable<Boolean>()
            {
                @Override
                public Boolean call() throws Exception {
                    return _environment.getSimulator().isActive();
                }
            });
        try {
            return future.get();
        } catch (InterruptedException e) {
            LOGGER.error("Timeout while waiting for simulation thread to finish", e);
        } catch (ExecutionException e) {
            LOGGER.error("Simulation thread threw an exception", e);
        }

        // We should never return nothing but some error occured befor.
        return false;

    }

    /**
     * Start the simulator in the background. Return immediately.
     */
    @Override
    public void startRun() throws RemoteException {
        Future<Object> future =
            SimulationThreadPool.getCurrent().submitAndWait(new Callable<Object>()
            {
                @Override
                public Object call() throws Exception {
                    _environment.getSimulator().startRun();
                    return null;
                }
            });
        try {
            future.get();
        } catch (InterruptedException e) {
            LOGGER.error("Timeout while waiting for simulation thread to finish", e);
        } catch (ExecutionException e) {
            LOGGER.error("Simulation thread threw an exception", e);
        }
    }

    /**
     * Stop the simulator as soon as possible.
     * Already firing transitions may continue to fire after
     * this method has returned, but no new transitions will
     * start firing.
     */
    @Override
    public void stopRun() throws RemoteException {
        Future<Object> future =
            SimulationThreadPool.getCurrent().submitAndWait(new Callable<Object>()
            {
                @Override
                public Object call() throws Exception {
                    _environment.getSimulator().stopRun();
                    return null;
                }
            });
        try {
            future.get();
        } catch (InterruptedException e) {
            LOGGER.error("Timeout while waiting for simulation thread to finish", e);
        } catch (ExecutionException e) {
            LOGGER.error("Simulation thread threw an exception", e);
        }
    }

    /**
     * Terminate the simulator once and for all.
     * Do some final clean-up and exit all threads.
     *
     * Already firing transitions may continue to fire after
     * this method has returned, but no new transitions will
     * start firing nor is it allowed to restart the simulator
     * by another call.
     */
    @Override
    public void terminateRun() throws RemoteException {
        Future<Object> future =
            SimulationThreadPool.getCurrent().submitAndWait(new Callable<Object>()
            {
                @Override
                public Object call() throws Exception {
                    _environment.getSimulator().terminateRun();
                    return null;
                }
            });
        try {
            future.get();
        } catch (InterruptedException e) {
            LOGGER.error("Timeout while waiting for simulation thread to finish", e);
        } catch (ExecutionException e) {
            LOGGER.error("Simulation thread threw an exception", e);
        }
    }

    /**
     * Try to perform one more step, then stop.
     * Return a status code according
     * to the five possibilities listed in the Simulator interface.
     *
     * @see de.renew.simulatorontology.simulation.Simulator#STATUS_STOPPED
     * @see de.renew.simulatorontology.simulation.Simulator#STATUS_STEP_COMPLETE
     * @see de.renew.simulatorontology.simulation.Simulator#STATUS_LAST_COMPLETE
     * @see de.renew.simulatorontology.simulation.Simulator#STATUS_CURRENTLY_DISABLED
     * @see de.renew.simulatorontology.simulation.Simulator#STATUS_DISABLED
     *
     * @return the status code after the possible step
     */
    @Override
    public int step() throws RemoteException {
        Future<Integer> future =
            SimulationThreadPool.getCurrent().submitAndWait(new Callable<Integer>()
            {
                @Override
                public Integer call() throws Exception {
                    return _environment.getSimulator().step();
                }
            });
        try {
            return future.get();
        } catch (InterruptedException e) {
            LOGGER.error("Timeout while waiting for simulation thread to finish", e);
        } catch (ExecutionException e) {
            LOGGER.error("Simulation thread threw an exception", e);
        }

        // We should never return nothing but some error occured befor.
        return -1;

    }

    /**
     * Make sure to refresh internal data structures after the
     * firing of a transition outside the control of this simulator.
     * This is be required by simulators that cache possible bindings.
     */
    @Override
    public void refresh() throws RemoteException {
        Future<Object> future =
            SimulationThreadPool.getCurrent().submitAndWait(new Callable<Object>()
            {
                @Override
                public Object call() throws Exception {
                    _environment.getSimulator().refresh();
                    return null;
                }
            });
        try {
            future.get();
        } catch (InterruptedException e) {
            LOGGER.error("Timeout while waiting for simulation thread to finish", e);
        } catch (ExecutionException e) {
            LOGGER.error("Simulation thread threw an exception", e);
        }
    }

    /**
     * Stop the simulator and return only after the last transition
     * has completed firing.
     *
     * @throws RemoteException if there are communication-related exceptions 
     *         during remote method invocation
     */
    public void totallyStopSimulation() throws RemoteException { //NOTICEthrows
        Future<Object> future =
            SimulationThreadPool.getCurrent().submitAndWait(new Callable<Object>()
            {
                @Override
                public Object call() throws Exception {
                    _environment.getSimulator().stopRun();
                    BindingList.waitUntilEmpty();
                    return null;
                }
            });
        try {
            future.get();
        } catch (InterruptedException e) {
            LOGGER.error("Timeout while waiting for simulation thread to finish", e);
        } catch (ExecutionException e) {
            LOGGER.error("Simulation thread threw an exception", e);
        }
    }
}