package de.renew.gui.pnml.creator;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.w3c.dom.Element;

import de.renew.draw.storables.ontology.Figure;
import de.renew.draw.storables.ontology.FigureEnumeration;
import de.renew.gui.ArcConnection;
import de.renew.gui.CPNDrawing;
import de.renew.gui.CPNTextFigure;
import de.renew.gui.DeclarationFigure;
import de.renew.gui.PlaceFigure;
import de.renew.gui.TransitionFigure;
import de.renew.gui.VirtualPlaceFigure;
import de.renew.gui.VirtualTransitionFigure;
import de.renew.gui.pnml.converter.Converter;


/**
 * Creator of a PNML {@code net} tag for a given {@link CPNDrawing}.
 * The PNML net type (stored in the {@code type} attribute at the created {@code net} tag)
 * is given in {@code NetCreator(String netType)} below.
 * The {@link CPNDrawing} is given to {@link #createElement(PNMLCreator, Object)}.
 */
public class NetCreator extends ElementCreator {
    private static final org.apache.log4j.Logger logger =
        org.apache.log4j.Logger.getLogger(NetCreator.class);
    private final List<PlaceFigure> placeList = new ArrayList<>();

    /**
     * Creates a new {@link NetCreator} for the given PNML net type.
     * It will get stored in the {@code type} attribute at the created {@code net} tag.
     *
     * @param netType  of which an converter is being instantiated
     * @param toolInsc weither the tool inscription should be converted or not
     */
    public NetCreator(String netType, boolean toolInsc) {
        // Create a new Converter so its shadow net is not yet set.
        super("net", new Converter(netType, toolInsc));
    }

    private void connectNewStart(VirtualPlaceFigure virPlace, ArcConnection arc) {
        while (virPlace.getSemanticFigure() instanceof VirtualPlaceFigure) {
            virPlace = (VirtualPlaceFigure) virPlace.getSemanticFigure();
        }
        PlaceFigure figure = virPlace.getSemanticFigure();
        arc.disconnectStart();
        arc.connectStart(figure.connectorAt(figure.center()));
    }

    private void connectNewStart(VirtualTransitionFigure virTransition, ArcConnection arc) {
        while (virTransition.getSemanticFigure() instanceof VirtualTransitionFigure) {
            virTransition = (VirtualTransitionFigure) virTransition.getSemanticFigure();
        }
        TransitionFigure figure = virTransition.getSemanticFigure();
        arc.disconnectStart();
        arc.connectStart(figure.connectorAt(figure.center()));
    }

    private void connectNewEnd(VirtualPlaceFigure virPlace, ArcConnection arc) {
        while (virPlace.getSemanticFigure() instanceof VirtualPlaceFigure) {
            virPlace = (VirtualPlaceFigure) virPlace.getSemanticFigure();
        }
        PlaceFigure figure = virPlace.getSemanticFigure();
        arc.disconnectEnd();
        arc.connectEnd(figure.connectorAt(figure.center()));
    }

    private void connectNewEnd(VirtualTransitionFigure virTransition, ArcConnection arc) {
        while (virTransition.getSemanticFigure() instanceof VirtualTransitionFigure) {
            virTransition = (VirtualTransitionFigure) virTransition.getSemanticFigure();
        }
        TransitionFigure figure = virTransition.getSemanticFigure();
        arc.disconnectEnd();
        arc.connectEnd(figure.connectorAt(figure.center()));
    }

    /**
     * Creates a PNML net tag containing all net elements
     * that is then returned as an {@link Element}.
     * The contained net elements are generated by their respective
     * {@link ElementCreator} implementations.
     * This method expects a {@link CPNDrawing} to have been given to
     * {@link ElementCreator#createElement(PNMLCreator, Object)}.
     */
    @Override
    protected Element doCreateElement(PNMLCreator pnmlCreator) {
        Element netEle = super.doCreateElement(pnmlCreator);
        CPNDrawing net = (CPNDrawing) getObject();
        Converter converter = getConverter();
        converter.setShadowNet(net);

        String netID = converter.getNetID();
        String netType = converter.getType();

        netEle.setAttribute("id", "net_" + netID);
        netEle.setAttribute("type", netType);

        // create name
        AttributeCreator name = new AttributeCreator(pnmlCreator, "name");
        netEle.appendChild(name.createElement(net.getName()));

        Element pageEle;

        // Do we want to create a page tag within the net tag?
        if (converter.getNetConverter().createPageTag()) {
            // Yes, create and append one.
            // All other tags get nested in this page tag.
            pageEle = pnmlCreator.createElement("page");
            pageEle.setAttribute("id", "page_" + netID);
            netEle.appendChild(pageEle);
        } else {
            // No, just put everything directly into the net tag.
            pageEle = netEle;
        }


        FigureEnumeration figures = net.figures();


        // Map from place IDs to the annotations of the virtual places that link it
        Map<Integer, List<Figure>> virtualPlaceAnnotations = new HashMap<>();
        // Map from transition IDs to the annotations of the virtual transitions that link it
        Map<Integer, List<Figure>> virtualTransitionAnnotations = new HashMap<>();

        // Fill the map by iterating over all figures...
        while (figures.hasMoreElements()) {
            Figure figure = figures.nextElement();

            // ...and if the figure is a virtual place,
            // add all of its annotations into the map for the original place's ID
            if (figure instanceof VirtualPlaceFigure virPlace) {
                VirtualPlaceCreator virCreator = new VirtualPlaceCreator(converter);
                Element toolSpec = pnmlCreator.createToolspecific();
                toolSpec.appendChild(virCreator.createElement(pnmlCreator, virPlace));
                pageEle.appendChild(toolSpec);
                // ID of original place
                Integer orgid = virPlace.getSemanticFigure().getID();


                // List of annotations
                List<Figure> annos;
                if (virtualPlaceAnnotations.containsKey(orgid)) {
                    annos = virtualPlaceAnnotations.get(orgid);
                } else {
                    annos = new ArrayList<>();
                    virtualPlaceAnnotations.put(orgid, annos);
                }

                // Add this virtual place's annotations:
                FigureEnumeration figureenu = virPlace.children();
                while (figureenu.hasMoreElements()) {
                    Figure nextFig = figureenu.nextFigure();
                    annos.add(nextFig);
                }
            } else if (figure instanceof VirtualTransitionFigure virTransition) {
                // ...and if the figure is a virtual transition,
                // add all of its annotations into the map for the original transition's ID

                VirtualTransitionCreator virCreator = new VirtualTransitionCreator(converter);
                Element toolSpec = pnmlCreator.createToolspecific();
                toolSpec.appendChild(virCreator.createElement(pnmlCreator, virTransition));
                pageEle.appendChild(toolSpec);
                // ID of original transition
                Integer orgid = virTransition.getSemanticFigure().getID();


                // List of annotations
                List<Figure> annos;
                if (virtualTransitionAnnotations.containsKey(orgid)) {
                    annos = virtualTransitionAnnotations.get(orgid);
                } else {
                    annos = new ArrayList<>();
                    virtualTransitionAnnotations.put(orgid, annos);
                }

                // Add this virtual transition's annotations:
                FigureEnumeration figureenu = virTransition.children();
                while (figureenu.hasMoreElements()) {
                    Figure nextFig = figureenu.nextFigure();
                    annos.add(nextFig);
                }
            }
        }

        // Iterate over all figures again...
        figures = net.figures();
        while (figures.hasMoreElements()) {
            Figure figure = figures.nextElement();
            if (figure instanceof PlaceFigure place && !(figure instanceof VirtualPlaceFigure)) {
                PlaceCreator placeCreator = new PlaceCreator(converter);
                placeList.add(place);
                Element pnmlPlace = placeCreator.createElement(pnmlCreator, figure);


                // Get the annotation of virtual places linking this place...
                List<Figure> virtAnnos = virtualPlaceAnnotations.get(place.getID());

                // ...and add them to the PNML:
                if (virtAnnos != null) {
                    for (Figure fig : virtAnnos) {
                        pnmlPlace.appendChild(
                            getNetConverter()
                                .annotationToPNML((CPNTextFigure) fig, pnmlCreator, true));
                    }
                }
                pageEle.appendChild(pnmlPlace);
            } else if (figure instanceof TransitionFigure transition
                && !(figure instanceof VirtualTransitionFigure)) {
                TransitionCreator transitionCreator = new TransitionCreator(converter);
                Element pnmlTransition = transitionCreator.createElement(pnmlCreator, figure);
                // Get the annotation of virtual transition linking this transition...
                List<Figure> virtAnnos = virtualTransitionAnnotations.get(transition.getID());

                // ...and add them to the PNML:
                if (virtAnnos != null) {
                    for (Figure fig : virtAnnos) {
                        pnmlTransition.appendChild(
                            getNetConverter()
                                .annotationToPNML((CPNTextFigure) fig, pnmlCreator, true));
                    }
                }
                pageEle.appendChild(pnmlTransition);
            } else if (figure instanceof ArcConnection arc) {
                Figure sourceFigure = arc.startFigure();
                Figure targetFigure = arc.endFigure();
                ArcCreator arcCreator = new ArcCreator(getConverter());
                if (sourceFigure instanceof VirtualPlaceFigure virPlace) {
                    connectNewStart(virPlace, arc);
                    arcCreator.setVirStart(virPlace);
                } else if (sourceFigure instanceof VirtualTransitionFigure virTransition) {
                    connectNewStart(virTransition, arc);
                    arcCreator.setVirStart(virTransition);
                }
                if (targetFigure instanceof VirtualPlaceFigure virPlace) {
                    connectNewEnd(virPlace, arc);
                    arcCreator.setVirEnd(virPlace);
                } else if (targetFigure instanceof VirtualTransitionFigure virTransition) {
                    connectNewEnd(virTransition, arc);
                    arcCreator.setVirEnd(virTransition);
                }
                pageEle.appendChild(arcCreator.createElement(pnmlCreator, figure));

                arc.disconnectStart();
                arc.connectStart(sourceFigure.connectorAt(sourceFigure.center()));
                arc.disconnectEnd();
                arc.connectEnd(targetFigure.connectorAt(targetFigure.center()));
            } else if (figure instanceof DeclarationFigure) {
                pageEle.appendChild(
                    converter.getNetConverter()
                        .annotationToPNML((DeclarationFigure) figure, pnmlCreator, false));
            } else if (figure instanceof CPNTextFigure) {
                if (((CPNTextFigure) figure).parent() == null) {
                    logger.error("NetCreator: Unknown type -> " + figure.getClass());
                }
            }
        }

        return netEle;
    }

    /**
     * A list of the visited place figures in the order the corresponding
     * PNML elements were created.
     */
    List<PlaceFigure> getPlaceList() {
        return placeList;
    }
}