package de.renew.gui;

import java.io.Serializable;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;

import CH.ifa.draw.framework.FigureWithID;
import CH.ifa.draw.standard.CompositeFigure;
import de.renew.draw.storables.ontology.Drawing;
import de.renew.draw.storables.ontology.Figure;
import de.renew.draw.storables.ontology.FigureEnumeration;
import de.renew.net.NetElementID;
import de.renew.remote.NetAccessor;
import de.renew.remote.NetInstanceAccessor;
import de.renew.remote.ObjectAccessor;
import de.renew.remote.PlaceInstanceAccessor;
import de.renew.remote.TransitionInstanceAccessor;


/**
 * The net instance element lookup class maps figures to net instance
 * elements. Since there may be several net instance elements for one
 * figure, the figures are mapped to Hashtables of group id and net
 * instance element pairs. The class provides functionality for x to 1
 * mappings like for places and transitions, and functionality for x
 * to n mappings like tasks and other aggregations. x usually is 1 in
 * these cases, but n is also supported, of course.
 */
public class NetInstanceElementLookup {
    private static final org.apache.log4j.Logger logger =
        org.apache.log4j.Logger.getLogger(NetInstanceElementLookup.class);

    /**
     * The figure lookup mapping figure ids to lists of figures.
     */
    private final Hashtable<Integer, List<Figure>> figureLookup = new Hashtable<>();

    /**
     * The lookup mapping figures to hashtables, which map
     * net element group ids to net instance elements.
     */
    private final Hashtable<Figure, Hashtable<Serializable, ObjectAccessor>> lookup =
        new Hashtable<>();

    /**
     * Creates a new net instance element lookup.
     * Tries to map graphical figures of an open CPNDrawing
     * belonging to a given instance drawing
     * to net elements of the given net instance.
     *
     * @param netInstance The net instance.
     * @throws RemoteException If an RMI problem occurred.
     */
    public NetInstanceElementLookup(NetInstanceAccessor netInstance) throws RemoteException {
        NetAccessor net = netInstance.getNet();
        String netName = net.getName();
        CPNDrawing netDrawing =
            ModeReplacement.getInstance().getDrawingLoader().getDrawing(netName);
        if (netDrawing == null) {
            logger.error("No open drawing found matching the compiled net \"" + netName + "\"");
        } else {
            addToFigureLookup(netDrawing.figures(), figureLookup);
            buildAndCheckLookup(netDrawing, netInstance);
        }
    }

    /**
     * Creates a new net instance element lookup.
     * Tries to map graphical figures of a given Drawing
     * to net elements of the given net instance.
     *
     * @param netDrawing The net drawing.
     * @param netInstance The net instance.
     * @throws RemoteException If an RMI problem occurred.
     */
    public NetInstanceElementLookup(Drawing netDrawing, NetInstanceAccessor netInstance)
        throws RemoteException
    {
        if (netDrawing == null) {
            logger.error(
                "No drawing provided for the compiled net \"" + netInstance.getNet().getName()
                    + "\"");
        } else {
            addToFigureLookup(netDrawing.figures(), figureLookup);
            buildAndCheckLookup(netDrawing, netInstance);
        }
    }

    /**
     * Build the lookup and check for net elements without corresponding
     * graphical figures and graphical figures without corresponding net
     * elements. Report the missing figures/elements results via logger
     * warning.
     *
     * @param netDrawing the template drawing (not instance drawing) representing the net instance
     * @param netInstance the net instance
     * @throws RemoteException If an RMI problem occurred.
     */
    private void buildAndCheckLookup(Drawing netDrawing, NetInstanceAccessor netInstance)
        throws RemoteException
    {
        boolean missingFigure = buildLookup(netInstance);
        boolean missingNetElement = checkForMissingNetElements();

        String netName = netDrawing.getName();
        if (missingFigure) {
            logger.warn(
                "The compiled net" + " contains one or more net elements with no"
                    + " corresponding graphical element in the drawing \"" + netName + "\".");
            logger.warn("These net elements will not be displayed.");
        }
        if (missingNetElement) {
            logger.warn(
                "The drawing \"" + netName + "\" contains one or more graphical"
                    + " elements with no corresponding net element.");
            logger.warn("These graphical elements will not be functional.");
        }
    }

    /**
     * Returns an enumeration of all figures in this lookup.
     * @return The enumeration.
     */
    public Enumeration<Figure> getFigures() {
        return lookup.keys();
    }

    /**
     * For a given figure, it returns a lookup that maps net
     * element group ids to net elements.
     * @param figure The figure.
     * @return The lookup.
     */
    public Hashtable<Serializable, ObjectAccessor> getNetElements(FigureWithID figure) {
        return lookup.get(figure);
    }

    /**
     * Adds a figure to place instance relation to the lookup.
     * @param figure The figure.
     * @param placeInstance The place instance.
     * @exception RemoteException If an RMI problem occurred.
     */
    private void put(FigureWithID figure, PlaceInstanceAccessor placeInstance)
        throws RemoteException
    {
        Hashtable<Serializable, ObjectAccessor> groupIdToNetElementMap = getNetElements(figure);
        if (groupIdToNetElementMap == null) {
            groupIdToNetElementMap = new Hashtable<>();
            lookup.put(figure, groupIdToNetElementMap);
        }
        groupIdToNetElementMap.put(placeInstance.getPlace().getID().getGroupID(), placeInstance);
    }

    /**
     * Adds a figure to transition instance relation to the lookup.
     * @param figure The figure.
     * @param transitionInstance The transition instance.
     * @exception RemoteException If an RMI problem occurred.
     */
    private void put(FigureWithID figure, TransitionInstanceAccessor transitionInstance)
        throws RemoteException
    {
        Hashtable<Serializable, ObjectAccessor> groupIdToNetElementMap = getNetElements(figure);
        if (groupIdToNetElementMap == null) {
            groupIdToNetElementMap = new Hashtable<>();
            lookup.put(figure, groupIdToNetElementMap);
        }
        groupIdToNetElementMap
            .put(transitionInstance.getTransition().getID().getGroupID(), transitionInstance);
    }

    /**
     * Build the actual lookup and report (via logger warning) net elements
     * that have no corresponding graphical element.
     *
     * @param netInstance The net instance
     * @return true if there are net elements without corresponding graphical elements
     * @throws RemoteException If an RMI problem occurred.
     */
    private boolean buildLookup(NetInstanceAccessor netInstance) throws RemoteException {
        boolean missingFigure = false;

        // Fill the net element lookup with places
        NetElementID[] placeIDs = netInstance.getNet().getPlaceIDs();
        for (NetElementID placeID : placeIDs) {
            List<Figure> figures = figureLookup.get(mapNetElementIdToFigureId(placeID));
            if (figures == null) {
                logger.warn(
                    "The place with figure id " + placeID.getFigureID()
                        + " has no corresponding graphical element.");
                missingFigure = true;
            } else {
                for (Figure value : figures) {
                    FigureWithID figure = (FigureWithID) value;
                    put(figure, netInstance.getPlaceInstance(placeID));
                }
            }
        }

        // Fill the net element lookup with transitions
        NetElementID[] transitionIDs = netInstance.getNet().getTransitionIDs();
        for (NetElementID transitionID : transitionIDs) {
            List<Figure> figures = figureLookup.get(mapNetElementIdToFigureId(transitionID));
            if (figures == null) {
                logger.warn(
                    "The transition with figure id " + transitionID.getFigureID()
                        + " has no corresponding graphical element.");
                missingFigure = true;
            } else {
                for (Figure value : figures) {
                    FigureWithID figure = (FigureWithID) value;
                    put(figure, netInstance.getTransitionInstance(transitionID));
                }
            }
        }

        return missingFigure;
    }

    /**
     * Map a net element id (with two components) to a figure id (single int).
     * By default, the figure id component of the net element id is used.
     * May be overridden for a different behavior.
     *
     * @param netElementID the net element id
     * @return the figure id for the given net element id
     */
    protected int mapNetElementIdToFigureId(NetElementID netElementID) {
        return netElementID.getFigureID();
    }

    /**
     * Check the lookup for missing net elements (figures in the drawing for
     * which no net element exist). Reports the missing net elements via a
     * logger warning.
     *
     * @return true if there are missing net elements
     */
    private boolean checkForMissingNetElements() {
        boolean missingNetElement = false;

        // Check if any figure has no net elements
        Enumeration<List<Figure>> figuresEnum = figureLookup.elements();
        while (figuresEnum.hasMoreElements()) {
            for (Figure value : figuresEnum.nextElement()) {
                FigureWithID figure = (FigureWithID) value;
                Hashtable<Serializable, ObjectAccessor> groupIdToNetElementMap =
                    getNetElements(figure);
                if (groupIdToNetElementMap == null || groupIdToNetElementMap.isEmpty()) {
                    logger.warn(
                        figure.getClass().getName() + " with id " + figure.getID()
                            + " has no corresponding net element.");
                    missingNetElement = true;
                }
            }
        }

        return missingNetElement;
    }


    /**
     * Builds up a mapping for figure ids to figures for the elements
     * of a given figure enumeration.
     * @param figuresEnum The figure enumeration.
     * @param figureLookup The figure lookup to be filled with the mapping.
     */
    protected void addToFigureLookup(
        FigureEnumeration figuresEnum, Hashtable<Integer, List<Figure>> figureLookup)
    {
        while (figuresEnum.hasMoreElements()) {
            Figure figure = figuresEnum.nextElement();
            if (figure instanceof CompositeFigure compositeFigure) {
                addToFigureLookup(compositeFigure.figures(), figureLookup);
            } else if (figure instanceof SimulableFigure simulableFigure) {
                int id = simulableFigure.getSemanticFigure().getID();
                figureLookup.computeIfAbsent(id, ArrayList::new).add(figure);
            }
        }
    }
}