package de.renew.net.inscription.arc;

import java.util.Vector;

import de.renew.engine.searcher.EarlyExecutable;
import de.renew.engine.searchqueue.SearchQueue;
import de.renew.net.SimulatablePlaceInstance;
import de.renew.simulatorontology.simulation.StepIdentifier;
import de.renew.unify.Impossible;

/**
 * The executable belonging to {@link FlexibleArcOccurrence} instances that are input arcs.
 */
class FlexibleInArcExecutable implements EarlyExecutable {
    /** The place instance the arc takes tokens from. */
    private final SimulatablePlaceInstance _placeInstance;
    /** The tokens removed from {@link #_placeInstance} by the flexible arc. */
    private final Vector<Object> _tokens;
    /**
     * The flexible arc whose {@code FlexibleArcOccurrence} in a {@link de.renew.net.NetInstance NetInstance}
     * this {@code FlexibleInArcExecutable} belongs to.
     */
    private final FlexibleArc _arc;
    /** The times at which tokens have been removed from {@link #_placeInstance}. */
    private final Vector<Double> _removedTimes;
    /** The amount of tokens that have been removed from {@link #_placeInstance}. */
    private int _numRemoved;

    /**
     * Constructs a new {@code FlexibleInArcExecutable} based on the instances of the
     * place and transition the arc connects, the tokens that should be moved during execution, and the arc itself.
     *
     * @param placeInstance the place instance that tokens will be taken from
     * @param tokens the tokens that will be removed
     * @param arc the arc whose occurrence in a net instance causes the executable to be created
     */
    FlexibleInArcExecutable(
        SimulatablePlaceInstance placeInstance, Vector<Object> tokens, FlexibleArc arc)
    {
        _placeInstance = placeInstance;
        _tokens = tokens;
        _arc = arc;
        _numRemoved = 0;
        _removedTimes = new Vector<>();
    }

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

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

    /**
     * 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 {
        try {
            for (int i = 0; i < _tokens.size(); i++) {
                Object token = _tokens.elementAt(i);
                double removedTime = _placeInstance.removeToken(token, 0);
                _removedTimes.addElement(removedTime);
                _numRemoved++;
            }
        } catch (Exception e) {
            // Undo all reservations.
            rollback();

            throw new Impossible();
        }
    }

    @Override
    public void execute(StepIdentifier stepIdentifier) {
        boolean isFastBoth = (_arc.getArcType() == FlexibleArc.FAST_BOTH);

        for (int i = 0; i < _numRemoved; i++) {
            Object token = _tokens.elementAt(i);
            if (_arc.isTrace()) {
                InputArcExecutable.traceInArc(stepIdentifier, isFastBoth, token, _placeInstance); //NOTICEsignature
            }
            if (isFastBoth) {
                _placeInstance.insertToken(token, SearchQueue.getTime());
            }
        }
    }

    @Override
    public void rollback() {
        // We have to undo the previous removals. 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 < _numRemoved; i++) {
            _placeInstance.internallyInsertToken(_tokens.get(i), _removedTimes.get(i), false);
        }
    }

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