package de.renew.ptchannel.single;

import java.io.StringReader;
import java.util.Collection;

import de.renew.formalism.java.InscriptionParser;
import de.renew.formalism.java.JavaNetParser;
import de.renew.formalism.java.ParseException;
import de.renew.formalism.java.ParsedDeclarationNode;
import de.renew.formalism.pt.SinglePTNetCompiler;
import de.renew.net.inscription.TransitionInscription;
import de.renew.simulatorontology.shadow.ShadowArc;
import de.renew.simulatorontology.shadow.ShadowDeclarationNode;
import de.renew.simulatorontology.shadow.ShadowInscription;
import de.renew.simulatorontology.shadow.ShadowNet;
import de.renew.simulatorontology.shadow.SyntaxException;


/**
 * This is the single for P/T nets with synchronous channels.
 * For a definition, see thesis of Voß or Kühnemund (2021).
 * It behaves like a P/T net single and only overrides methods
 * that are necessary for channels.
 * Channels are compiled using a specialized {@link PTCTransitionCompiler}.
 * Importantly, transitions may only carry one inscription.
 * <br><br>
 * Integer parameters for channels are valid. They are compiled by
 * converting them into lists with n black tokens and converting
 * all corresponding inscripted arcs into flexible arcs which
 * unpack those lists.
 *
 * @author Lukas Voß
 */
public class SinglePTNetWithChannelCompiler extends SinglePTNetCompiler {

    private final PTCTransitionCompiler _transitionCompiler;
    private ParsedDeclarationNode _declaration;

    /**
     * Creates a new <code>SinglePTNetWithChannelCompiler</code> instance.
     * The only additional step to the super constructor is setting
     * a {@link PTCTransitionCompiler} for compilation of transition
     * inscriptions.
     */
    public SinglePTNetWithChannelCompiler() {
        super();
        this._transitionCompiler = new PTCTransitionCompiler(false, false, false);
    }

    /**
     * This is the syntax check for transition inscriptions.
     * After a transition inscription is edited, this method gets
     * called. It throws an exception whenever the inscription
     * includes anything other than an up-/downlink.
     *
     * @param inscription the edited entire inscription
     * @param special whether it is a special inscription
     * @param shadowNet the corresponding shadow net
     * @return type of the first inscription
     * @throws SyntaxException whenever an inscription is no up-/downlink
     */
    @Override
    public String checkTransitionInscription(
        String inscription, boolean special, ShadowNet shadowNet) throws SyntaxException
    {
        String trans =
            _transitionCompiler.checkTransitionInscription(inscription, special, shadowNet);
        _declaration = _transitionCompiler.makeDeclarationNode(shadowNet);
        return trans;
    }

    @Override
    public String checkDeclarationNode(String inscription, boolean special, ShadowNet shadowNet)
        throws SyntaxException
    {
        parseDeclarationNode(inscription);
        return "declaration";
    }

    ParsedDeclarationNode parseDeclarationNode(String inscr) throws SyntaxException {
        if (inscr != null) {
            InscriptionParser parser = makeParser(inscr);
            try {
                return parser.DeclarationNode();
            } catch (ParseException e) {
                throw PTCTransitionCompiler.makeSyntaxException(e);
            }
        } else {
            return makeEmptyDeclarationNode();
        }
    }

    /** Initializes an empty declaration node.
     *
     * @return an empty declaration node
     */
    protected ParsedDeclarationNode makeEmptyDeclarationNode() {
        return new ParsedDeclarationNode();
    }

    @Override
    public void parseDeclarations(ShadowNet shadowNet) throws SyntaxException {
        makeDeclarationNode(shadowNet);
    }

    /**
     * Compiles the declaration node.
     * Creates an empty node if no declaration node is found in the shadow net.
     *
     * @param shadowNet the corresponding shadow net
     * @throws SyntaxException when a syntax exception occurs
     */
    public void makeDeclarationNode(ShadowNet shadowNet) throws SyntaxException {
        // Compile the declaration node.
        ShadowDeclarationNode node = findDeclarationNode(shadowNet);
        if (node == null) {
            _declaration = makeEmptyDeclarationNode();
        } else {
            _declaration = compile(node);
        }
    }

    /**
     * Compiles the parsed declaration node with a given shadow declaration node.
     * @param declarationNode the shadow declaration node to compile
     * @return the parsed declaration node
     * @throws SyntaxException if a syntax exception occurs
     */
    protected ParsedDeclarationNode compile(ShadowDeclarationNode declarationNode)
        throws SyntaxException
    {
        try {
            return parseDeclarationNode(declarationNode.getInscription());
        } catch (SyntaxException e) {
            throw e.addObject(declarationNode);
        }
    }

    /**
     * When starting a simulation, this method gets called to compile
     * transition inscriptions.
     * The compilation process takes place in the {@link PTCTransitionCompiler}.
     *
     * @param inscription inscription to compile
     * @return All inscriptions of a transition
     * @throws SyntaxException whenever a inscription is no up-/downlink
     */
    @Override
    protected Collection<TransitionInscription> compileTransitionInscription(
        ShadowInscription inscription) throws SyntaxException
    {
        return _transitionCompiler
            .makeInscriptions(inscription, lookup, true, _declaration, _loopbackNetLoader);
    }

    /**
     * Creates a new parser for the given inscription.
     *
     * @param inscr the inscription to parse
     * @return a new parser for the given inscription
     */
    protected InscriptionParser makeParser(String inscr) {
        JavaNetParser parser = new JavaNetParser(new StringReader(inscr));
        parser.setNetLoader(_loopbackNetLoader);
        return parser;
    }

    @Override
    public String checkArcInscription(String inscription, boolean special, ShadowNet shadowNet)
        throws SyntaxException
    {
        return _transitionCompiler.checkArcInscription(inscription, special, shadowNet);
    }

    /**
     * Compile an arc like in the ordinary P/T formalism.
     * If that throws an exception, the arc is inscribed.
     * Thus, it needs to be compiled using the
     * transitionCompiler instead.
     *
     * @param shadowArc the arc to compile
     * @throws SyntaxException if the transition single cannot compile the arc
     */
    @Override
    protected void compileArc(ShadowArc shadowArc) throws SyntaxException {
        try {
            super.compileArc(shadowArc);
        } catch (SyntaxException e) {
            if (shadowArc.getShadowArcType() == ShadowArc.ORDINARY) {
                _transitionCompiler.compileArc(shadowArc, _declaration, lookup);
            } else {
                throw new SyntaxException("Only ordinary arcs are allowed.").addObject(shadowArc);
            }
        }
    }

    /**
     * Returns the transition compiler.
     *
     * @return the transition compiler
     */
    protected PTCTransitionCompiler getTransitionCompiler() {
        return _transitionCompiler;
    }

    /**
     * Returns the parsed declaration node.
     *
     * @return the parsed declaration node
     */
    protected ParsedDeclarationNode getDeclaration() {
        return _declaration;
    }
}
