package de.renew.gui.nin;

import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

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.PlaceFigure;
import de.renew.gui.TransitionFigure;

/**
 * An instance of this class can unfold a P/T net with
 * synchronous channels into an ordinary P/T net.
 * It follows the algorithm proposed in the thesis Voß(2021).
 * The current implementation assumes that all transitions
 * have a maximum of one inscription.
 *
 * @author Lukas Voß
 */
public class PTCUnfolder {

    PTCTransitionBuilder ptcTransitionBuilder;
    PTCNetInformation ptcNetInformation;

    /**
     * This method is the entry point to unfold a net system
     * which consists of multiple {@link Drawing Net Drawings}.
     * It creates a new {@link CPNDrawing} out of all input drawings
     * and hands over that drawing to the single unfold method.
     *
     * @param drawings unfold a net system consisting of these drawings
     * @return an unfolding of the net system
     */
    public CPNDrawing unfold(
        Enumeration<Drawing> drawings, boolean allowUplinkToUplinkSynchronization)
    {
        CPNDrawing combinedDrawing;
        try {
            combinedDrawing = (CPNDrawing) StorableApi.cloneStorable(drawings.nextElement());
            while (drawings.hasMoreElements()) {
                Drawing drawing = StorableApi.cloneStorable(drawings.nextElement());
                combinedDrawing.add(drawing);
                int xMover = rightmostFigureInDrawing(combinedDrawing);
                FigureEnumeration figures = drawing.figures();
                while (figures.hasMoreElements()) {
                    Figure fig = figures.nextFigure();
                    if (fig instanceof PlaceFigure || fig instanceof TransitionFigure) {
                        fig.moveBy(xMover, 0);
                    }
                    combinedDrawing.add(fig);
                }
            }
        } catch (Exception e) {
            return null;
        }
        return unfold(combinedDrawing, allowUplinkToUplinkSynchronization);
    }

    /**
     * This method is responsible for unfolding P/T nets with synchronous
     * channels into ordinary P/T nets.
     * It does this by building a transition for each possible synchronization
     * that can take place.
     *
     * @param ptc_netdrawing drawing of the net to unfold.
     * @return an unfolded version of the original drawing, if no error occur.
     * If errors occur, e.g. in cloning the drawing, the original is returned.
     */
    public CPNDrawing unfold(
        CPNDrawing ptc_netdrawing, boolean allowUplinkToUplinkSynchronization)
    {

        // Null check
        if (!isValidInput(ptc_netdrawing)) {
            return ptc_netdrawing;
        }

        // Clone Drawing
        CPNDrawing unfoldedDrawing;
        try {
            unfoldedDrawing = (CPNDrawing) StorableApi.cloneStorable(ptc_netdrawing);
            unfoldedDrawing.setName("unfolded" + unfoldedDrawing.getName());
        } catch (Exception e) {
            return ptc_netdrawing;
        }

        // Set fields for later use
        ptcNetInformation = new PTCNetInformation(unfoldedDrawing);
        ptcTransitionBuilder = new PTCTransitionBuilder(unfoldedDrawing, ptcNetInformation);

        if (!allowUplinkToUplinkSynchronization) {
            for (TransitionFigure uplinkTransition : ptcNetInformation.getInitialUplinkTransitions()
                .keySet()) {
                // Build transition for up- and downlink
                for (TransitionFigure downlinkTransition : ptcNetInformation
                    .getInitialDownlinkTransitions().keySet()) {
                    if (ptcNetInformation.getInitialDownlinkTransitions().get(downlinkTransition)
                        .equals(
                            ptcNetInformation.getInitialUplinkTransitions()
                                .get(uplinkTransition))) {
                        ptcTransitionBuilder.buildTransitionIfBindingIsPossible(
                            uplinkTransition, downlinkTransition);
                    }
                }
            }
        } else {
            Set<TransitionFigure> alreadyConsideredUplinks = new HashSet<>();
            for (TransitionFigure uplinkTransition : ptcNetInformation.getInitialUplinkTransitions()
                .keySet()) {
                alreadyConsideredUplinks.add(uplinkTransition);
                for (TransitionFigure partnerUplinkTransition : ptcNetInformation
                    .getInitialUplinkTransitions().keySet()) {
                    // Build transition for up- and uplink
                    if (!alreadyConsideredUplinks.contains(partnerUplinkTransition)
                        && ptcNetInformation.getInitialUplinkTransitions()
                            .get(partnerUplinkTransition).equals(
                                ptcNetInformation.getInitialUplinkTransitions()
                                    .get(uplinkTransition))) {
                        ptcTransitionBuilder.buildTransitionIfBindingIsPossible(
                            uplinkTransition, partnerUplinkTransition);
                    }
                }
            }
        }
        // Build a transition for each possible synchronization
        removeUnfoldedTransitions(unfoldedDrawing);
        return unfoldedDrawing;
    }

    /**
     * Method to check whether or not the given drawing and net
     * are valid inputs for the unfolding.
     * TODO: Check if the input net is a syntactically correct P/T net with channels.
     *
     * @param ptc_netdrawing the drawing to check
     * @return whether those are valid input for unfolding
     */
    private boolean isValidInput(Drawing ptc_netdrawing) {
        return ptc_netdrawing instanceof CPNDrawing;
    }

    /**
     * Returns the rightmost position that is occupied by
     * a {@link Figure} in the drawing.
     *
     * @param drawing determine this drawing's rightmost figure
     * @return rightmost x-coordinate
     */
    private int rightmostFigureInDrawing(CPNDrawing drawing) {
        FigureEnumeration figures = drawing.figures();
        int rightmostPosition = 0;
        while (figures.hasMoreElements()) {
            Figure fig = figures.nextFigure();
            int xPositionOfFigure = fig.displayBox().x;
            if (xPositionOfFigure > rightmostPosition) {
                rightmostPosition = xPositionOfFigure;
            }
        }
        return rightmostPosition;
    }

    /**
     * After all transitions with up-/downlinks have been unfolded,
     * they need to be removed from the drawing.
     *
     * @param unfoldedDrawing remove all transitions inscribed with
     *                        up-/downlinks from this drawing
     */
    private void removeUnfoldedTransitions(Drawing unfoldedDrawing) {
        for (TransitionFigure uplinkTransition : ptcNetInformation.getInitialUplinkTransitions()
            .keySet()) {
            unfoldedDrawing.remove(uplinkTransition);
        }
        for (TransitionFigure downlinkTransition : ptcNetInformation.getInitialDownlinkTransitions()
            .keySet()) {
            unfoldedDrawing.remove(downlinkTransition);
        }
    }
}
