package de.renew.net;

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

import de.renew.net.event.PlaceEventListener;
import de.renew.net.event.PlaceEventListenerSet;
import de.renew.net.event.PlaceEventProducer;
import de.renew.simulatorontology.simulation.StepIdentifier;
import de.renew.util.Lock;


/**
 * A representation of a place instance.
 * <p>
 * This abstract class contains all functionality that is needed to display a place instance with the {@code remote} plugin.
 * It abstracts from its implementation, especially from the functionality that is used by the simulation.
 *
 * @author Michael Simon
 */
public abstract class PlaceInstance implements PlaceEventProducer, Serializable {
    @Serial
    private static final long serialVersionUID = 0L;

    /** The NetInstance this resides in. */
    protected NetInstance _netInstance;
    /** The Place this is an instance of. */
    protected Place _place;

    /**
     * The lock that controls access to this place instance.
     */
    public transient Lock _lock = new Lock();

    /**
     * The listeners that want to be informed about every update
     * of the current marking.
     **/
    protected transient PlaceEventListenerSet _listeners = new PlaceEventListenerSet();

    /**
     * Constructs a new PlaceInstance based on a NetInstance and a Place.
     *
     * @param netInstance the NetInstance the new PlaceInstance resides in
     * @param place the Place the new PlaceInstance is an instance of
     */
    protected PlaceInstance(NetInstance netInstance, Place place) {
        _netInstance = netInstance;
        _place = place;
    }

    /**
     * Returns the NetInstance that this PlaceInstance resides in.
     *
     * @return the NetInstance that this PlaceInstance resides in
     */
    public NetInstance getNetInstance() {
        return _netInstance;
    }

    /**
     * Returns the Place that this PlaceInstance is an instance of.
     *
     * @return the Place that this PlaceInstance is an instance of
     */
    public Place getPlace() {
        return _place;
    }

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

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

    /**
     * Returns the set of currently untested tokens.
     *
     * @return the set of currently untested tokens
     */
    public abstract Set<Object> getDistinctTokens();

    /**
     * Returns the set of currently testable tokens.
     *
     * @return the set of currently testable tokens
     */
    public abstract Set<Object> getDistinctTestableTokens();

    /**
     * Returns the number of tokens that reside in this place and are not currently tested by a transition.
     *
     * @return the number of tokens in this place that are not currently tested by a transition
     */
    public abstract int getNumberOfTokens();

    /**
     * Return the number of tokens that are currently tested
     * by a transition, but that reside in this place.
     * We do not count multiple tests on a single token as
     * multiple tokens.
     *
     * @return the number of tokens in this place that are currently tested by a transition
     */
    public abstract int getNumberOfTestedTokens();

    /**
     * Returns whether there are no tokens in this PlaceInstance.
     *
     * @return whether there are no tokens in this PlaceInstance
     */
    public boolean isEmpty() {
        return getNumberOfTokens() + getNumberOfTestedTokens() == 0;
    }

    /**
     * Returns how often a given token exists as part of the untested tokens that reside in this place.
     *
     * @param token the token to count
     * @return how often this PlaceInstance contains the given token
     */
    public abstract int getTokenCount(Object token);

    /**
     * Returns the set of times when a given token is free (residing in the PlaceInstance and not tested
     * by a Transition).
     *
     * @param token the token whose TimeSet to get
     * @return the TimeSet representing the times when a given token is free
     */
    public abstract TimeSet getFreeTimeSet(Object token);

    // Here consider only already tested tokens.
    /**
     * Checks whether a given token resides in this place and is currently tested by a transition.
     *
     * @param token the token to check for
     * @return whether a given token resides in this place and is currently tested by a transition
     */
    public abstract boolean containsTestedToken(Object token);

    /**
     * The associated net instance will call this method while
     * its creation is confirmed and trace messages are printed.
     * <p>
     * I will notify the database about all tokens that I kept during
     * my initialization, if the tokens must be available early.
     * <p>
     * You must call this method at most once.
     * <p>
     * In fact, this method is rather a kludge in the sense
     * that transaction support should not be activated
     * at all if a net requests early confirmation.
     */
    void earlyConfirmation() {
        // Do nothing.
    }

    /**
     * My net will call this method while its creation is traced.
     */
    void earlyConfirmationTrace(StepIdentifier stepIdentifier) {
        // Do nothing.
    }

    /**
     * This method is called by the {@link #_netInstance} late during its confirmation.
     *
     * @param stepIdentifier the current simulation step
     */
    void lateConfirmation(StepIdentifier stepIdentifier) {
        // Do nothing.
    }

    /**
     * Deserialization method, behaves like default readObject
     * method except restoring additional transient fields.
     * Creates new lock object.
     *
     * @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(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        _lock = new Lock();
        _listeners = new PlaceEventListenerSet();
    }
}
