/*
 * Created: 06.05.2004
 *
 */

package de.renew.application;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import org.apache.log4j.Logger;

import de.renew.plugin.load.AbstractPluginLoader;
import de.renew.simulator.api.SimulationManager;
import de.renew.simulatorontology.loading.PathlessFinder;
import de.renew.simulatorontology.shadow.ShadowCompilationResult;
import de.renew.simulatorontology.shadow.ShadowNet;
import de.renew.simulatorontology.shadow.ShadowNetSystem;
import de.renew.simulatorontology.simulation.SimulationEnvironment;
import de.renew.simulatorontology.simulation.SimulatorExtension;
import de.renew.util.ObjectInputStreamUsingBottomLoader;

/**
 * Special Extension that allows a plugin to add its Nets to a running
 * Simulation. Nets are loaded from the resources of the plugin.
 * <p>
 * The feature provided by this extension is experimental! The plugin's nets are
 * inserted into the simulation during the first call to
 * {@link SimulationManager#addShadowNetSystem(ShadowNetSystem)}, which leads to a recursive call of that
 * method. As a result, other {@link SimulatorExtension} implementations may
 * observe the wrong order of net insertions.
 * </p>
 * <p>
 * TODO: It would be better if this extension uses the
 * {@link de.renew.net.loading.NetLoader} interface instead of compiling all
 * nets at simulation setup.
 * </p>
 *
 * @author Timo Carl
 * @author (additional comments by Michael Duvigneau)
 */
public class AddPluginNetsExtension implements SimulatorExtension, PathlessFinder {
    /**
     * The logger for this class.
     */
    private static final Logger LOGGER = Logger.getLogger(AddPluginNetsExtension.class);
    private JarFile[] _jarRes = null;
    private Map<String, ShadowNetSystem> _netSnsMap = null;

    private static final String SNS_EXTENSION = ".sns";

    /**
     * Creates the extension.
     *
     * @param resPath the <code>URL</code> to the <code>.jar</code> file
     *                where the nets reside.
     */
    public AddPluginNetsExtension(URL resPath) {
        LOGGER.debug("AddPluginNetsExtension with URL " + resPath);
        try {
            URL[] urls = AbstractPluginLoader.unifyURL(resPath);

            _jarRes = new JarFile[urls.length];

            for (int i = 0; i < urls.length; i++) {
                _jarRes[i] = new JarFile(new File(urls[i].toURI()));
            }
        } catch (IOException | URISyntaxException e) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.warn("Resource path " + resPath + " is not a valid jar file: " + e, e);
            } else {
                LOGGER.warn("Resource path " + resPath + " is not a valid jar file.");
            }
        }
    }

    @Override
    public void simulationSetup(SimulationEnvironment env) {
        // reset sns index so it can be rebuilt on first use.
        synchronized (this) {
            _netSnsMap = null;
        }
    }

    @Override
    public void netsCompiled(ShadowCompilationResult result) {
        // no longer interested in this event?
    }


    /**
     * Processes Shadow Net System files ending in ".sns" found within the plugin's JAR resources and creates
     * a mapping for each net contained in these files.
     *
     * @return A Map where keys are net names (String) and values are their
     * corresponding {@link ShadowNetSystem} instances.
     */
    protected synchronized Map<String, ShadowNetSystem> getNetSnsMap() {
        if (_netSnsMap != null) {
            return _netSnsMap;
        }
        _netSnsMap = new HashMap<>();
        for (JarFile jf : _jarRes) {
            LOGGER.debug("AddPluginNetsExtension: Building net-to-sns map for " + jf.getName());


            // assumes that jarRes is valid because Plugin would not
            // be functional.
            Enumeration<JarEntry> entries = jf.entries();
            String fileName = null;
            while (entries.hasMoreElements()) {
                try {
                    ZipEntry element = entries.nextElement();
                    fileName = element.toString();
                    if (!element.getName().endsWith(SNS_EXTENSION)) {
                        continue;
                    }
                    Object o = new ObjectInputStreamUsingBottomLoader(jf.getInputStream(element))
                        .readObject();
                    if (!(o instanceof ShadowNetSystem newSns)) {
                        continue;
                    }
                    LOGGER.debug("AddPluginNetsExtension: adding sns file " + fileName);
                    for (ShadowNet net : newSns.elements()) {
                        String netName = net.getName();
                        if (_netSnsMap.containsKey(netName)) {
                            LOGGER.warn(
                                "AddPluginNetsExtension: Skipping duplicate net definition for "
                                    + netName + " in sns file " + fileName + " of jar "
                                    + Arrays.toString(_jarRes) + ".");
                        } else {
                            _netSnsMap.put(net.getName(), newSns);
                        }
                    }
                    fileName = null;
                } catch (ClassNotFoundException | IOException e) {
                    LOGGER.error(
                        "AddPluginNetsExtension: Could not read shadow net system from file "
                            + fileName + " within jar  " + Arrays.toString(_jarRes) + " due to " + e
                            + ".",
                        e);
                }
            }
        }
        return _netSnsMap;
    }

    @Override
    public void simulationTerminated() {
        // no longer interested in this event?
    }

    @Override
    public void simulationTerminating() {
        // Nothing to do ?
    }

    @Override
    public ShadowNetSystem findNetFile(final String name) {
        if (LOGGER.isDebugEnabled() && getNetSnsMap().containsKey(name)) {
            LOGGER.debug(
                "AddPluginNetsExtension: providing sns for net " + name + ": "
                    + getNetSnsMap().get(name) + ".");
        }
        return getNetSnsMap().get(name);
    }
}