package de.renew.net;

import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable;

import de.renew.engine.searcher.DeltaSet;
import de.renew.engine.searcher.DeltaSetFactory;
import de.renew.engine.searcher.Searcher;
import de.renew.engine.thread.SimulationThreadPool;


/**
 * A token reserver is responsible for simulating
 * the state of several place instances that would occur if
 * a number of token removals and tests was done.
 */
public class TokenReserver implements DeltaSet {
    private static final Factory FACTORY = new Factory();

    private static class Factory implements DeltaSetFactory {
        @Override
        public String getCategory() {
            return "de.renew.nets.TokenReserver";
        }

        @Override
        public DeltaSet createDeltaSet() {
            return new TokenReserver();
        }
    }

    /**
     * Returns a {@code TokenReserver} using the given {@code Searcher}.
     *
     * @param searcher the Searcher to get a {@code TokenReserver} for
     * @return a {@code TokenReserver} using {@code searcher}
     */
    public static TokenReserver getInstance(Searcher searcher) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        return (TokenReserver) searcher.getDeltaSet(FACTORY);
    }

    private Hashtable<SimulatablePlaceInstance, TokenReservation> _reservations = new Hashtable<>();

    TokenReserver() {}

    /**
     * Removes all reservations made on the TokenReserver.
     */
    public void reset() {
        // In theory, a reservations.clear() should suffice
        // at this point. For some entirely obscure point, however,
        // the Java garbage collector does not like it.
        // This is annoying.
        _reservations = new Hashtable<SimulatablePlaceInstance, TokenReservation>();
    }

    @Override
    public double computeEarliestTime() {
        double result = 0;
        Enumeration<TokenReservation> enumeration = _reservations.elements();
        while (enumeration.hasMoreElements()) {
            TokenReservation reservation = enumeration.nextElement();
            double time = reservation.computeEarliestTime();
            if (time > result) {
                result = time;
            }
        }
        return result;
    }

    private TokenReservation getReservation(SimulatablePlaceInstance place) {
        if (_reservations.containsKey(place)) {
            return _reservations.get(place);
        } else {
            TokenReservation reservation = new TokenReservation(place);
            _reservations.put(place, reservation);
            return reservation;
        }
    }

    private void disposeReservation(PlaceInstance place) {
        TokenReservation reservation = _reservations.get(place);
        if (reservation.isRemovable()) {
            _reservations.remove(place);
        }
    }

    /**
     * Checks if, considering the current reservations on the TokenReserver, a given place contains a removable
     * token with a given delay.
     *
     * @param place the place to check for removable tokens
     * @param token the token to check for in the place
     * @param delay the delay after which the token should be removable
     * @return whether the place contains the given token and it is removable
     */
    public synchronized boolean containsRemovableToken(
        SimulatablePlaceInstance place, Object token, double delay)
    {
        TokenReservation reservation = getReservation(place);
        boolean result = reservation.containsRemovableToken(token, delay);
        disposeReservation(place);
        return result;
    }

    /**
     * Checks if, considering the current reservations on the TokenReserver, a given place contains a testable token.
     *
     * @param place the place to check for removable tokens
     * @param token the token to check for in the place
     * @return whether the place contains the given token and it is removable
     */
    public synchronized boolean containsTestableToken(
        SimulatablePlaceInstance place, Object token)
    {
        TokenReservation reservation = getReservation(place);
        boolean result = reservation.containsTestableToken(token);
        disposeReservation(place);
        return result;
    }

    /**
     * Changes the reservation on a given place such that a given token is removed at a given time.
     *
     * @param place the place to remove the token from
     * @param token the token to remove
     * @param time the time at which to remove the token
     * @return whether removing the token was successful
     */
    public synchronized boolean removeToken(
        SimulatablePlaceInstance place, Object token, double time)
    {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        TokenReservation reservation = getReservation(place);
        boolean result = reservation.removeToken(token, time);
        disposeReservation(place);
        return result;
    }

    /**
     * Tests the reservation for a given place for a given token.
     *
     * @param place the place to test for the token
     * @param token the token to test for
     * @return the test result
     */
    public synchronized boolean testToken(SimulatablePlaceInstance place, Object token) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        TokenReservation reservation = getReservation(place);
        boolean result = reservation.testToken(token);
        disposeReservation(place);
        return result;
    }

    /**
     * Changes the reservation of a given place such that a given removed token is no longer removed from it.
     *
     * @param place the place to undo the token removal on
     * @param token the token whose removal should be undone
     * @param time the time at which the removal is undone
     */
    public synchronized void unremoveToken(
        SimulatablePlaceInstance place, Object token, double time)
    {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        TokenReservation reservation = getReservation(place);
        reservation.unremoveToken(token, time);
        disposeReservation(place);
    }

    /**
     * Undoes the testing of the reservation for a given place for a given token.
     *
     * @param place the place on which to undo the testing
     * @param token the token to un-test
     */
    public synchronized void untestToken(SimulatablePlaceInstance place, Object token) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        TokenReservation reservation = getReservation(place);
        reservation.untestToken(token);
        disposeReservation(place);
    }

    /**
     * Returns the tokens on a given place that are removable under the current reservation.
     * At least those tokens that are unifiable with a given pattern are returned.
     *
     * @param place the place whose removable tokens should be returned
     * @param pattern at least the tokens unifiable with this pattern should be returned
     * @return the tokens on a given place that are removable and match the pattern
     */
    public synchronized Collection<Object> getRemovableTokens(
        SimulatablePlaceInstance place, Object pattern)
    {
        TokenReservation reservation = getReservation(place);
        Collection<Object> result = reservation.getRemovableTokens(pattern);
        disposeReservation(place);
        return result;
    }

    /**
     * Returns the tokens on a given place that are testable under the current reservation.
     * At least those tokens that are unifiable with a given pattern are returned.
     *
     * @param place the place whose testable tokens should be returned
     * @param pattern at least the tokens unifiable with this pattern should be returned
     * @return the tokens on a given place that are testable  and match the pattern
     */
    public synchronized Collection<Object> getTestableTokens(
        SimulatablePlaceInstance place, Object pattern)
    {
        TokenReservation reservation = getReservation(place);
        Collection<Object> result = reservation.getTestableTokens(pattern);
        disposeReservation(place);
        return result;
    }
}