package de.renew.remote;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import de.renew.engine.searcher.TriggerCollection;
import de.renew.engine.searcher.TriggerCollectionImpl;
import de.renew.engine.searcher.Triggerable;
import de.renew.engine.simulator.Binding;
import de.renew.engine.simulator.SimulatorHelper;
import de.renew.engine.thread.SimulationThreadPool;
import de.renew.net.NetElementID;
import de.renew.net.TransitionInstance;
import de.renew.net.event.FiringEvent;
import de.renew.net.event.TransitionEventListener;
import de.renew.simulatorontology.simulation.SimulationEnvironment;


/**
 * This class implements the <code>TransitionInstanceAccessor</code>
 * interface and nothing more than needed to implement it.
 * TransitionInstanceAccessorImpl.java
 * Created: Mon Jul 17  2000
 * @author Michael Duvigneau
 */
public class TransitionInstanceAccessorImpl extends ObjectAccessorImpl
    implements TransitionInstanceAccessor, TransitionEventListener, Triggerable
{
    /**
     * A constant logger instance specifically for the {@code TransitionInstanceAccessorImpl} class.
     * This logger is used for logging events, exceptions, and debugging information within
     * the context of the transition instance accessor's operations. The logger is derived
     * from the Apache Log4j framework and configured for the {@code TransitionInstanceAccessorImpl}
     * class to help trace program execution steps and identify issues efficiently.
     */
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(TransitionInstanceAccessorImpl.class);

    /**
     * The set of registered remote event listeners that want
     * to be informed about transition events.
     */
    private Set<RemoteEventListener> _firingListeners =
        Collections.synchronizedSet(new HashSet<RemoteEventListener>());

    /**
     * The set of registered triggerable forwarders that want
     * to be informed about search proposals.
     */
    private Set<TriggerableForwarder> _proposalListeners =
        Collections.synchronizedSet(new HashSet<TriggerableForwarder>());

    /**
     * The triggers of this triggerable.
     */
    private TriggerCollection _triggers = new TriggerCollectionImpl(this);

    /**
     * Creates a new transition instance accessor for the given transition
     * instance.
     *
     * @param transitionInstance the transition instance to access
     * @param environment the simulation environment that contains the transition instance
     * @exception java.rmi.RemoteException if an RMI failure occured.
     */
    public TransitionInstanceAccessorImpl(
        TransitionInstance transitionInstance, SimulationEnvironment environment)
        throws RemoteException
    {
        super(transitionInstance, environment);
    }

    /**
     * Registers the given remote event listener so that it will
     * receive future remote event messages concerning the accessed
     * transition instance.
     * <p>
     * If the listener is registered already, the additional
     * registration try is ignored. The <code>equals()</code>
     * method serves as indicator to allow the mechanism to
     * work also for remote objects.
     * </p>
     * <p>
     * This method is specified by the <code>RemoteEventProducer</code>
     * interface which is required by the <code>TransitionInstanceAccessor</code>
     * interface.
     * </p>
     *
     * @param listener the listener to register
     * @exception java.rmi.RemoteException if a RMI failure occured.
     */
    @Override
    public void addRemoteEventListener(RemoteEventListener listener) throws RemoteException {
        synchronized (_firingListeners) {
            // We register this accessor as TransitionEventListener
            // when adding the first remote event listener.
            if (_firingListeners.isEmpty()) {
                ((TransitionInstance) _object).addTransitionEventListener(this);
            }

            _firingListeners.add(listener);
        }
    }

    /**
     * {@inheritDoc}
     **/
    @Override
    public BindingAccessor[] findAllBindings(final TriggerableForwarder triggerableForwarder)
        throws RemoteException
    {
        final TransitionInstanceAccessorImpl impl = this;
        Future<BindingAccessor[]> future =
            SimulationThreadPool.getCurrent().submitAndWait(new Callable<BindingAccessor[]>()
            {
                @Override
                public BindingAccessor[] call() throws Exception {
                    synchronized (_proposalListeners) {
                        _proposalListeners.add(triggerableForwarder);
                    }

                    BindingAccessor[] bindings;
                    try {
                        _triggers.clear();
                        Collection<Binding> allBindings =
                            SimulatorHelper.findAllBindings((TransitionInstance) _object, impl);
                        Iterator<Binding> iter = allBindings.iterator();

                        bindings = new BindingAccessor[allBindings.size()];
                        for (int i = 0; iter.hasNext(); i++) {
                            bindings[i] = new BindingAccessorImpl(iter.next(), getEnvironment());
                        }
                    } catch (RemoteException e) {
                        LOGGER.error(e.getMessage(), e);
                        bindings = new BindingAccessor[0];
                    }

                    return bindings;
                }
            });
        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;

    }

    /**
     * {@inheritDoc}
     **/
    @Override
    public void forgetBindings(final TriggerableForwarder triggerableForwarder)
        throws RemoteException
    {
        SimulationThreadPool.getCurrent().executeAndWait(new Runnable() {
            @Override
            public void run() {
                synchronized (_proposalListeners) {
                    _proposalListeners.remove(triggerableForwarder);
                    if (_proposalListeners.isEmpty()) {
                        _triggers.clear();
                    }
                }
            }
        });
    }

    /**
     * {@inheritDoc}
     **/
    @Override
    public boolean fireOneBinding() throws RemoteException {
        Future<Boolean> future =
            SimulationThreadPool.getCurrent().submitAndWait(new Callable<Boolean>()
            {
                @Override
                public Boolean call() throws Exception {
                    boolean asynchronous = !getEnvironment().getSimulator().isSequential();
                    return ((TransitionInstance) _object).fireOneBinding(asynchronous);
                }
            });
        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;

    }

    /**
     * The common reaction to all transition events is to send a
     * remote event to all currently registered remote event
     * listeners.
     */
    private void fireRemoteEvent() {
        Future<Object> future =
            SimulationThreadPool.getCurrent().submitAndWait(new Callable<Object>()
            {
                @Override
                public Object call() throws Exception {
                    Iterator<RemoteEventListener> iterator;
                    RemoteEventListener listener;
                    synchronized (_firingListeners) {
                        iterator = _firingListeners.iterator();
                        while (iterator.hasNext()) {
                            listener = iterator.next();
                            try {
                                listener.update();
                            } catch (RemoteException e) {
                                LOGGER.error(
                                    "TransitionInstanceAccessor: Remote event to " + listener
                                        + " probably got lost due to " + e);
                            }
                        }
                    }
                    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);
        }

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

    /**
     * Converts the local transition event into a remote event and
     * forwards it to all remote listeners.
     */
    @Override
    public void firingComplete(FiringEvent event) {
        fireRemoteEvent();
    }

    /**
     * Converts the local transition event into a remote event and
     * forwards it to all remote listeners.
     */
    @Override
    public void firingStarted(FiringEvent event) {
        fireRemoteEvent();
    }

    /* This method is specified by the TransitionInstanceAccessor interface. */
    @Override
    public NetElementID getID() throws RemoteException {
        return ((TransitionInstance) _object).getTransition().getID();
    }

    /* This method is specified by the TransitionInstanceAccessor interface. */
    @Override
    public TransitionAccessor getTransition() throws RemoteException {
        return new TransitionAccessorImpl(
            ((TransitionInstance) _object).getTransition(), getEnvironment());
    }

    /**
     * Returns the transition instance, if the caller knows that
     * this is the local representation. This is required for setting breakpoints.
     * @return The transition instance.
     */
    public TransitionInstance getTransitionInstance() {
        return (TransitionInstance) _object;
    }

    /* This method is specified by the TransitionInstanceAccessor interface. */
    @Override
    public NetInstanceAccessor getNetInstance() throws RemoteException {
        return new NetInstanceAccessorImpl(
            ((TransitionInstance) _object).getNetInstance(), getEnvironment());
    }

    /**
     * Returns whether the transition instance is currently firing
     * at least one occurrance.
     * @return Whether it is firing.
     * @exception java.rmi.RemoteException if an RMI failure occured.
     */
    @Override
    public boolean isFiring() throws RemoteException {
        return ((TransitionInstance) _object).isFiring();
    }

    /**
     * Called when a trigger proposes a new search.
     */
    @Override
    public synchronized void proposeSearch() {
        Future<Object> future =
            SimulationThreadPool.getCurrent().submitAndWait(new Callable<Object>()
            {
                @Override
                public Object call() throws Exception {
                    List<TriggerableForwarder> triggerableForwarders;
                    synchronized (_proposalListeners) {
                        triggerableForwarders =
                            new ArrayList<TriggerableForwarder>(_proposalListeners);
                        _proposalListeners.clear();
                        _triggers.clear();
                    }
                    for (Iterator<TriggerableForwarder> i = triggerableForwarders.iterator(); i
                        .hasNext();) {
                        TriggerableForwarder forwarder = null;
                        try {
                            forwarder = i.next();
                            forwarder.proposeSearch();
                        } catch (RemoteException e) {
                            LOGGER.error(
                                "TransitionInstanceAccessor: Search proposal to " + forwarder
                                    + " probably got lost due to " + e);
                        }
                    }
                    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);
        }
    }

    /**
     * Unregisters the given remote event listener so that it will
     * not receive future remote event messages from this transition
     * instance accessor.
     * <p>
     * All listeners that equal the specified one will be
     * unregistered. If the listener was not registered, the
     * unregistration try is ignored.
     * </p>
     * <p>
     * This method is specified by the <code>RemoteEventProducer</code>
     * interface which is required by the <code>TransitionInstanceAccessor</code>
     * interface.
     * </p>
     *
     * @param listener the listener to unregister
     * @exception java.rmi.RemoteException if a RMI failure occured.
     */
    @Override
    public void removeRemoteEventListener(RemoteEventListener listener) throws RemoteException {
        synchronized (_firingListeners) {
            _firingListeners.remove(listener);


            // We unregister this accessor as TransitionEventListener
            // when removing the last remote event listener.
            if (_firingListeners.isEmpty()) {
                ((TransitionInstance) _object).removeTransitionEventListener(this);
            }
        }
    }

    /**
     * Returns the triggers of this triggerable.
     * @return The triggers of this triggerable.
     */
    @Override
    public TriggerCollection triggers() {
        return _triggers;
    }

    /**
     * This class does not use synchronous notifications, because
     * it might lead to deadlocks for some applications. Asynchronous
     * notifications are always safe, although they might arrive slightly
     * later.
     *
     * @return a <code>boolean</code> value
     */
    @Override
    public boolean wantSynchronousNotification() {
        return false;
    }
}