package de.renew.plugin;

import java.lang.module.Configuration;
import java.lang.module.ModuleFinder;
import java.lang.reflect.Field;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Set;

import de.renew.plugin.command.LoadCommandTest;
import de.renew.plugin.jpms.impl.ModuleManager;
import de.renew.plugin.locate.PluginJarLocationFinder;
import de.renew.plugin.locate.PluginLocationFinders;

import static java.lang.ModuleLayer.defineModulesWithOneLoader;
import static java.util.Objects.requireNonNull;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class LoadHelper {

    public static final String TEST_RESOURCE_JARS_FOLDER = "/de.renew.plugin.command";
    public static final String RENEW_LOAD_TEST_JAR = "TestPlugins-main.jar";

    /**
     * this method checks weather the given module is loaded, and if it is unloads it.
     */
    public static void assertPluginNotLoaded(final String pluginName) {
        requireNonNull(pluginName, "pluginName");
        assertFalse(
            isPluginLoaded(pluginName),
            String.format(
                "plugin '%s' is already loaded, please us another plugin for this test.",
                pluginName));
    }

    public static void assertModuleNotLoaded(final String moduleName)
        throws NoSuchFieldException, ClassNotFoundException, IllegalAccessException
    {
        requireNonNull(moduleName, "moduleName");
        assertFalse(
            isModuleLoaded(moduleName),
            String.format(
                "Module '%s' is already loaded, please use another module for this test.",
                moduleName));
    }

    public static boolean isPluginLoaded(final String pluginName) {
        requireNonNull(pluginName, "pluginName");
        return PluginManager.getInstance().getPlugins().stream().map(IPlugin::getName)
            .anyMatch(pluginName::equals);
    }


    public static boolean isModuleLoaded(final String moduleName)
        throws NoSuchFieldException, ClassNotFoundException, IllegalAccessException
    {
        requireNonNull(moduleName, "moduleName");
        return getLoadedLayers().stream().anyMatch(ll -> ll.findModule(moduleName).isPresent());
    }

    public static URL getJar(final Class<?> clazz, final String jarName) {
        final URL resource = clazz.getResource(TEST_RESOURCE_JARS_FOLDER + "/" + jarName);
        assertNotNull(
            resource,
            "Please check if the file is still in 'test/java/resources" + TEST_RESOURCE_JARS_FOLDER
                + "/" + jarName + "\n'"
                + "Please check the documentation for information on where the jars can be found or recreated.");
        return resource;
    }

    public static void addFinderForTestResources(
        final Class<? extends LoadCommandTest> loadCommandClass)
    {
        final PluginJarLocationFinder jarLocationFinder =
            new PluginJarLocationFinder(loadCommandClass.getResource(TEST_RESOURCE_JARS_FOLDER));
        assertNotNull(jarLocationFinder);
        PluginLocationFinders.getInstance().addLocationFinder(jarLocationFinder);
    }

    public static void addTestModuleLayer(final Class<?> clazz) throws ClassNotFoundException,
        NoSuchFieldException, IllegalAccessException, URISyntaxException
    {
        // check if execution necessary
        if (isModuleLoaded("de.renew.test")) {
            return;
        }

        // do execute
        final List<ModuleLayer> loadedLayers = getLoadedLayers();
        Path[] paths = getModuleLocations();
        paths[0] = Paths.get(getJar(clazz, RENEW_LOAD_TEST_JAR).toURI()).getParent(); // add location to

        final ModuleFinder moduleFinder =
            ModuleFinder.of(Paths.get(getJar(clazz, RENEW_LOAD_TEST_JAR).toURI()));
        Configuration newConfiguration = Configuration.resolve(
            ModuleFinder.of(), List.of(loadedLayers.get(0).configuration()), moduleFinder,
            Set.of(moduleFinder.findAll().stream().findFirst().get().descriptor().name()) // TODO: change with newer Java-Version to Optional.orElseThrow()
        );
        final ModuleLayer layer = defineModulesWithOneLoader(
            newConfiguration, List.of(loadedLayers.get(0)), clazz.getClassLoader()).layer();
        loadedLayers.add(layer);
        assertTrue(isModuleLoaded("de.renew.test"));
    }

    private static List<ModuleLayer> getLoadedLayers()
        throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException
    {
        final Class<?> moduleManagerClass = Class.forName(ModuleManager.class.getCanonicalName());

        final Field loadedLayersField = moduleManagerClass.getDeclaredField("_loadedLayers");

        loadedLayersField.setAccessible(true);

        final ModuleManager moduleManager = getModuleManager();
        //noinspection unchecked
        return (List<ModuleLayer>) loadedLayersField.get(moduleManager);
    }

    private static List<ModuleLayer> getRemovableLayers()
        throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException
    {
        final Class<?> moduleManagerClass = Class.forName(ModuleManager.class.getCanonicalName());

        final Field removableLayersField = moduleManagerClass.getDeclaredField("_removableLayers");

        removableLayersField.setAccessible(true);

        final ModuleManager moduleManager = getModuleManager();
        //noinspection unchecked
        return (List<ModuleLayer>) removableLayersField.get(moduleManager);
    }

    private static Path[] getModuleLocations()
        throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException
    {
        final Class<?> moduleManagerClass = Class.forName(ModuleManager.class.getCanonicalName());
        final Field moduleLocationsField = moduleManagerClass.getDeclaredField("moduleLocations");
        moduleLocationsField.setAccessible(true);
        final ModuleManager moduleManager = getModuleManager();
        return (Path[]) moduleLocationsField.get(moduleManager);
    }


    private static ModuleManager getModuleManager()
        throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException
    {
        final Class<?> pluginManagerClass = Class.forName(PluginManager.class.getCanonicalName());
        final Field moduleManagerField = pluginManagerClass.getDeclaredField("moduleManager");
        moduleManagerField.setAccessible(true);
        return (ModuleManager) moduleManagerField.get(PluginManager.getInstance());
    }

}
