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.PlaceEventListener;
import de.renew.net.event.PlaceEventListenerSet;
import de.renew.net.event.PlaceEventProducer;
import de.renew.unify.Impossible;
import de.renew.util.RenewObjectOutputStream;


/**
 * A {@code Place} represents a place with all its semantic information, but without any state.
 * It mainly stores a name and a set of inscriptions and does not actually perform any useful services.
 */
public class Place implements Serializable, PlaceEventProducer {
    @Serial
    private static final long serialVersionUID = 5661978821450640374L;
    /** Represents the {@link #_placetype place type} "Multi Set Place" (which uses a {@link MultisetPlaceInstance}) */
    public static final int MULTISETPLACE = 0;
    /** Represents the {@link #_placetype place type} "FIFO Place" (which uses a {@link FIFOPlaceInstance}) */
    public static final int FIFOPLACE = 1;

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

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

    /**
     * A description of me.
     */
    private String _comment;

    /**
     * true, if my instances should output a trace message when they
     * receive an initial token. This is the default.
     **/
    private boolean _trace;
    /**
     * My set of token source inscriptions.
     **/
    private final Set<TokenSource> _inscriptions;

    /**
     *  {@code placetype} contains the type of the place.
     */
    private int _placetype = MULTISETPLACE;

    // ---- PlaceEvent Handling ----------------------------------------


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

    /**
     * Constructs a new place based on a Net, a name and its NetElementID.
     *
     * @param net the Net that contains the Place
     * @param name the name of the Place
     * @param id the ID of the Place
     */
    public Place(Net net, String name, NetElementID id) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        _id = id;
        _name = name;

        _trace = true;
        _inscriptions = new HashSet<TokenSource>();

        net.add(this);
    }

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

    @Override
    public String toString() {
        return _name;
    }

    /**
     * Sets the place's {@link #_placetype}.
     *
     * @param behaviour either {@link #MULTISETPLACE} or {@link #FIFOPLACE}
     */
    public void setBehaviour(int behaviour) {
        _placetype = behaviour;
    }

    /**
     * Creates a PlaceInstance of this Place.
     *
     * @param netInstance the NetInstance the PlaceInstance is a part of
     * @param wantInitialTokens true, if initial marking should be calculated
     * @return a new PlaceInstance of this Place
     * @throws Impossible if unification fails on the initial tokens of this instance
     *                    (see {@link SimulatablePlaceInstance}).
     */
    protected PlaceInstance makeInstance(NetInstance netInstance, boolean wantInitialTokens)
        throws Impossible
    {
        return switch (_placetype) {
            case MULTISETPLACE -> new MultisetPlaceInstance(netInstance, this, wantInitialTokens);
            case FIFOPLACE -> new FIFOPlaceInstance(netInstance, this, wantInitialTokens);
            default -> throw new RuntimeException("Illegal place behaviour: " + _placetype);
        };
    }

    /**
     * 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;
    }

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

    /**
     * Returns the place type.
     * 
     * @return the place type
     **/
    int getPlacetype() {
        return _placetype;
    }

    /**
     * Give me another token source.
     *
     * @param tokenSource the token source to add to this {@code Place}
     */
    public void add(TokenSource tokenSource) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        _inscriptions.add(tokenSource);
    }

    /**
     * Remove one my current token sources.
     *
     * @param tokenSource the token source to remove from this {@code Place}
     */
    public void remove(TokenSource tokenSource) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        _inscriptions.remove(tokenSource);
    }

    /**
     * Receive my token sources.
     *
     * @return the {@code TokenSource}s of this {@code Place}
     */
    public Set<TokenSource> getTokenSources() {
        return _inscriptions;
    }

    /**
     * Do I have a TokenSource?
     *
     * @return whether this {@code Place} has a {@code TokenSource}
     */
    public boolean hasInitialTokens() {
        return !_inscriptions.isEmpty();
    }

    /**
     * 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 renewOut =
            (out instanceof RenewObjectOutputStream) ? (RenewObjectOutputStream) out : null;

        if (renewOut != null) {
            renewOut.beginDomain(this);
        }

        out.defaultWriteObject();

        if (renewOut != null) {
            renewOut.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 PlaceEventListenerSet();
    }

    @Override
    public void addPlaceEventListener(PlaceEventListener listener) {
        _listeners.addPlaceEventListener(listener);
    }

    @Override
    public void removePlaceEventListener(PlaceEventListener listener) {
        _listeners.removePlaceEventListener(listener);
    }

    /**
     * Gets the set of listeners to notify if place events occur.
     *
     * @return the set of listeners to notify if place events occur
     */
    public PlaceEventListenerSet getListenerSet() {
        return _listeners;
    }

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

    }

    /**
     * Returns the description of the Place.
     *
     * @return the description of the Place
     */
    public String getComment() {
        return _comment;
    }

    Set<TokenSource> getInscriptions() {
        return _inscriptions;
    }
}