package de.renew.shadowcompiler;

import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;

import de.renew.net.Net;
import de.renew.net.NetLookup;
import de.renew.net.Place;
import de.renew.net.Transition;
import de.renew.simulatorontology.shadow.ShadowPlace;
import de.renew.simulatorontology.shadow.ShadowTransition;


/**
 * This is simply a typesafe hash table to identify the
 * compiled objects that arise from shadow objects.
 **/
public class ShadowLookup {
    private final Hashtable<String, Net> _netMap;
    private final HashSet<String> _namesOfNewlyCompiledNets;
    private final Hashtable<ShadowPlace, Place> _placeMap;
    private final Hashtable<ShadowTransition, Transition> _transitionMap;
    private final Hashtable<String, ShadowLookupExtension> _extensions;

    /**
     * Creates a new, empty {@code ShadowLookup}.
     */
    public ShadowLookup() {
        _netMap = new Hashtable<>();
        _namesOfNewlyCompiledNets = new HashSet<>();
        _placeMap = new Hashtable<>();
        _transitionMap = new Hashtable<>();
        _extensions = new Hashtable<>();
    }

    /**
     * Adds the given name as the key with the given net as the value to the
     * {@code ShadowLookup}.
     *
     * @param name the name of the net to add as the key
     * @param net the net to add as the value
     */
    public void setNet(String name, Net net) {
        _netMap.put(name, net);
    }

    /**
     * Returns the net to which the given name is mapped, or {@code null}
     * if there is no net with that name in this {@code ShadowLookup}.
     *
     * @param name the name of the net to retrieve
     * @return the net to which the given name is mapped, or {@code null}
     *         if there is no net with that name in the lookup
     */
    public Net getNet(String name) {
        return _netMap.get(name);
    }

    /**
     * Returns an {@code Enumeration} of all the names of the nets in this
     * {@code ShadowLookup}.
     *
     * @return an {@code Enumeration} of all the names of the nets in this
     *         {@code ShadowLookup}
     */
    public Enumeration<String> allNetNames() {
        return _netMap.keys();
    }

    /**
     * Returns an {@code Iterator} over all newly compiled nets in this
     * {@code ShadowLookup}.
     * <p>
     * A newly compiled net is any net where at least one place or transition
     * has been added to this {@code ShadowLookup} via {@link #set(ShadowPlace, Place)}
     * or {@link #set(ShadowTransition, Transition)}.
     *
     * @return an {@code Iterator} over all newly compiled nets in this
     *         {@code ShadowLookup}
     */
    public Iterator<String> allNewlyCompiledNetNames() {
        return _namesOfNewlyCompiledNets.iterator();
    }

    /**
     * Adds the given {@code ShadowPlace} as the key with the corresponding
     * {@code Place} as the value to this {@code ShadowLookup}.
     * <p>
     * Also adds the net this place belongs to the set of newly compiled nets.
     *
     * @param element the {@code ShadowPlace} to add to this lookup
     * @param place the corresponding {@code Place} to add to this lookup
     */
    public void set(ShadowPlace element, Place place) {
        _placeMap.put(element, place);
        _namesOfNewlyCompiledNets.add(element.getNet().getName());
    }

    /**
     * Returns the {@code Place} to which the given {@code ShadowPlace} is mapped,
     * or {@code null} if that shadow place is not in the {@code ShadowLookup}.
     *
     * @param element the {@code ShadowPlace} to which wanted {@code Place} is mapped
     * @return the {@code Place} to which the given {@code ShadowPlace} is mapped,
     *         or {@code null} if that shadow place is not in the {@code ShadowLookup}
     */
    public Place get(ShadowPlace element) {
        return _placeMap.get(element);
    }

    /**
     * Returns an {@code Enumeration} of all the names of the places in this
     * {@code ShadowLookup}.
     *
     * @return an {@code Enumeration} of all the names of the places in this
     *         {@code ShadowLookup}
     */
    public Enumeration<ShadowPlace> allPlaces() {
        return _placeMap.keys();
    }

    /**
     * Adds the given {@code ShadowTransition} as the key with the corresponding
     * {@code Transition} as the value to this {@code ShadowLookup}.
     * <p>
     * The net that this transition belongs to is also added to the set of newly
     * compiled nets.
     *
     * @param element the {@code ShadowTransition} to add to this lookup
     * @param transition the corresponding {@code Transition} to add to this lookup
     */
    public void set(ShadowTransition element, Transition transition) {
        _transitionMap.put(element, transition);
        _namesOfNewlyCompiledNets.add(element.getNet().getName());
    }

    /**
     * Returns the {@code Transition} to which the given {@code ShadowTransition}
     * is mapped, or {@code null} if that shadow transition is not in the
     * {@code ShadowLookup}.
     *
     * @param element the {@code ShadowTransition} the wanted {@code Transition}
     *                is mapped to
     * @return the {@code Transition} to which the given {@code ShadowTransition}
     *         is mapped, or {@code null} if that shadow transition is not in the
     *         {@code ShadowLookup}
     */
    public Transition get(ShadowTransition element) {
        return _transitionMap.get(element);
    }

    /**
     * Returns an {@code Enumeration} of all the names of the transitions in this
     * {@code ShadowLookup}.
     *
     * @return an {@code Enumeration} of all the names of the transitions in this
     *         {@code ShadowLookup}
     */
    public Enumeration<ShadowTransition> allTransitions() {
        return _transitionMap.keys();
    }

    /**
     * Return an extension to this shadow lookup
     * for the extension category specified by
     * the given factory. If no extension of said
     * category is currently available, use the
     * factory to create an instance.
     * On multiple calls with factories for the same
     * category, this method will consistently
     * return the same object.
     *
     * @param factory the factory used to identify the extension category and
     *                create the extension if needed
     * @return the {@code ShadowLookupExtension} for the specified category
     **/
    public synchronized ShadowLookupExtension getShadowLookupExtension(
        ShadowLookupExtensionFactory factory)
    {
        ShadowLookupExtension extension = _extensions.get(factory.getCategory());
        if (extension == null) {
            extension = factory.createExtension();
            _extensions.put(factory.getCategory(), extension);
        }
        return extension;
    }


    /**
     * Publicly announces all compiled nets. This has to be done
     * at the end of the compilation process.
     **/
    public void makeNetsKnown() {
        new NetLookup().makeNetsKnown(_netMap.values());
    }

    /**
     * Returns whether this {@code ShadowLookup} contains newly compiled nets.
     *
     * @return {@code true} if this {@code ShadowLookup} contains newly
     *         compiled nets, otherwise {@code false}
     */
    public boolean containsNewlyCompiledNets() {
        return !_namesOfNewlyCompiledNets.isEmpty();
    }
}