package com.axis.innovators.box.plugins;

import com.axis.innovators.box.Main;
import com.axis.innovators.box.tools.FolderCreator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.jar.*;

/**
 * 插件加载器
 * @author tzdwindows 7
 */
public class PluginLoader {
    private static final Logger logger = LogManager.getLogger(PluginLoader.class);
    public static final String PLUGIN_PATH = FolderCreator.getPluginFolder();
    private static final List<PluginDescriptor> loadedPlugins = new ArrayList<>();
    private static final List<IClassTransformer> transformers = new ArrayList<>();

    public static void loadPlugins() throws IOException {
        File pluginDir = new File(PLUGIN_PATH);
        File[] jars = pluginDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".jar"));

        if (jars == null) {
            return;
        }
        for (int i = 0; i < jars.length; i++) {
            processJarFile(jars[i]);
            Main.progressBarManager.updateSubProgress(
                    "Loading Plugin " + i,
                    i,
                    jars.length);
        }
    }

    private static void processJarFile(File jarFile) throws IOException {
        try (JarFile jar = new JarFile(jarFile)) {
            JarEntry pluginFile = jar.getJarEntry("plug-in.box");
            if (pluginFile != null) {
                processWithManifest(jar, pluginFile, jarFile);
            } else {
                processWithAnnotations(jar, jarFile);
            }

            // Check for CorePlugin in MANIFEST.MF
            Attributes attributes = jar.getManifest().getMainAttributes();
            String corePluginClass = attributes.getValue("CorePlugin");
            if (corePluginClass != null) {
                processCorePlugin(jarFile, corePluginClass);
            }
        }
    }

    private static void processCorePlugin(File jarFile, String corePluginClass) {
        try (URLClassLoader classLoader = new URLClassLoader(
                new URL[]{jarFile.toURI().toURL()},
                PluginLoader.class.getClassLoader())
        ) {
            Class<?> coreClass = classLoader.loadClass(corePluginClass);
            if (LoadingCorePlugin.class.isAssignableFrom(coreClass)) {
                LoadingCorePlugin corePlugin = (LoadingCorePlugin) coreClass.getDeclaredConstructor().newInstance();
                registerTransformers(corePlugin);
            }
        } catch (Exception e) {
            logger.error("Failed to load core plugin: {}", corePluginClass, e);
        }
    }

    private static void registerTransformers(LoadingCorePlugin corePlugin) {
        String[] transformerClasses = corePlugin.getASMTransformerClass();
        if (transformerClasses != null) {
            for (String transformerClass : transformerClasses) {
                try {
                    Class<?> transformerClazz = Class.forName(transformerClass);
                    if (IClassTransformer.class.isAssignableFrom(transformerClazz)) {
                        IClassTransformer transformer = (IClassTransformer) transformerClazz.getDeclaredConstructor().newInstance();
                        transformers.add(transformer);
                    }
                } catch (Exception e) {
                    logger.error("Failed to register transformer: {}", transformerClass, e);
                }
            }
        }
    }

    private static void processWithManifest(JarFile jar, JarEntry entry, File jarFile) {
        Properties props = new Properties();
        try (InputStream is = jar.getInputStream(entry)) {
            props.load(is);
        } catch (IOException e) {
            logger.error("Failed to load plugin from jar: {}", jarFile.getName(), e);
            return;
        }

        PluginDescriptor descriptor = new PluginDescriptor();
        descriptor.setId(props.getProperty("id"));
        descriptor.setName(props.getProperty("name"));
        descriptor.setDescription(props.getProperty("description"));
        descriptor.setIcon(props.getProperty("icon"));
        descriptor.setSupportedVersions(
                Arrays.asList(props.getProperty("supportedVersions").split(","))
        );

        loadMainClass(props.getProperty("mainClass"), jarFile, descriptor);
        if (descriptor.getInstance() != null) {
            loadedPlugins.add(descriptor);
        }
    }

    private static void processWithAnnotations(JarFile jar, File jarFile) {
        URLClassLoader classLoader = createClassLoader(jarFile);
        Enumeration<JarEntry> entries = jar.entries();

        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            if (entry.getName().endsWith(".class")) {
                processClassEntry(entry, classLoader);
            }
        }
    }

    private static URLClassLoader createClassLoader(File jarFile) {
        try {
            return new URLClassLoader(
                    new URL[]{jarFile.toURI().toURL()},
                    PluginLoader.class.getClassLoader()
            );
        } catch (MalformedURLException e) {
            logger.error("Error creating URLClassLoader", e);
        }
        return null;
    }

    private static void processClassEntry(JarEntry entry, URLClassLoader classLoader) {
        String className = entry.getName()
                .replace("/", ".")
                .replace(".class", "");

        try {
            Class<?> clazz = classLoader.loadClass(className);
            PluginMeta meta = clazz.getAnnotation(PluginMeta.class);
            if (meta != null) {
                PluginDescriptor descriptor = new PluginDescriptor();
                descriptor.setId(meta.id());
                descriptor.setName(meta.name());
                descriptor.setDescription(meta.description());
                descriptor.setIcon(meta.icon());
                descriptor.setSupportedVersions(Arrays.asList(meta.supportedVersions()));

                try {
                    Object instance = clazz.getDeclaredConstructor().newInstance();
                    descriptor.setInstance(instance);
                    loadedPlugins.add(descriptor);
                } catch (Exception e) {
                    logger.error("Failed to instantiate plugin class: {}", className, e);
                }
            }
        } catch (ClassNotFoundException | NoClassDefFoundError e) {
            logger.error("Error loading class: {}", className, e);
        }
    }

    private static void loadMainClass(String mainClassName, File jarFile, PluginDescriptor descriptor) {
        if (mainClassName == null || mainClassName.isEmpty()) {
            return;
        }

        try (URLClassLoader classLoader = new URLClassLoader(
                new URL[]{jarFile.toURI().toURL()},
                PluginLoader.class.getClassLoader())
        ) {
            Class<?> mainClass = classLoader.loadClass(mainClassName);
            Object instance = mainClass.getDeclaredConstructor().newInstance();
            descriptor.setInstance(instance);
        } catch (Exception e) {
            logger.error("Failed to load main class: {}", mainClassName, e);
        }
    }

    public static List<PluginDescriptor> getLoadedPlugins() {
        return Collections.unmodifiableList(loadedPlugins);
    }

    public static byte[] transformClass(String name, String transformedName, byte[] basicClass) {
        for (IClassTransformer transformer : transformers) {
            basicClass = transformer.transform(name, transformedName, basicClass);
        }
        return basicClass;
    }
}