package de.renew.net.inscription.arc;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Vector;

import de.renew.engine.searcher.Binder;
import de.renew.engine.searcher.Executable;
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.SimulatablePlaceInstance;
import de.renew.net.inscription.AbstractOccurrence;
import de.renew.unify.Copier;
import de.renew.unify.Impossible;
import de.renew.unify.Variable;
import de.renew.util.Value;

/**
 * An ArcOccurrence is the occurrence of an Arc.
 */
public class ArcOccurrence extends AbstractOccurrence {
    /**
     * This is a complete and immutable variable that is used
     * whenever not time specification is explicitly given.
     */
    public static final Variable THE_NULL_TIME_VAR = new Variable(new Value(0.0), null);

    /**
     * The arc associated with this occurrence.
     */
    private final Arc _arc;

    /**
     * The instance of the place associated with this arc occurrence.
     */
    private final SimulatablePlaceInstance _placeInstance;

    /**
     * The {@code VariableMapper} that is used during the unification algorithm.
     */
    private final VariableMapper _mapper;

    /**
     * The variable representing the token handled by this arc occurrence.
     * It is used for both input and output operations, depending on the arc type.
     */
    private Variable _tokenVar;

    /**
     * The variable representing the delay (time) associated with the arc.
     * It is only used for timed arcs and is evaluated during the binding process.
     */
    private Variable _delayVar;


    /**
     * The constructor of ArcOccurrence.
     *
     * @param arc the arc
     * @param mapper the mapper for the ArcOccurrence's variables
     * @param netInstance the net instance that the ArcOccurrence is in
     */
    protected ArcOccurrence(Arc arc, VariableMapper mapper, NetInstance netInstance) {
        super(netInstance.getInstance(arc.getTransition()));
        _arc = arc;
        _mapper = mapper;
        _placeInstance = (SimulatablePlaceInstance) netInstance.getInstance(arc.getPlace());
    }

    /**
     * Creates an {@code InhibitorArcBinder} with the given values.
     *
     * @param tokenVar the variable for the InhibitorArcBinder
     * @param placeInstance the place instance for the InhibitorArcBinder
     * @return the {@code InhibitorArcBinder} for the supplied variable and place instance.
     */
    protected InhibitorArcBinder getInhibitorArcBinder(
        Variable tokenVar, SimulatablePlaceInstance placeInstance)
    {
        return new InhibitorArcBinder(tokenVar, placeInstance);
    }

    @Override
    public Collection<Binder> makeBinders(Searcher searcher) throws Impossible {
        // For the moment, arc expressions are always early.
        // We might make them late for output arcs, but this
        // might cause significant confusion. By introducing a
        // new variable and an action statement a late evaluation
        // can always be enforced within the model.
        _tokenVar = new Variable(
            _arc.getTokenExpression().startEvaluation(
                _mapper, searcher.getStateRecorder(), searcher.getCalculationChecker()),
            searcher.getStateRecorder());

        if (!_arc.isUntimedArc()) {
            _delayVar = new Variable(
                _arc.getTimeExpression().startEvaluation(
                    _mapper, searcher.getStateRecorder(), searcher.getCalculationChecker()),
                searcher.getStateRecorder());
        }


        // Prepare a sequence of binders for this arc.
        List<Binder> binders = new ArrayList<>();

        if (_arc.isTypeOf(Arc.Type.OUT)) {
            // We must make sure that the variables associated with
            // an output arc are going to be computed before we declare
            // a transition enabled.
            searcher.getCalculationChecker()
                .addLateVariable(_tokenVar, searcher.getStateRecorder());
            searcher.getCalculationChecker()
                .addLateVariable(_delayVar, searcher.getStateRecorder());
        } else if (_arc.isTypeOf(Arc.Type.INHIBITOR)) {
            binders.add(getInhibitorArcBinder(_tokenVar, _placeInstance));
        } else {
            // If the arc expression is invertable, there is a good
            // reason to try to assign values to it.
            if (_arc.getTokenExpression().isInvertible()) {
                binders.add(new ArcAssignBinder(_tokenVar, _placeInstance, _arc.isTestArc()));
            }


            // Input arcs and double arcs simply check the
            // existence of tokens during binding.
            if (_arc.isTestArc()) {
                binders.add(new TestArcBinder(_tokenVar, _placeInstance));
            } else if (_arc.isTypeOf(Arc.Type.BOTH)) {
                binders.add(new InputArcBinder(_tokenVar, new Variable(0, null), _placeInstance));
                searcher.getCalculationChecker()
                    .addLateVariable(_delayVar, searcher.getStateRecorder());
            } else {
                binders.add(new InputArcBinder(_tokenVar, _delayVar, _placeInstance));
            }
        }

        return binders;
    }

    private double getDelay() {
        Object timeObj = _delayVar.getValue();
        if (timeObj instanceof Value) {
            timeObj = ((Value) timeObj).value;
        }
        return ((Number) timeObj).doubleValue();
    }

    @Override
    public Collection<Executable> makeExecutables(VariableMapperCopier variableMapperCopier) {
        Copier copier = variableMapperCopier.getCopier();


        // There will be no undo past this point, so we need
        // not supply a state recorder. However, we must always
        // make a copy, because the current bindings will be
        // completely rolled back. Even when we make a getValue()
        // call to the old variable, that variable might contain
        // a tuple that is taken apart later on.
        Variable copiedToken = (Variable) copier.copy(_tokenVar);

        Collection<Executable> coll = new Vector<>();

        // Depending on the type of the arc we must create different
        // executables.
        switch (_arc.getArcType()) {
            case IN:
                coll.add(
                    new InputArcExecutable(
                        _placeInstance, copiedToken.getValue(), getDelay(), false,
                        _arc.getTrace()));
                return coll;
            case TEST:
                TestArcExecutable tester = new TestArcExecutable(
                    _placeInstance, copiedToken.getValue(), false, _arc.getTrace());
                return List.of(tester, new UntestArcExecutable(tester));
            case OUT:
                // For an output arc, the time might be calculated later.
                Variable copiedDelay = (Variable) copier.copy(_delayVar);
                coll.add(
                    new OutputArcExecutable(
                        _placeInstance, copiedToken, copiedDelay, _arc.getTrace()));
                return coll;
            case BOTH:
                return List.of(
                    new InputArcExecutable(
                        _placeInstance, copiedToken.getValue(), getDelay(), false, _arc.getTrace()),
                    new OutputArcExecutable(_placeInstance, copiedToken, null, _arc.getTrace()));
            case FAST_BOTH:
                coll.add(
                    new InputArcExecutable(
                        _placeInstance, copiedToken.getValue(), getDelay(), true, _arc.getTrace()));
                return coll;
            case FAST_TEST:
                coll.add(
                    new TestArcExecutable(
                        _placeInstance, copiedToken.getValue(), true, _arc.getTrace()));
                return coll;
            case INHIBITOR:
                coll.add(
                    new InhibitorExecutable(
                        _placeInstance, copiedToken.getValue(), _arc.getTrace()));
                return coll;
            case BOTH_OT:
                // For an output arc, the time might be calculated later.
                copiedDelay = (Variable) copier.copy(_delayVar);
                return Arrays.asList(
                    new InputArcExecutable(
                        _placeInstance, copiedToken.getValue(), 0, false, _arc.getTrace()),
                    new OutputArcExecutable(
                        _placeInstance, copiedToken, copiedDelay, _arc.getTrace()));
            default:
                throw new RuntimeException("Bad arc type.");
        }
    }

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