package de.renew.engine.searcher;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import de.renew.engine.thread.SimulationThreadPool;
import de.renew.unify.Variable;

/**
 * This class binds channels by looping through all the possible binding candidates
 * and calculates the maximum binding badness.
 */
public class ChannelBinder implements Binder {
    // Try bindings with all the transitions possible
    // for the given net. Usually there will be very few
    // possible transitions.
    private final Variable _targetVariable;
    private final String _name;
    private final Variable _params;

    // If true, indicates that this channel need not be used if
    // the called net has no appropriate channels at all.
    private final boolean _isOptional;

    /**
     * Assigns the given values to {@code targetVariable}, {@code name}, {@code params} and {@code isOptional}
     *
     * @param targetVariable the {@link Variable} to be assigned a value to
     * @param name the name of the channel
     * @param params the {@link Variable} to be bound
     * @param isOptional {@code true} when it indicates that this channel need not be used if
     *                   the called net has no appropriate channels at all
     */
    public ChannelBinder(
        Variable targetVariable, String name, Variable params, boolean isOptional)
    {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        _targetVariable = targetVariable;
        _name = name;
        _params = params;
        _isOptional = isOptional;
    }

    @Override
    public int bindingBadness(Searcher searcher) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        Object value = _targetVariable.getValue();
        if (value instanceof ChannelTarget channelTarget) {
            Collection<UplinkProvider> uplinkProviders = channelTarget.getUplinkProviders(_name);
            return BindingBadness.clip(uplinkProviders.size());
        }
        return BindingBadness.MAX;
    }

    @Override
    public void bind(Searcher searcher) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        ChannelTarget channelTarget = (ChannelTarget) _targetVariable.getValue();
        List<UplinkProvider> providers = new ArrayList<>(channelTarget.getUplinkProviders(_name));
        Collections.shuffle(providers);
        Iterator<UplinkProvider> uplinkProviders = providers.iterator();

        if (_isOptional && !uplinkProviders.hasNext()) {
            // This channel is optional and no channel is offered at
            // the invoked net. This is too easy.
            searcher.search();
            return;
        }
        // Either the channel is not optional, or a suitable
        // channel is provided.
        while (uplinkProviders.hasNext() && !searcher.isCompleted()) {
            UplinkProvider uplinkProvider = uplinkProviders.next();
            uplinkProvider.bindChannel(_params, searcher);
        }
    }
}