package de.renew.net.inscription.arc;

import de.renew.engine.searcher.Binder;
import de.renew.engine.searcher.BindingBadness;
import de.renew.engine.searcher.Searcher;
import de.renew.net.SimulatablePlaceInstance;
import de.renew.net.TokenReserver;
import de.renew.unify.Unify;
import de.renew.unify.Variable;

/**
 * An {@code ArcRemoveBinder} is a {@code Binder} that is used to simulate the addition and removal of tokens from a
 * {@link SimulatablePlaceInstance}. This can result in a token being reserved or being marked as tested.
 * How these operations work exactly is determined by the classes that extend it.
 */
abstract class ArcRemoveBinder implements Binder {
    /** The variable which the {@code ArcRemoveBinder} tries to bind. */
    private final Variable _variable;
    /** The place instance from which the {@code ArcRemoveBinder} removes tokens. */
    private final SimulatablePlaceInstance _placeInstance;

    /**
     * Constructs a new {@code ArcRemoveBinder} based on a variable and a place instance.
     *
     * @param variable the variable which the {@code ArcRemoveBinder} tries to bind
     * @param placeInstance the place instance that the {@code ArcRemoveBinder} removes tokens from
     */
    protected ArcRemoveBinder(Variable variable, SimulatablePlaceInstance placeInstance) {
        _variable = variable;
        _placeInstance = placeInstance;
    }

    /**
     * Returns {@code true} if binding using this {@code ArcRemoveBinder} is possible at all or {@code false} otherwise.
     *
     * @return {@code true}, if binding using this {@code ArcRemoveBinder} is possible at all or {@code false}, otherwise
     */
    abstract boolean mayBind();

    /**
     * Check whether it is possible to bind using this {@code ArcRemoveBinder} and the given token.
     *
     * @param reserver the reserver used for checking the binder's place instance for tokens
     * @param token the token to check for
     * @return {@code true}, if it is possible to bind or {@code false}, otherwise
     */
    protected abstract boolean possible(TokenReserver reserver, Object token);

    /**
     * Tries to remove the given token from the place instance of this {@code ArcRemoveBinder}.
     *
     * @param reserver the {@code TokenReserver} that may be used to implement the removal
     * @param token the token that should be removed.
     * @return {@code true}, if removing the {@code Token} was successful or {@code false}, otherwise
     */
    abstract boolean remove(TokenReserver reserver, Object token);

    /**
     * Adds the token back to the place instance of this {@code ArcRemoveBinder}, if it was {@link #remove removed}
     * before.
     *
     * @param reserver the {@code TokenReserver} that may be used to implement the operation
     * @param token the token that should be added back
     */
    abstract void unremove(TokenReserver reserver, Object token);

    /**
     * Get the place instance the ArcRemoveBinder's arc removes tokens from.
     *
     * @return the place instance
     */
    protected SimulatablePlaceInstance getPlaceInstance() {
        return _placeInstance;
    }

    @Override
    public int bindingBadness(Searcher searcher) {
        // The possibly incomplete value in the variable
        // will not be stored anywhere, so that it is
        // not required to make a copy of it.
        if (!Unify.isBound(_variable) || !mayBind()) {
            // We must not try to bind the variable.
            return BindingBadness.MAX;
        }
        if (possible(TokenReserver.getInstance(searcher), _variable.getValue())) {
            return 1;
        }
        return 0;
    }

    @Override
    public void bind(Searcher searcher) {
        // Make sure that the place instance notifies
        // the searchable if its marking changes,
        // because in that case the possible binding would have
        // to be rechecked.
        searcher.insertTriggerable(_placeInstance.triggerables());

        // Here we must make a copy of the value, because
        // the current value might be rolled back.
        // However, the new value must be created
        // without the possibility for rollback.
        // The value is completely bound anyway, so there
        // won't be a problem.
        Object value = Unify.copyBoundValue(_variable.getValue());
        TokenReserver tokenReserver = TokenReserver.getInstance(searcher);
        if (remove(tokenReserver, value)) {
            searcher.search();
            unremove(tokenReserver, value);
        }
    }
}