package de.renew.net;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import de.renew.engine.thread.SimulationThreadPool;


/**
 * A token reservation keeps track of the number of tokens
 * that would have to be tested and removed from a single
 * place instance.
 * <p>
 * Accesses to objects of this class are unsynchronized.
 * It is assumed that any access is guarded by a lock on the
 * associated token reserver.
 */
class TokenReservation {
    private final SimulatablePlaceInstance _instance;
    private int _reservations;
    private final TestTokenBag _testedTokens;
    private final TokenBag _removedTokenDelays;

    TokenReservation(SimulatablePlaceInstance instance) {
        _instance = instance;
        _reservations = 0;

        _testedTokens = new TestTokenBag();
        _removedTokenDelays = new TokenBag();
    }

    boolean isRemovable() {
        return _reservations == 0;
    }

    // Return the set of registered delays for the given token.
    // If there are also tests, but the tests are not
    // targeted at an already tested token, an additional zero delay is
    // included.
    private TimeSet getRemovedDelaySet(Object token, boolean isTested) {
        TimeSet delays = _removedTokenDelays.getTimeSet(token);
        if (isTested && !_instance.containsTestedToken(token)) {
            delays = delays.including(0);
        }
        return delays;
    }

    public double computeEarliestTime() {
        double result;
        _instance._lock.lock();
        try {
            result = 0;
            Set<Object> tokens = new HashSet<>();
            tokens.addAll(_testedTokens.uniqueElements());
            tokens.addAll(_removedTokenDelays.uniqueElements());
            for (Object token : tokens) {
                TimeSet delays =
                    getRemovedDelaySet(token, _testedTokens.getTestMultiplicity(token) > 0);
                double time = _instance.computeEarliestTime(token, delays);
                if (time > result) {
                    result = time;
                }
            }
        } finally {
            _instance._lock.unlock();
        }
        return result;
    }

    public boolean containsRemovableToken(Object token, double delay) {
        boolean result;
        _instance._lock.lock();
        try {
            TimeSet delays = getRemovedDelaySet(token, _testedTokens.getTestMultiplicity(token) > 0)
                .including(delay);
            double time = _instance.computeEarliestTime(token, delays);
            result = (time < Double.POSITIVE_INFINITY);
        } finally {
            _instance._lock.unlock();
        }
        return result;
    }

    public boolean containsTestableToken(Object token) {
        boolean result;
        _instance._lock.lock();
        try {
            TimeSet delays = getRemovedDelaySet(token, true);
            double time = _instance.computeEarliestTime(token, delays);
            result = (time < Double.POSITIVE_INFINITY);
        } finally {
            _instance._lock.unlock();
        }
        return result;
    }

    public boolean removeToken(Object token, double delay) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        boolean result;
        _instance._lock.lock();
        try {
            result = containsRemovableToken(token, delay);
            if (result) {
                _removedTokenDelays.add(token, delay);
                _reservations++;
            }
        } finally {
            _instance._lock.unlock();
        }
        return result;
    }

    public boolean testToken(Object token) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        boolean result;
        _instance._lock.lock();
        try {
            result = containsTestableToken(token);
            if (result) {
                // We can ignore the time. It is only used to
                // restore a previous state in the case of places.
                _testedTokens.addTested(token, 0);
                _reservations++;
            }
        } finally {
            _instance._lock.unlock();
        }
        return result;
    }

    public void unremoveToken(Object token, double delay) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        _removedTokenDelays.removeOneOf(token, delay);
        _reservations--;
    }

    public void untestToken(Object token) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        _testedTokens.removeTested(token);
        _reservations--;
    }

    public Collection<Object> getRemovableTokens(Object pattern) {
        List<Object> result = new ArrayList<>();
        _instance._lock.lock();
        try {
            for (Object token : _instance.getDistinctTokens(pattern)) {
                // The token must be removable sometimes, not neccessarily
                // now, because the arc might specify a negative time delay.
                if (containsRemovableToken(token, Double.NEGATIVE_INFINITY)) {
                    result.add(token);
                }
            }
        } finally {
            _instance._lock.unlock();
        }
        return result;
    }

    public Collection<Object> getTestableTokens(Object pattern) {
        List<Object> result = new ArrayList<Object>();
        _instance._lock.lock();
        try {
            for (Object token : _instance.getDistinctTestableTokens(pattern)) {
                if (containsTestableToken(token)) {
                    result.add(token);
                }
            }
        } finally {
            _instance._lock.unlock();
        }
        return result;
    }
}