package de.renew.net.inscription.arc;

import java.lang.reflect.Array;
import java.util.Vector;

import de.renew.engine.common.SimulatorEventLogger;
import de.renew.engine.events.Clearing;
import de.renew.engine.searcher.EarlyExecutable;
import de.renew.net.SimulatablePlaceInstance;
import de.renew.simulatorontology.simulation.StepIdentifier;
import de.renew.unify.Impossible;
import de.renew.unify.Unify;
import de.renew.unify.Variable;
import de.renew.util.Value;

/**
 * The executable belonging to a {@link ClearArcOccurrence}.
 * It is used to clear all tokens from a {@link SimulatablePlaceInstance}.
 */
class ClearArcExecutable implements EarlyExecutable {
    /**
     * The place instance whose tokens are cleared.
     */
    private final SimulatablePlaceInstance _placeInstance;
    /**
     * The variable which will be bound to an array containing the tokens removed from the place instance.
     */
    private final Variable _variable;
    /**
     * The clear arc whose occurrence leads to this {@code ClearArcExecutable} being executed.
     */
    private final ClearArc _arc;
    /**
     * The tokens that the clear arc has removed from the place instance.
     */
    private Vector<Object> _removedTokens;
    /**
     * The time stamps of the times at which each of the removed tokens were removed from the place instance.
     */
    private Vector<Double> _removedTimeStamps;

    /**
     * Constructs a new {@code ClearArcExecutable} based on its place and transition instances, a {@code Variable} and
     * the {@code ClearArc} it is based on.
     * 
     * @param pInstance the place instance whose tokens will be cleared
     * @param variable the variable in which the removed tokens will be stored
     * @param arc the {@code ClearArc} that the {@code ClearArcExecutable} is based on
     */
    ClearArcExecutable(SimulatablePlaceInstance pInstance, Variable variable, ClearArc arc) {
        _placeInstance = pInstance;
        _variable = variable;
        _arc = arc;
    }

    @Override
    public long lockPriority() {
        return _placeInstance.getLockOrder();
    }

    @Override
    public int phase() {
        return CLEAR;
    }

    /**
     * Locks the {@link de.renew.net.PlaceInstance#_lock PlaceInstance} associated with this arc.
     **/
    @Override
    public void lock() {
        _placeInstance._lock.lock();
    }

    @Override
    public void verify(StepIdentifier stepIdentifier) throws Impossible {
        // Ensure large size increments.
        _removedTokens = new Vector<>(8, 0);
        _removedTimeStamps = new Vector<>(8, 0);

        _placeInstance.extractAllTokens(_removedTokens, _removedTimeStamps);

        Object result = Array.newInstance(_arc.getElementType(), _removedTokens.size());
        for (int i = 0; i < _removedTokens.size(); i++) {
            Object value = _removedTokens.elementAt(i);
            if (_arc.getElementType().isPrimitive()) {
                value = Value.unvalueAndCast(value, _arc.getElementType());
            }
            Array.set(result, i, value);
        }

        Unify.unify(_variable, result, null);
    }

    @Override
    public void execute(StepIdentifier stepIdentifier) {
        if (_arc.getTrace()) {
            // log activities on net level
            SimulatorEventLogger.log(stepIdentifier, new Clearing(_placeInstance), _placeInstance);
        }
    }

    @Override
    public void rollback() {
        // We have to undo the previous removal. We cannot do this
        // without notifying the observers and listeners, because
        // it was not done silently. However, the database must not log
        // this modification.
        for (int i = 0; i < _removedTokens.size(); i++) {
            Object token = _removedTokens.elementAt(i);
            double time = _removedTimeStamps.elementAt(i);
            _placeInstance.internallyInsertToken(token, time, false);
        }
        _removedTokens = null;
    }

    /**
     * Unlocks the {@link de.renew.net.PlaceInstance#_lock PlaceInstance} associated with this arc.
     **/
    @Override
    public void unlock() {
        _placeInstance._lock.unlock();
    }
}