package de.renew.gui.nin;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.*;

import CH.ifa.draw.figures.TextFigure;
import de.renew.draw.storables.api.StorableApi;
import de.renew.draw.storables.ontology.Drawing;
import de.renew.draw.storables.ontology.Figure;
import de.renew.draw.storables.ontology.FigureEnumeration;
import de.renew.gui.CPNDrawing;
import de.renew.gui.CPNTextFigure;
import de.renew.gui.InscribableFigure;
import de.renew.gui.PlaceFigure;
import de.renew.gui.TransitionFigure;

/**
 * Build a system net for a collection of nets, which
 * coordinates the synchronization between different net instances.
 * Each system net must contain a transition which creates
 * other net instances.
 * Additionally, it contains transitions for each possible
 * type of synchronization for each considered channel.
 *
 * @author Lukas Voß
 */
public class SystemNetBuilder {

    public static final String formalismCompilerName = "P/T Net in Net Compiler";
    private static final org.apache.log4j.Logger logger =
        org.apache.log4j.Logger.getLogger(SystemNetBuilder.class);
    private final Map<Integer, Set<String>> channelsForOption;
    final boolean[] options;
    int downMover = 0;
    int numSyncTransitions = 0;
    int rightMover = 0;
    private CPNDrawing systemNet;
    private Collection<TransitionFigure> transitions;
    private Collection<PlaceFigure> places;
    private Collection<TextFigure> texts;
    private TransitionFigure templateSyncTransition;
    private CPNTextFigure templateSyncInscription;

    /**
     * Instantiate a new SystemNetBuilder.
     * It loads the net template which serves as
     * assistance in building the system net
     * and calls the main construction function with
     * that template.
     *
     * @param drawingsToConsider net instances of these drawings should be created in the system net
     * @param options            all possible types of synchronization
     * @param channelsForOption  which channels should be considered in each type of synchronization
     * @param customSynchronizations add transitions for these custom synchronizations to system net
     */
    public SystemNetBuilder(
        Map<Drawing, Integer> drawingsToConsider, boolean[] options,
        Map<Integer, Set<String>> channelsForOption,
        DefaultListModel<String> customSynchronizations)
    {
        this.options = options;
        this.channelsForOption = channelsForOption;
        CPNDrawing template = TemplateBuilder.buildSystemNetTemplate();
        buildSystemNet(template, drawingsToConsider, customSynchronizations);
    }

    /**
     * This is the main method which initiates the system net construction.
     * It uses a net template to build the system net, which contains
     * a template net-creation transition, a place which
     * holds their references and a transition which allows them to
     * communicate via synchronous channels.
     * That transition hold downlinks to the nets, while the
     * nets themselves have downlinks for communication.
     * The templates for these are expended to take the actual
     * nets into consideration.
     *
     * @param template               net template for system net creation
     * @param drawingsToConsider     which nets should be able to communicate via the system
     * @param customSynchronizations add transitions for these custom synchronizations to system net
     */
    private void buildSystemNet(
        CPNDrawing template, Map<Drawing, Integer> drawingsToConsider,
        DefaultListModel<String> customSynchronizations)
    {
        try {
            systemNet = StorableApi.cloneStorable(template);
        } catch (Exception e) {
            logger.error("Could not clone template net.");
            return;
        }
        setFigureFields(systemNet);

        TextFigure createReferencesText = getCreateText();
        TextFigure moveReferencesArcText = getRefArcText();
        setTextsForCreatingAndMovingAllConsideredNets(
            createReferencesText, moveReferencesArcText, drawingsToConsider);
        buildTransitionsForAllUplinksInNets();
        for (int i = 0; i < customSynchronizations.getSize(); i++) {
            addDownlinkTransitionForInscriptionToSystem(customSynchronizations.get(i));
        }

        systemNet.setName("SystemNet");
        systemNet.checkDamage();
    }

    /**
     * This method changes the "creation" Transition for creating
     * new net instances via the :new syntax, as well as the
     * outgoing arc connected to that transition which moves the
     * reference tokens.
     * For each net that should be created in the system net, the
     * transition's text gets appended accordingly.
     * After that, the outgoing arc gets inscripted with the
     * newly created net reference, such that the reference gets
     * transferred to the reference place.
     *
     * @param createReferencesText  inscription of the create-transition
     * @param moveReferencesArcText inscription of the outgoing move-arc
     * @param drawingsToConsider    drawings which should be created in the system net
     */
    private void setTextsForCreatingAndMovingAllConsideredNets(
        TextFigure createReferencesText, TextFigure moveReferencesArcText,
        Map<Drawing, Integer> drawingsToConsider)
    {
        if (createReferencesText != null && moveReferencesArcText != null) {
            String createText = "";
            String moveText = "";
            for (Drawing drawing : drawingsToConsider.keySet()) {
                for (int i = 0; i < drawingsToConsider.get(drawing); i++) {
                    String netVariableName = drawing.getName().toLowerCase() + i;
                    createText =
                        createText.concat(netVariableName + ":new " + drawing.getName() + ";\n");
                    moveText = moveText.concat(netVariableName + ";");
                }
            }
            createReferencesText.setText(createText);
            moveReferencesArcText.setText(moveText);
        }
    }

    /**
     * This method is responsible for creating transitions
     * which allow the communication of synchronous channels.
     * Each of the options represents one possible type of
     * synchronization (i.e. synchronization between different
     * net instances of within one instance).
     * If an option is selected, an according transition for
     * each of the possible channels is built in the system net.
     * <p>
     * Depending on the type of synchronization (indicated by the option),
     * the inscription contains different communication partners in
     * the form of strings, different numbers of communication partners.
     */
    private void buildTransitionsForAllUplinksInNets() {
        if (options[0]) {
            String[] nets = { "netA", "netB" };
            int numOfCommunicationPartners = 2;
            for (String channel : channelsForOption.get(0)) {
                addDownlinkTransitionForChannelToSystem(nets, channel, numOfCommunicationPartners);
            }
        }
        if (options[1]) {
            String[] nets = { "netA" };
            int numOfCommunicationPartners = 2;
            for (String channel : channelsForOption.get(1)) {
                addDownlinkTransitionForChannelToSystem(nets, channel, numOfCommunicationPartners);
            }
        }
        if (options[2]) {
            String[] nets = { "netA" };
            int numOfCommunicationPartners = 1;
            for (String channel : channelsForOption.get(2)) {
                addDownlinkTransitionForChannelToSystem(nets, channel, numOfCommunicationPartners);
            }
        }
        removeTemplateFigures();
    }

    /**
     * This method adds a downlink inscription transition to
     * the system net.
     * A number of downlinks is added to the net and each
     * of these downlinks is inscribed with a net from the input
     * array, as well as the input channel.
     *
     * @param nets                  a downlink should be added for these nets
     * @param channel               the channel to add
     * @param downlinksOnTransition how many downlinks should be inscribed to the transition
     */
    private void addDownlinkTransitionForChannelToSystem(
        String[] nets, String channel, int downlinksOnTransition)
    {
        TransitionFigure newTransition = (TransitionFigure) templateSyncTransition.clone();
        CPNTextFigure newInscription = (CPNTextFigure) templateSyncInscription.clone();
        // Add transition inscription
        StringBuilder text = new StringBuilder();
        for (int i = 0; i < downlinksOnTransition; i++) {
            // Modulo if nets contains less nets than number of downlinks (e.g. if concurrent sync is selected)
            text.append("this:getReference(").append(nets[i % nets.length]).append(");\n");
            text.append(nets[i % nets.length]).append(channel).append(";\n");
        }
        newInscription.setText(text.toString());
        addInscription(newTransition, newInscription);
        moveNewFigure(newTransition);
        systemNet.add(newTransition);
    }

    private void addDownlinkTransitionForInscriptionToSystem(String inscription) {
        TransitionFigure newTransition = (TransitionFigure) templateSyncTransition.clone();
        CPNTextFigure newInscription = (CPNTextFigure) templateSyncInscription.clone();
        // Add transition inscription
        newInscription.setText(inscription);
        addInscription(newTransition, newInscription);
        moveNewFigure(newTransition);
        systemNet.add(newTransition);
    }

    /**
     * Move a transition figure in the net
     *
     * @param newTransition this transition should be moved
     */
    private void moveNewFigure(TransitionFigure newTransition) {
        int pixelsToMoveDown = 80;
        int pixelsToMoveRight = 165;
        newTransition.moveBy(rightMover, downMover);
        numSyncTransitions += 1;
        if (numSyncTransitions < 3) {
            downMover += pixelsToMoveDown;
        } else if (numSyncTransitions == 3) {
            downMover = 0;
            rightMover += pixelsToMoveRight;
        } else if (numSyncTransitions < 5) {
            downMover += pixelsToMoveDown;
        } else if (numSyncTransitions % 6 == 0) {
            rightMover += pixelsToMoveRight;
            downMover = -3 * pixelsToMoveDown;
        } else {
            downMover += pixelsToMoveDown;
        }
    }

    /**
     * Add an inscription to an inscribable figure.
     *
     * @param parent this figure should be inscribed
     * @param child  this is the inscription
     */
    private void addInscription(InscribableFigure parent, CPNTextFigure child) {
        systemNet.add(child);
        parent.addChild(child);
        child.setParent(parent);
    }

    /**
     * This method returns the text figure of the arc which transfers
     * net instances from their creation to the reference place.
     *
     * @return the creation arc inscription
     */
    private TextFigure getRefArcText() {
        for (TextFigure text : texts) {
            if (figureHoldsText(text, "netA;netB")) {
                return text;
            }
        }
        return null;
    }

    /**
     * This method returns the text figure of the transition which
     * creates net instances.
     *
     * @return the creation arc inscription
     */
    private TextFigure getCreateText() {
        for (TextFigure text : texts) {
            if (figureHoldsText(text, ".*:new .*")) {
                return text;
            }
        }
        return null;
    }

    /**
     * This method loops through all {@link Figure Figures} of the drawing
     * and sets fields for later access.
     * This allows to reduce computation, as it is not necessary
     * to loop through all figures every time one such
     * figure needs to be accessed.
     *
     * @param drawing drawing to get its figures
     */
    private void setFigureFields(CPNDrawing drawing) {
        transitions = new ArrayList<>();
        places = new ArrayList<>();
        texts = new ArrayList<>();
        FigureEnumeration figureEnumeration = drawing.figures();
        while (figureEnumeration.hasMoreElements()) {
            Figure fig = figureEnumeration.nextFigure();
            if (fig instanceof TransitionFigure transitionFigure) {
                transitions.add(transitionFigure);
            } else if (fig instanceof PlaceFigure placeFigure) {
                places.add(placeFigure);
            } else if (fig instanceof TextFigure textFigure) {
                texts.add(textFigure);
            }
        }
        setTemplateSyncFigures();
    }

    /**
     * This method loops through all transitions and arcs
     * in the template and sets the fields for the figures
     * which are responsible for communication (i.e. the
     * communication transition and its inscription).
     */
    private void setTemplateSyncFigures() {
        for (TransitionFigure transition : transitions) {
            FigureEnumeration children = transition.children();
            while (children.hasMoreElements()) {
                Figure fig = children.nextFigure();
                if (figureHoldsText(fig, ".*:sync().*")) {
                    templateSyncTransition = transition;
                    templateSyncInscription = (CPNTextFigure) fig;
                }
            }
        }
    }

    /**
     * After certain template figures are used,
     * they should be removed from the drawing.
     */
    private void removeTemplateFigures() {
        systemNet.remove(templateSyncTransition);
        systemNet.remove(templateSyncInscription);
    }

    /**
     * Get the ID of the transition which is responsible for
     * creating net instances.
     *
     * @return creation-transition ID
     */
    public Integer getCreateTransitionID() {
        for (TransitionFigure transition : transitions) {
            FigureEnumeration children = transition.children();
            while (children.hasMoreElements()) {
                Figure fig = children.nextFigure();
                if (figureHoldsText(fig, ".*:new .*")) {
                    return transition.getID();
                }
            }
        }
        return null;
    }

    /**
     * Get the ID of the place which holds
     * references to the created net instances.
     *
     * @return place ID
     */
    public Integer getReferencePlaceID() {
        for (PlaceFigure place : places) {
            FigureEnumeration children = place.children();
            while (children.hasMoreElements()) {
                Figure fig = children.nextFigure();
                if (figureHoldsText(fig, "NetReferences")) {
                    return place.getID();
                }
            }
        }
        return null;
    }

    /**
     * Answers whether a figure holds a given text
     *
     * @param fig   does this figure hold a text
     * @param regex the regular expression of the text
     * @return does the figure match the regular expression
     */
    private boolean figureHoldsText(Figure fig, String regex) {
        if (fig instanceof CPNTextFigure) {
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(((CPNTextFigure) fig).getText());
            return m.find();
        }
        return false;
    }

    public CPNDrawing getSystemNet() {
        return systemNet;
    }
}
