package de.renew.gui;

import java.awt.Dimension;
import java.awt.Point;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.List;

import CH.ifa.draw.framework.ParentFigure;
import de.renew.draw.storables.ontology.Drawing;
import de.renew.draw.storables.ontology.Figure;
import de.renew.simulatorontology.shadow.ShadowArc;
import de.renew.simulatorontology.shadow.ShadowDeclarationNode;
import de.renew.simulatorontology.shadow.ShadowInscribable;
import de.renew.simulatorontology.shadow.ShadowInscription;
import de.renew.simulatorontology.shadow.ShadowNet;
import de.renew.simulatorontology.shadow.ShadowNetElement;
import de.renew.simulatorontology.shadow.ShadowNetSystem;
import de.renew.simulatorontology.shadow.ShadowNode;
import de.renew.simulatorontology.shadow.ShadowTransition;
import de.renew.util.ObjectInputStreamUsingBottomLoader;


public class ShadowNetSystemRenderer {
    private static final org.apache.log4j.Logger logger =
        org.apache.log4j.Logger.getLogger(ShadowNetSystemRenderer.class);

    /**
     * Reads a ShadowNetSystem from an URL.
     *
     * Modification due to Modular Renew: The ShadowNetSystem gets loaded by two different ClassLoader,
     *  so the ObjectInputStream needs to be told to use the PluginManager for getting the right Classes.
     * @param location URL to the ShadowNetSystem File
     * @return the ShadowNetSystem from the URL
     */
    public static ShadowNetSystem readShadowNetSystem(URL location) {
        ShadowNetSystem netSystem = null;
        try (InputStream stream = location.openStream()) {
            ObjectInput input = new ObjectInputStreamUsingBottomLoader(stream);
            netSystem = (ShadowNetSystem) input.readObject();
        } catch (IOException | ClassNotFoundException e) {
            logger.error(e.getMessage(), e);
            logger.error("Could not load net system from " + location + ".");
        }
        // ignore it.
        return netSystem;
    }

    /**
     * Renders a file into Drawings.
     *
     * @param location a valid location of a file
     * @return a List of Drawings, empty if the file could not be read successfully
     */
    public static List<Drawing> render(URL location) {
        ShadowNetSystem netSystem = readShadowNetSystem(location);

        if (netSystem == null) {
            return new ArrayList<>();
        }

        Collection<ShadowNet> nets = netSystem.elements();
        List<Drawing> drawings = new ArrayList<>(nets.size());
        for (ShadowNet net : nets) {
            drawings.add(new ShadowNetRenderer(net).drawing);
        }
        return drawings;
    }

    private static class ShadowNetRenderer {
        final CPNDrawing drawing = new CPNDrawing();
        private final Hashtable<ShadowInscribable, ParentFigure> lookup = new Hashtable<>();
        private final Point loc = new Point(100, 20);

        ShadowNetRenderer(ShadowNet net) {
            render(net);
        }

        private void render(ShadowNet net) {
            drawing.setName(net.getName());
            for (ShadowNetElement element : net.elements()) {
                if (element instanceof ShadowNode shadowNode) {
                    renderNode(shadowNode);
                }
                if (element instanceof ShadowDeclarationNode declarationNode) {
                    renderDeclarationNode(declarationNode);
                }
            }
            for (ShadowNetElement element : net.elements()) {
                if (element instanceof ShadowArc shadowArc) {
                    renderArc(shadowArc);
                }
            }
            for (ShadowNetElement element : net.elements()) {
                if (element instanceof ShadowInscription shadowInscription) {
                    renderInscription(shadowInscription);
                }
            }
        }

        private void renderDeclarationNode(ShadowDeclarationNode declNode) {
            DeclarationFigure createdFig = new DeclarationFigure();
            createdFig.setText(declNode.getInscription());
            drawing.add(createdFig);
        }

        private void renderNode(ShadowNode node) {
            Dimension dim;
            ParentFigure createdFig;
            if (node instanceof ShadowTransition) {
                createdFig = new TransitionFigure();
                dim = TransitionFigure.defaultDimension();
            } else {
                createdFig = new PlaceFigure();
                dim = PlaceFigure.defaultDimension();
            }
            drawing.add(createdFig);
            if (node.getName() != null) {
                CPNTextFigure nameFig = new CPNTextFigure(CPNTextFigure.NAME);
                nameFig.setText(node.getName());
                int nameWidth = nameFig.displayBox().width;
                if (nameWidth + 8 > dim.width) {
                    dim.width = nameWidth + 8;
                }
                drawing.add(nameFig);
                nameFig.setParent(createdFig);
            }
            Point rightLower = new Point(dim.width, dim.height);
            createdFig.displayBox(new Point(), rightLower);
            createdFig.moveBy(loc.x, loc.y);

            lookup.put(node, createdFig);
            createdFig.setAttribute("TraceMode", node.getTrace());
        }

        private void renderArc(ShadowArc arc) {
            int shadowArcType = arc.getShadowArcType();

            Figure startFig = lookup.get(arc.getPlace());
            Figure endFig = lookup.get(arc.getTransition());
            if (!arc.isPlaceToTransition()) {
                Figure helpFig = startFig;
                startFig = endFig;
                endFig = helpFig;
            }

            ArcConnection arcFig = switch (shadowArcType) {
                case ShadowArc.ORDINARY, ShadowArc.TEST, ShadowArc.BOTH ->
                    new ArcConnection(shadowArcType);
                case ShadowArc.INHIBITOR -> new InhibitorConnection();
                case ShadowArc.DOUBLE_ORDINARY -> new DoubleArcConnection();
                case ShadowArc.DOUBLE_HOLLOW -> new HollowDoubleArcConnection();
                default -> throw new RuntimeException("Bad shadow arc type.");
            };

            arcFig.startPoint(0, 0);
            arcFig.endPoint(0, 0);
            drawing.add(arcFig);
            lookup.put(arc, arcFig);
            arcFig.connectStart(startFig.connectorAt(0, 0));
            arcFig.connectEnd(endFig.connectorAt(0, 0));
            arcFig.updateConnection();
            arcFig.setAttribute("TraceMode", arc.getTrace());
        }

        private void renderInscription(ShadowInscription inscription) {
            CPNTextFigure inscrFig = new CPNTextFigure(CPNTextFigure.INSCRIPTION);
            inscrFig.setText(inscription.getInscription());
            ParentFigure parent = lookup.get(inscription.getInscribable());
            drawing.add(inscrFig);
            inscrFig.setParent(parent);
            inscrFig.setAttribute("TraceMode", inscription.getTrace());
            if (parent instanceof ArcConnection) {
                inscrFig.setAttribute("FillColor", CH.ifa.draw.util.ColorMap.BACKGROUND);
            }
        }
    }
}