package de.renew.net;

import java.io.IOException;
import java.io.Serial;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

import de.renew.engine.thread.SimulationThreadPool;
import de.renew.net.event.TransitionEventListener;
import de.renew.net.event.TransitionEventListenerSet;
import de.renew.net.event.TransitionEventProducer;
import de.renew.net.inscription.TransitionInscription;
import de.renew.net.inscription.transition.ManualInscription;
import de.renew.net.inscription.transition.UplinkInscription;
import de.renew.util.RenewObjectOutputStream;


/**
 * I represent a transition with all its semantic information,
 * but without any state. I can have a name, an uplink,
 * and a set of inscriptions. My main job is to store
 * all this information. I do not actually perform any
 * useful services.
 */
public class Transition implements Serializable, TransitionEventProducer {
    @Serial
    private static final long serialVersionUID = -7955261691016471176L;

    /**
     * The net element id.
     * @serial
     **/
    private final NetElementID _id;

    /**
     * My name.
     **/
    private final String _name;

    /**
     * A description of what I do.
     */
    private String _comment;

    /**
     * true, if my instances should output a trace message when they
     * occur. This is the default.
     **/
    private boolean _trace;

    /**
     * My uplink or null, if I am spontaneous.
     **/
    private UplinkInscription _uplink;

    /**
     * A set of inscriptions. I expect all inscription objects to be
     * of the type TransitionInscription.
     *
     * @see TransitionInscription
     **/
    private final Set<TransitionInscription> _inscriptions;

    // ---- TransitionEvent Handling ----------------------------------------


    /**
     * The listeners to notify if transition events occur.
     * <p>
     * This object is used to synchronize listener additions/removals
     * and notification.
     **/
    private transient TransitionEventListenerSet _listeners = new TransitionEventListenerSet();

    /**
     * I (a transition) am created as a spontaneous transition.
     * Later on, inscriptions may be added, even an uplink.
     * I am automatically registered at the net as a
     * net element.
     *
     * @param net the net to which this transition should be added
     * @param name the name for this transition
     * @param id the ID for this transition
     */
    public Transition(Net net, String name, NetElementID id) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        _id = id;
        _name = name;


        // Trace actions.
        _trace = true;


        // Clear the uplink: no uplink is the default.
        _uplink = null;


        // Initialize the array for the inscriptions.
        _inscriptions = new HashSet<TransitionInscription>();


        // Notify the net that we are done and want to be
        // inserted into the list of transitions.
        net.add(this);
    }

    /**
     * Returns the net element id.
     *
     * @return The net element id
     */
    public NetElementID getID() {
        return _id;
    }

    /**
     * What's my name?
     *
     * @return my name
     **/
    public String getName() {
        return _name;
    }

    /**
     * I return my name as my string representation.
     * My net instances use my name as a prefix for their name.
     *
     * @return my name
     **/
    @Override
    public String toString() {
        return _name;
    }

    /**
     * Switch my trace flag on or off.
     *
     * @param trace true, if tracing is desired
     **/
    public void setTrace(boolean trace) {
        _trace = trace;
    }

    /**
     * Am I being traced?
     *
     * @return true, if my trace flag is set
     **/
    public boolean getTrace() {
        return _trace;
    }

    /**
     * Am I spontaneous?
     *
     * @return whether I am spontaneous, meaning I neither have an uplink nor am manual
     **/
    public boolean isSpontaneous() {
        return _uplink == null && !_inscriptions.contains(ManualInscription.getInstance());
    }

    /**
     * Checks whether the Transition is inscribed with an uplink to a given channel name.
     *
     * @param channel the name of the synchronous channel to check for
     * @return whether the Transition has an uplink inscription to a channel with the given name
     */
    public boolean listensToChannel(String channel) {
        return _uplink != null && _uplink.getName().equals(channel);
    }

    /**
     * Here I extract my uplink, if any, from my inscriptions.
     * If there is more than one uplink, it is undetermined which
     * uplink I will use. This method is called after every
     * update to my set of inscriptions.
     **/
    private void checkUplink() {
        // Maintain the count of uplinks and select an uplink
        // from the set of inscriptions. By allowing multiple inscriptions
        // the embedding into a graphical editor becomes easier.
        // On the other hand, the transition might get into
        // an inconsistent state. We try to make it as simulatable as
        // possible, however.
        _uplink = null;
        for (Object inscription : _inscriptions) {
            if (inscription instanceof UplinkInscription) {
                _uplink = (UplinkInscription) inscription;
            }
        }
    }

    /**
     * Give me another inscription. After inserting
     * the inscriptions, I will recheck my uplink.
     *
     * @param transitionInscription the inscription that should be added
     **/
    public void add(TransitionInscription transitionInscription) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        if (!_inscriptions.contains(transitionInscription)) {
            _inscriptions.add(transitionInscription);
            checkUplink();
        }
    }

    /**
     * Remove one my current inscriptions. Check if an
     * uplink is still provided.
     *
     * @param transitionInscription the inscription that should be removed
     **/
    public void remove(TransitionInscription transitionInscription) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        if (_inscriptions.contains(transitionInscription)) {
            _inscriptions.remove(transitionInscription);
            checkUplink();
        }
    }

    /**
     * Serialization method, behaves like default writeObject
     * method except using the domain trace feature, if the
     * output is a RenewObjectOutputStream.
     * See also: {@link de.renew.util.RenewObjectOutputStream}
     *
     * @param out the stream to serialize to
     * @throws IOException if an error occurs while writing
     **/
    @Serial
    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        RenewObjectOutputStream rOut = null;
        if (out instanceof RenewObjectOutputStream) {
            rOut = (RenewObjectOutputStream) out;
        }
        if (rOut != null) {
            rOut.beginDomain(this);
        }
        out.defaultWriteObject();
        if (rOut != null) {
            rOut.endDomain(this);
        }
    }

    /**
     * Deserialization method, behaves like default readObject
     * method except restoring additional transient fields.
     *
     * @param in the stream to read objects from
     * @throws IOException if an error occurs while reading
     * @throws ClassNotFoundException if an object is read whose class cannot be found
     **/
    @Serial
    private void readObject(java.io.ObjectInputStream in)
        throws IOException, ClassNotFoundException
    {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        in.defaultReadObject();
        _listeners = new TransitionEventListenerSet();
    }

    @Override
    public void addTransitionEventListener(TransitionEventListener listener) {
        _listeners.addTransitionEventListener(listener);
    }

    @Override
    public void removeTransitionEventListener(TransitionEventListener listener) {
        _listeners.removeTransitionEventListener(listener);
    }

    TransitionEventListenerSet getListenerSet() {
        return _listeners;
    }

    /**
     * Sets a description of what the Transition does.
     *
     * @param comment a new description of what the Transition does
     */
    public void setComment(String comment) {
        _comment = comment;

    }

    /**
     * Returns what the Transition does.
     *
     * @return what the Transition does
     */
    public String getComment() {
        return _comment;
    }

    /**
     * Returns the inscriptions on the Transition.
     *
     * @return the inscriptions on the Transition
     */
    public Set<TransitionInscription> inscriptions() {
        return _inscriptions;
    }

    /**
     * check if a transition is manual
     *
     * @param transition to check
     * @return {@code true} if the transition is manual, {@code false}
     */
    public static boolean isManual(Transition transition) {
        return transition._inscriptions.contains(ManualInscription.getInstance());
    }

    /**
     * Get my uplink, if I have one.
     *
     * @return this {@code Transition}'s uplink or {@code null}, if it has none
     */
    public UplinkInscription getUplink() {
        return _uplink;
    }

    /**
     * Getter for {@code inscriptions}.
     *
     * @return the {@code inscriptions}.
     */
    Set<TransitionInscription> getInscriptions() {
        return _inscriptions;
    }
}