package de.renew.simulatorontology.shadow;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serial;
import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

import de.renew.simulatorontology.serialisation.SerialisationListener;
import de.renew.simulatorontology.serialisation.SerialisationListenerRegistry;


/**
 * This class is responsible for creating {@code ShadowNet} instances that contain
 * {@link ShadowNetElement} objects and belong to a {@link ShadowNetSystem}.
 */
public class ShadowNet implements java.io.Serializable {
    /* Serialization support. */
    @Serial
    private static final long serialVersionUID = 5180248532101141649L;
    /** The context of this {@code ShadowNet}. */
    private transient Object _context = null;
    /** The elements of this {@code ShadowNet}. */
    private final Set<ShadowNetElement> _elements;
    /** The name of this {@code ShadowNet}. */
    private String _name;
    /** The {@code ShadowNetSystem} this shadow net belongs to. */
    private ShadowNetSystem _netSystem;

    /**
     * Creates a new {@code ShadowNet} with the given name and adds it to the
     * given {@code ShadowNetSystem}.
     *
     * @param name the name of the shadow net
     * @param netSystem the system the shadow net belongs to
     */
    public ShadowNet(String name, ShadowNetSystem netSystem) {
        _elements = new HashSet<>();
        _name = name;
        _netSystem = netSystem;
        netSystem.add(this);
    }

    /**
     * Gets the {@code ShadowNetSystem} this net is a part of.
     *
     * @return the {@code ShadowNetSystem} this net is a part of.
     */
    public ShadowNetSystem getShadowNetSystem() {
        return _netSystem;
    }

    /**
     * Sets the name of this {@code ShadowNet}.
     *
     * @param name the name to set for the shadow net
     */
    public void setName(String name) {
        _name = name;
    }

    /**
     * Returns the name of this {@code ShadowNet}.
     *
     * @return the name of this {@code ShadowNet}
     */
    public String getName() {
        return _name;
    }

    /**
     * Returns an unmodifiable set containing the current elements of this {@code ShadowNet}.
     *
     * @return an unmodifiable set containing the current elements of this {@code ShadowNet}
     */
    public Set<ShadowNetElement> elements() {
        return Collections.unmodifiableSet(_elements);
    }

    /**
     * Removes this shadow net from the {@link ShadowNetSystem} it belongs to.
     */
    public void discard() {
        _netSystem.remove(this);
    }

    /**
     * Inserts this shadow net into another shadow net system. This
     * immediately implies removal from the previous net system.
     * If the old and the new net system use different compilers, the
     * net-specific compiler attribute is modified to retain the compiler
     * assignment for the net.
     * <p>
     * This method should not be executed after compilation of the
     * shadow net: The results and relations to the compiled net
     * and other shadow nets of the previous net system are not
     * defined.
     * </p>
     *
     * @param newNetSystem the new shadow net system this net should be switched to
     **/
    public void switchNetSystem(ShadowNetSystem newNetSystem) {
        // Switch the net system.
        _netSystem.remove(this);
        _netSystem = newNetSystem;
        _netSystem.add(this);
    }

    void add(ShadowNetElement element) {
        _elements.add(element);
    }

    void remove(ShadowNetElement element) {
        _elements.remove(element);
        element.setContext(null);
    }

    /**
     * Getter for the context.
     *
     * @return the context
     */
    public Object getContext() {
        return _context;
    }

    /**
     * Setter for the context.
     *
     * @param context the new context
     */
    public void setContext(Object context) {
        _context = context;
    }

    @Serial
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        Optional<SerialisationListener<ShadowNet>> listener =
            SerialisationListenerRegistry.getListener(ShadowNet.class);
        if (listener.isPresent()) {
            listener.get().onRead(this, in);
        }
    }

    @Serial
    private void writeObject(ObjectOutputStream in) throws IOException, ClassNotFoundException {
        in.defaultWriteObject();
        Optional<SerialisationListener<ShadowNet>> listener =
            SerialisationListenerRegistry.getListener(ShadowNet.class);
        if (listener.isPresent()) {
            listener.get().onWrite(this, in);
        }
    }

    @Override
    public String toString() {
        return "ShadowNet \"" + _name + "\" (" + _elements.size() + " elements)";
    }
}