package de.renew.remote;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Properties;

import de.renew.plugin.PropertyHelper;
import de.renew.simulatorontology.shadow.ShadowCompilationResult;
import de.renew.simulatorontology.simulation.SimulationEnvironment;
import de.renew.simulatorontology.simulation.SimulatorExtension;


/**
 * This simulator extension can provide RMI access to the running
 * simulation. It is always registered within the Renew Simulator
 * plugin. But to be activated, one of the properties
 * <code>de.renew.remote.serverClass</code> or
 * <code>de.renew.remote.enable</code> needs to be set.
 *
 * @author Michael Duvigneau
 * @since Renew 2.0
 **/
public class RemoteExtension implements SimulatorExtension {

    /**
     * A static logger instance from the Apache Log4j framework used to log
     * events, messages, and errors specifically for the {@link RemoteExtension} class.
     * This logger is initialized with the class name "RemoteExtension" and is designed
     * to facilitate logging at various levels (e.g., debug, info, warn, error).
     * The use of this logger can help monitor and debug the behavior and interactions
     * related to the remote extension of simulations in the Renew framework.
     */
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(RemoteExtension.class);

    /**
     * A reference to the current simulation environment. Is
     * <code>null</code> whenever no simulation is set up.
     **/
    private SimulationEnvironment _currentEnvironment = null;

    /**
     * A reference to the current remote simulation interface server.
     * Is <code>null</code> whenever no simulation with enabled
     * remote access is set up.
     **/
    private Server _currentServer = null;

    /**
     * The URL under which the current server had been registered
     * with the RMI naming service.
     **/
    private String _currentServerUrl = null;

    /**
     * The name of the property to get the server implementation
     * class from. The name is: {@value}.
     **/
    public static final String CLASS_PROP_NAME = "de.renew.remote.serverClass";

    /**
     * The name of the property to check whether remote access
     * should be enabled. The name is: {@value}.
     **/
    public static final String ENABLE_PROP_NAME = "de.renew.remote.enable";

    /**
     * The name of the property to get the server name under
     * which the remote access is announced. The name is: {@value}.
     **/
    public static final String NAME_PROP_NAME = "de.renew.remote.publicName";

    /**
     * The default server implementation to use, if not
     * configured by a property. The class is: {@link ServerImpl}.
     **/
    public static final Class<?> DEFAULT_SERVER_CLASS = ServerImpl.class;


    /**
     * Describe <code>simulationSetup</code> method here.
     *
     * @param env a <code>SimulationEnvironment</code> value
     */
    @Override
    public void simulationSetup(SimulationEnvironment env) {
        // The simulator plugin enforces the following conditions:
        assert _currentEnvironment == null
            : "The last simulation has not been cleaned up properly.";
        assert _currentServer == null : "The last remote server has not been cleaned up properly.";
        assert _currentServerUrl == null
            : "The last remote server has not been cleaned up properly.";

        this._currentEnvironment = env;

        // Interpret the properties.
        Properties props = env.getProperties();


        // Query for the general enabledness of this extension. It
        // can also be implicitly enabled by any other property.
        boolean enableRemote = PropertyHelper.getBoolProperty(props, ENABLE_PROP_NAME);

        if (enableRemote) {
            RemotePlugin.configureInterface();


            // Query for the server implementation class to use.
            Class<?> serverClass =
                PropertyHelper.getClassProperty(props, CLASS_PROP_NAME, Server.class);

            // Query for the public name the server should use.
            String serverName = props.getProperty(NAME_PROP_NAME);

            // Set the defaults for unset properties.
            if (serverClass == null) {
                serverClass = ServerImpl.class;
            }
            if (serverName == null) {
                serverName = Server.DEFAULT_SERVER_NAME;
            }

            this._currentServer = createServer(serverClass);

            if (_currentServer != null) {
                props.setProperty(NAME_PROP_NAME, serverName);
                props.setProperty(CLASS_PROP_NAME, serverClass.getName());
                props.setProperty(ENABLE_PROP_NAME, "true");

                this._currentServerUrl = ServerImpl.bindServer(_currentServer, serverName, true);
            }
        } else {
            props.remove(NAME_PROP_NAME);
            props.remove(CLASS_PROP_NAME);
            props.setProperty(ENABLE_PROP_NAME, "false");
        }
    }

    /**
     *
     **/
    private Server createServer(Class<?> serverClass) {
        Server server = null;
        LOGGER.debug("RemoteExtension: Using server class " + serverClass.getName() + "...");
        try {
            Constructor<?> constructor =
                serverClass.getConstructor(new Class<?>[] { SimulationEnvironment.class });
            server = (Server) constructor.newInstance(new Object[] { _currentEnvironment });
            LOGGER.debug("RemoteExtension: Created server.");
        } catch (NoSuchMethodException e) {
            LOGGER.error("RemoteExtension: Could not create remote server: " + e);
        } catch (SecurityException e) {
            LOGGER.error("RemoteExtension: Could not create remote server: " + e);
        } catch (IllegalAccessException e) {
            LOGGER.error("RemoteExtension: Could not create remote server: " + e);
        } catch (InstantiationException e) {
            LOGGER.error("RemoteExtension: Could not create remote server: " + e);
        } catch (InvocationTargetException e) {
            LOGGER.error("RemoteExtension: Server initialisation failed: ");
            LOGGER.error(e.getTargetException().getMessage(), e.getTargetException());
        }
        return server;
    }


    /**
     * Describe <code>simulationTerminated</code> method here.
     **/
    @Override
    public void simulationTerminated() {
        if (_currentServerUrl != null) {
            ServerImpl.unbindServer(_currentServerUrl);
            LOGGER.debug("RemoteExtension: Server disconnected from registry.");
            _currentServerUrl = null;
        }
        _currentServer = null;
        _currentEnvironment = null;
    }

    @Override
    public void simulationTerminating() {
        //Nothing to do ?
    }

    /**
     * Does nothing.
     **/
    @Override
    public void netsCompiled(ShadowCompilationResult result) {}
}