package de.renew.net.inscription.arc;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;

import de.renew.engine.common.SimulatorEventLogger;
import de.renew.engine.events.Putting;
import de.renew.engine.searcher.LateExecutable;
import de.renew.engine.searchqueue.SearchQueue;
import de.renew.net.SimulatablePlaceInstance;
import de.renew.simulatorontology.simulation.StepIdentifier;
import de.renew.unify.Impossible;
import de.renew.unify.List;
import de.renew.unify.Variable;
import de.renew.util.Value;

/**
 * The executable belonging to {@link FlexibleArcOccurrence} instances that are output arcs.
 */
class FlexibleOutArcExecutable implements LateExecutable {
    /** The place instance that tokens will be added to. */
    private final SimulatablePlaceInstance _placeInstance;
    /** The variable determining what the tokens to be added to {@link #_placeInstance} are.*/
    private final Variable _tokenVar;
    /** The {@code FlexibleArc} whose occurrence causes this executable to be created. */
    private final FlexibleArc _arc;

    /**
     * Constructs a new {@code FlexibleOutArcExecutable} based on instances of the place and transition the arc is
     * connected to, a variable containing the tokens that the arc transports, and the arc itself.
     *
     * @param placeInstance the place instance that tokens will be added to
     * @param tokenVar the variable containing the tokens that will be transported
     * @param arc the arc whose occurrence in a net instance caused the creation of the executable
     */
    FlexibleOutArcExecutable(
        SimulatablePlaceInstance placeInstance, Variable tokenVar, FlexibleArc arc)
    {
        _placeInstance = placeInstance;
        _tokenVar = tokenVar;
        _arc = arc;
    }

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

    // We can put a token into an output place quickly.
    @Override
    public boolean isLong() {
        return false;
    }

    private void putSingleToken(StepIdentifier stepIdentifier, Object token) {
        if (_arc.getForwardFunction() != null) {
            try {
                token = _arc.getForwardFunction().function(token);
            } catch (Impossible e) {
                // This should not happen.
            }
        }


        // We ignore the backward function. It is supposed to detect
        // errors for input arcs, but for output arcs it should not be set.
        if (_arc.getTrace()) {
            // log activities on net level
            SimulatorEventLogger
                .log(stepIdentifier, new Putting(token, _placeInstance), _placeInstance);
        }

        _placeInstance.insertToken(token, SearchQueue.getTime());
    }

    @Override
    public void execute(StepIdentifier stepIdentifier) {
        Object tokens = _tokenVar.getValue();

        _placeInstance._lock.lock();
        try {
            if (tokens == null) {
                // Let's ignore this arc. Probably we are not meant to
                // insert a single null token into the place, but rather
                // the null is a substitute for an empty array.
                //
                // Actually, who knows? But we cannot complain, because we
                // are in the late phase of firing.
            } else if (tokens instanceof Iterator) {
                // The place must be untyped, or a type error would have
                // occurred earlier on.
                for (Iterator<?> i = (Iterator<?>) tokens; i.hasNext();) {
                    putSingleToken(stepIdentifier, i.next());
                }
            } else if (tokens instanceof Enumeration<?> enumeration) {
                // The place must be untyped, or a type error would have
                // occurred earlier on.
                while (enumeration.hasMoreElements()) {
                    putSingleToken(stepIdentifier, enumeration.nextElement());
                }
            } else if (tokens.getClass().isArray()) {
                Class<?> elementType = tokens.getClass().getComponentType();
                for (int i = 0; i < Array.getLength(tokens); i++) {
                    Object token = Array.get(tokens, i);
                    if (elementType.isPrimitive()) {
                        token = new Value(token);
                    }
                    putSingleToken(stepIdentifier, token);
                }
            } else if (tokens instanceof List current) {
                // The place must be untyped, or a type error would have
                // occurred earlier on.
                while (!current.isNull()) {
                    putSingleToken(stepIdentifier, current.head());
                    if (!(current.tail() instanceof List)) { // stop if open/corrupted list
                        break;
                    }
                    current = (List) current.tail();
                }
            } else if (tokens instanceof Collection<?> coll) {
                // The place must be untyped, or a type error would have
                // occurred earlier on.
                for (Object o : coll) {
                    putSingleToken(stepIdentifier, o);
                }
            } else {
                // As a last resort, we insert the value itself into the
                // place. This is barely acceptable, but at least we
                // know that the place is not typed, otherwise a type
                // check would have detected the illegal situation.
                putSingleToken(stepIdentifier, tokens);
            }
        } finally {
            _placeInstance._lock.unlock();
        }
    }

    @Override
    public void executeAfterException(StepIdentifier stepIdentifier, Throwable t) {}
}