package de.renew.net.inscription.transition;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Vector;

import de.renew.engine.searcher.Binder;
import de.renew.engine.searcher.BindingBadness;
import de.renew.engine.searcher.Executable;
import de.renew.engine.searcher.Occurrence;
import de.renew.engine.searcher.OccurrenceDescription;
import de.renew.engine.searcher.Searcher;
import de.renew.engine.searcher.VariableMapperCopier;
import de.renew.expression.VariableMapper;
import de.renew.net.NetInstance;
import de.renew.net.Transition;
import de.renew.net.inscription.AbstractOccurrence;
import de.renew.unify.ICalculationChecker;
import de.renew.unify.IStateRecorder;
import de.renew.unify.Impossible;
import de.renew.unify.Unify;
import de.renew.unify.Variable;
import de.renew.util.Value;

/**
 * A {@code ConditionalOccurrence} is the occurrence of a {@link ConditionalInscription}. Its usage is unclear because
 * the latter is no longer in use. It uses a {@code Variable} to decide whether the occurrences of inscriptions
 * wrapped in its {@code ConditionalInscription} should be created and occur. It is its own binder, which simplifies the
 * code a bit.
 */
class ConditionalOccurrence extends AbstractOccurrence implements Occurrence, Binder {
    private final ConditionalInscription _conditionalInscription;
    private final VariableMapper _mapper;
    private final NetInstance _netInstance;
    private Variable _conditionVariable;
    private Collection<Occurrence> _secondaryOccurrences;
    private boolean _wantToOccur;

    /**
     * Creates a new {@code ConditionalOccurrence}.
     *
     * @param conditionalInscription the {@code ConditionalInscription} the {@code ConditionalOccurrence} is based on
     * @param mapper the {@code VariableMapper} which is used for the condition variable
     * @param netInstance the net instance in which the {@code ConditionalOccurrence} can occur
     * @param transition the transition to which the {@code ConditionalOccurrence} is related
     */
    public ConditionalOccurrence(
        ConditionalInscription conditionalInscription, VariableMapper mapper,
        NetInstance netInstance, Transition transition)
    {
        super(netInstance.getInstance(transition));
        _conditionalInscription = conditionalInscription;
        _mapper = mapper;
        _netInstance = netInstance;
    }

    @Override
    public Collection<Binder> makeBinders(Searcher searcher) throws Impossible {
        IStateRecorder stateRecorder = searcher.getStateRecorder();
        ICalculationChecker calculationChecker = searcher.getCalculationChecker();

        _conditionVariable = new Variable(
            _conditionalInscription.getConditionExpression()
                .startEvaluation(_mapper, stateRecorder, calculationChecker),
            stateRecorder);


        // I am my own binder.
        Collection<Binder> coll = new Vector<Binder>();
        coll.add(this);
        return coll;
    }

    @Override
    public int bindingBadness(Searcher searcher) {
        if (Unify.isBound(_conditionVariable)) {
            return 1;
        } else {
            return BindingBadness.MAX;
        }
    }

    @Override
    public void bind(Searcher searcher) {
        Object obj = _conditionVariable.getValue();
        if (!(obj instanceof Value)) {
            // Expression is badly bound.
            return;
        }

        if (!(obj instanceof Boolean)) {
            // Expression is badly bound.
            return;
        }

        Collection<Binder> binders = new ArrayList<Binder>();

        int checkpoint = searcher.getStateRecorder().checkpoint();

        try {
            boolean wantToOccur = (Boolean) ((Value) obj).value;

            _secondaryOccurrences = new ArrayList<>();
            if (wantToOccur) {
                _secondaryOccurrences.addAll(
                    _conditionalInscription.getInscription()
                        .makeOccurrences(_mapper, _netInstance, searcher));


                // Create all binders.
                for (Occurrence occurrence : _secondaryOccurrences) {
                    binders.addAll(occurrence.makeBinders(searcher));
                }

                // After this statement, no further exceptions should be thrown.
                // Add binders to searcher.
                searcher.addBinders(binders);
            }

            searcher.removeBinder(this);
            searcher.search();
            searcher.addBinder(this);

            if (wantToOccur) {
                searcher.removeBinders(binders);
                _secondaryOccurrences = null;
            }
        } catch (Impossible e) {
            // This is expected. The secondary occurrence could
            // not create its binders.
        }

        searcher.getStateRecorder().restore(checkpoint);
    }


    @Override
    public Collection<Executable> makeExecutables(VariableMapperCopier copier) {
        if (!_wantToOccur) {
            return Collections.emptyList();
        }

        return _secondaryOccurrences.stream()
            .flatMap(occurrence -> occurrence.makeExecutables(copier).stream()).toList();
    }

    @Override
    public OccurrenceDescription makeOccurrenceDescription(
        VariableMapperCopier variableMapperCopier)
    {
        return null;
    }

    Variable getConditionVariable() {
        return _conditionVariable;
    }
}